pidgin/purple-plugin-pack

merge of '473803c9bd5b7821a3b34914b6785b338a65cdef'
org.guifications.plugins.smartear
2008-03-24, rekkanoryo
bb96382ef953
merge of '473803c9bd5b7821a3b34914b6785b338a65cdef'
and 'b459dd956b446010f04ffcdd1e7e452404efb727'
  • +4 -1
    AUTHORS
  • +15 -1
    ChangeLog
  • +9 -0
    README
  • +1 -1
    VERSION
  • +11 -2
    album/album-ui.c
  • +4 -2
    album/album-ui.h
  • +3 -3
    album/album.c
  • +2 -2
    album/album.h
  • +1 -1
    autogen.sh
  • +60 -0
    autoprofile/Makefile.am
  • +37 -0
    autoprofile/Makefile.mingw
  • +145 -0
    autoprofile/autoaway.c
  • +861 -0
    autoprofile/autoprofile.c
  • +110 -0
    autoprofile/autoprofile.h
  • +324 -0
    autoprofile/autoreply.c
  • +438 -0
    autoprofile/comp_countdownup.c
  • +169 -0
    autoprofile/comp_executable.c
  • +204 -0
    autoprofile/comp_http.c
  • +1042 -0
    autoprofile/comp_logstats.c
  • +28 -0
    autoprofile/comp_logstats.h
  • +355 -0
    autoprofile/comp_logstats_gtk.c
  • +602 -0
    autoprofile/comp_quotation.c
  • +477 -0
    autoprofile/comp_rss.c
  • +52 -0
    autoprofile/comp_rss.h
  • +350 -0
    autoprofile/comp_rss_parser.c
  • +117 -0
    autoprofile/comp_rss_xanga.c
  • +268 -0
    autoprofile/comp_textfile.c
  • +142 -0
    autoprofile/comp_timestamp.c
  • +100 -0
    autoprofile/comp_uptime.c
  • +87 -0
    autoprofile/component.c
  • +73 -0
    autoprofile/component.h
  • +341 -0
    autoprofile/gtk_actions.c
  • +486 -0
    autoprofile/gtk_away_msgs.c
  • +778 -0
    autoprofile/gtk_widget.c
  • +750 -0
    autoprofile/preferences.c
  • +49 -0
    autoprofile/sizes.h
  • +221 -0
    autoprofile/utility.c
  • +41 -0
    autoprofile/utility.h
  • +187 -0
    autoprofile/utility_rfc822.c
  • +607 -0
    autoprofile/widget.c
  • +96 -0
    autoprofile/widget.h
  • +1 -1
    autoreply/autoreply.c
  • +1 -1
    awaynotify/awaynotify.c
  • +1 -1
    bash/bash.c
  • +1 -1
    bit/bit.c
  • +2 -1
    blistops/blistops.c
  • +1 -1
    chronic/chronic.c
  • +1 -1
    common/Makefile.am
  • +0 -84
    common/core_template.c
  • +3 -7
    common/glib_compat.h
  • +1 -1
    common/gtk_template.c
  • +1 -1
    common/pp_internal.h
  • +84 -0
    common/purple_template.c
  • +9 -1
    configure.ac
  • +8 -2
    convbadger/convbadger.c
  • +6 -1
    dewysiwygification/dewysiwygification.c
  • +2 -2
    dice/dice.c
  • +1 -1
    difftopic/difftopic.c
  • +1 -1
    eight_ball/eight_ball.c
  • +19 -10
    enhancedhist/enhancedhist.c
  • +1 -1
    findip/findip.c
  • +5 -1
    gRIM/gRIM.c
  • +1 -1
    groupmsg/groupmsg.c
  • +1 -1
    hideconv/hideconv.c
  • +1 -1
    highlight/highlight.c
  • +2 -2
    ignore/ignore.c
  • +1 -1
    infopane/infopane.c
  • +9 -4
    irc-more/irc-more.c
  • +7 -2
    irchelper/irchelper.c
  • +3 -2
    irssi/datechange.c
  • +3 -2
    irssi/datechange.h
  • +3 -2
    irssi/irssi.c
  • +3 -2
    irssi/lastlog.c
  • +3 -2
    irssi/lastlog.h
  • +3 -2
    irssi/layout.c
  • +3 -2
    irssi/layout.h
  • +8 -3
    irssi/textfmt.c
  • +3 -2
    irssi/textfmt.h
  • +3 -2
    irssi/window.c
  • +3 -2
    irssi/window.h
  • +1 -1
    lastseen/lastseen.c
  • +1 -1
    listhandler/aim_blt_files.c
  • +1 -1
    listhandler/aim_blt_files.h
  • +1 -1
    listhandler/alias_xml_files.c
  • +1 -1
    listhandler/alias_xml_files.h
  • +1 -1
    listhandler/gen_xml_files.c
  • +1 -1
    listhandler/gen_xml_files.h
  • +1 -1
    listhandler/lh_util.c
  • +1 -1
    listhandler/lh_util.h
  • +1 -1
    listhandler/listhandler.c
  • +1 -1
    listhandler/listhandler.h
  • +1 -1
    listhandler/migrate.c
  • +1 -1
    listhandler/migrate.h
  • +1 -1
    listhandler/purple_blist_xml.c
  • +1 -1
    listhandler/purple_blist_xml.h
  • +59 -20
    m4/pluginpack.m4
  • +1 -1
    mystatusbox/mystatusbox.c
  • +0 -0
    napster/16/napster.png
  • +0 -0
    napster/22/napster.png
  • +0 -0
    napster/48/napster.png
  • +12 -15
    napster/Makefile.am
  • +7 -4
    napster/napster.c
  • +0 -0
    napster/napster16x16.png
  • +0 -0
    napster/napster22x22.png
  • +0 -0
    napster/napster48x48.png
  • +1 -1
    nicksaid/nicksaid.c
  • +1 -1
    oldlogger/oldlogger.c
  • +2 -2
    plonkers/plonkers.c
  • +4 -0
    plugin_pack.spec.in
  • +1 -3
    po/POTFILES.in
  • +1 -1
    schedule/pidgin-schedule.c
  • +1 -1
    schedule/schedule.c
  • +1 -1
    schedule/schedule.h
  • +1 -1
    sepandtab/sepandtab.c
  • +1 -1
    showoffline/showoffline.c
  • +1 -1
    simfix/simfix.c
  • +5 -5
    slashexec/slashexec.c
  • +39 -13
    smartear/Makefile.am
  • +1 -3
    smartear/gtksmartear.c
  • +11 -2
    snpp/snpp.c
  • +1 -1
    sslinfo/sslinfo.c
  • +1 -1
    stocker/stocker.c
  • +1 -1
    stocker/stocker_prefs.c
  • +1 -1
    stocker/stocker_prefs.h
  • +2 -1
    switchspell/switchspell.c
  • +2 -3
    timelog/log-widget.c
  • +3 -4
    timelog/log-widget.h
  • +2 -3
    timelog/range-widget.c
  • +3 -4
    timelog/range-widget.h
  • +2 -3
    timelog/timelog.c
  • +3 -4
    timelog/timelog.h
  • +48 -13
    xchat-chats/xchat-chats.c
  • +1 -1
    xmmsremote/xmmsremote.c
  • --- a/AUTHORS Sun Dec 23 07:18:21 2007 -0500
    +++ b/AUTHORS Mon Mar 24 15:08:23 2008 -0400
    @@ -10,6 +10,9 @@
    Sadrul H Chowdhury <sadrul@users.sourceforge.net>
    Richard Laager <rlaager@guifications.org>
    Martijn van Oosterhout <kleptog@svana.org>
    +Matt Perry <guy@fscked.org>
    +Andrew Pangborn <gaim@andrewpangborn.com>
    +Casey Ho
    Translators
    ===========
    @@ -40,13 +43,13 @@
    Lucas Paul
    Chris Petersen
    Jérôme Poulin (TiCPU)
    +qwert
    Lee Roach
    Ryan Seeber
    Ankit Singla
    Anthony Sofocleous
    William Thompson
    Chris Weyl
    -qwert - realphabetize or replace with real name before release!
    Special Thanks
    ==============
    --- a/ChangeLog Sun Dec 23 07:18:21 2007 -0500
    +++ b/ChangeLog Mon Mar 24 15:08:23 2008 -0400
    @@ -1,4 +1,8 @@
    -Version 2.3.0mtn:
    +Version 2.4.0mtn:
    + * Merged the Autoprofile plugin into our build system.
    + * Fixed convbadger's failure to update on conversation switch.
    +
    +Version 2.3.0: 03/17/08
    * Fixed a typo in irc-more's source that allowed a potential double-free
    * Fixed unregistering commands when unloading gRIM and Magic 8 ball plugin
    * Fixed napster plugin. It builds cleanly and loads properly now.
    @@ -10,8 +14,18 @@
    (http://en.wikipedia.org/wiki/Dice_notation), but it's not perfect yet.
    (Lucas Paul)
    * Dice plugin now calls the /me command with its output.
    + * Irc-more plugin adds notice support only when built against libpurple
    + older than 2.4.0.
    * Napster plugin now builds by default.
    * Removed the broadcast plugin.
    + * Memory leak fixes
    + * Fixed the --with-plugins configure argument. It now correctly handles
    + all, default, and a comma separated list of plugins.
    + * Fixed a crash in the xchat-chats plugin which occurs due to the changes
    + to the conversation window in 2.4.0.
    + * Fixed a missing header include in the timelog plugin which caused a
    + plugin load failure under some circumstances.
    + * Finally added some content to README
    Version 2.2.0: 10/25/07
    * Added 'menuconfig' script to make it easier to select what plugins to
    --- a/README Sun Dec 23 07:18:21 2007 -0500
    +++ b/README Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,9 @@
    +The Purple Plugin Pack was originally created by Gary Kramlich and Stu
    +Tomlinson as a way to distribute their ever growing lists of simple Pidgin
    +plugins. It has since grown from its origins of about 6 plugins to nearly 50.
    +
    +Also, many more developers have continued to add to it, including John Bailey,
    +Peter Lawler, Sadrul Habib Chowdhury, and most recently Richard Laager.
    +
    +More information on the Plugin Pack can be found at
    +http://plugins.guifications.org/trac/wiki/PluginPack
    --- a/VERSION Sun Dec 23 07:18:21 2007 -0500
    +++ b/VERSION Mon Mar 24 15:08:23 2008 -0400
    @@ -1,1 +1,1 @@
    -2.3.0mtn
    +2.4.0mtn
    --- a/album/album-ui.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/album/album-ui.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,8 +1,8 @@
    /*
    * Album (Buddy Icon Archiver)
    *
    - * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
    - * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com>
    + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im>
    * Copyright (C) 2006, Jérôme Poulin (TiCPU) <jeromepoulin@gmail.com>
    *
    * This program is free software; you can redistribute it and/or
    @@ -126,6 +126,13 @@
    static void update_icon_view(icon_viewer_key *key);
    static void show_buddy_icon_window(icon_viewer_key *key, const char *name);
    +void icon_viewer_key_free(void *data)
    +{
    + icon_viewer_key *key = (icon_viewer_key *)data;
    + g_free(key->screenname);
    + g_free(key);
    +}
    +
    guint icon_viewer_hash(gconstpointer data)
    {
    const icon_viewer_key *key = data;
    @@ -932,6 +939,7 @@
    /* Return if a window is already opened for the buddy. */
    if ((bw = g_hash_table_lookup(buddy_windows, key)) != NULL)
    {
    + icon_viewer_key_free(key);
    gtk_window_present(GTK_WINDOW(bw->window));
    return;
    }
    @@ -942,6 +950,7 @@
    if (key->contact == NULL &&
    (bw = g_hash_table_find(buddy_windows, (GHRFunc)compare_buddy_keys, key)) != NULL)
    {
    + icon_viewer_key_free(key);
    gtk_window_present(GTK_WINDOW(bw->window));
    return;
    }
    --- a/album/album-ui.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/album/album-ui.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,8 +1,8 @@
    /*
    * Album (Buddy Icon Archiver)
    *
    - * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
    - * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com>
    + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -36,6 +36,8 @@
    gboolean icon_viewer_equal(gconstpointer y, gconstpointer z);
    +void icon_viewer_key_free(void *key);
    +
    GList *album_get_plugin_actions(PurplePlugin *plugin, gpointer data);
    void album_blist_node_menu_cb(PurpleBlistNode *node, GList **menu);
    --- a/album/album.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/album/album.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,8 +1,8 @@
    /*
    * Album (Buddy Icon Archiver)
    *
    - * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
    - * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com>
    + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -253,7 +253,7 @@
    cache_existing_icons(NULL);
    - buddy_windows = g_hash_table_new_full(icon_viewer_hash, icon_viewer_equal, g_free, g_free);
    + buddy_windows = g_hash_table_new_full(icon_viewer_hash, icon_viewer_equal, icon_viewer_key_free, g_free);
    return TRUE;
    }
    --- a/album/album.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/album/album.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,8 +1,8 @@
    /*
    * Album (Buddy Icon Archiver)
    *
    - * Copyright (C) 2005-2006, Sadrul Habib Chowdhury <imadil@gmail.com>
    - * Copyright (C) 2005-2006, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2005-2008, Sadrul Habib Chowdhury <imadil@gmail.com>
    + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/autogen.sh Sun Dec 23 07:18:21 2007 -0500
    +++ b/autogen.sh Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    #! /bin/sh
    # Guifications - The end-all, be-all notification framework
    -# Copyright (C) 2003-2007 Gary Kramlich <grim@reaperworld.com>
    +# Copyright (C) 2003-2008 Gary Kramlich <grim@reaperworld.com>
    #
    # 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
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/Makefile.am Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,60 @@
    +EXTRA_DIST = \
    + .build \
    + .pidgin-plugin
    +
    +autoprofiledir = $(PURPLE_LIBDIR)
    +
    +autoprofile_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +autoprofile_LTLIBRARIES = autoprofile.la
    +
    +autoprofile_la_SOURCES = \
    + autoaway.c \
    + autoprofile.c \
    + autoprofile.h \
    + autoreply.c \
    + comp_countdownup.c \
    + comp_executable.c \
    + comp_http.c \
    + comp_logstats.c \
    + comp_logstats_gtk.c \
    + comp_logstats.h \
    + component.c \
    + component.h \
    + comp_quotation.c \
    + comp_rss.c \
    + comp_rss.h \
    + comp_rss_parser.c \
    + comp_rss_xanga.c \
    + comp_textfile.c \
    + comp_timestamp.c \
    + comp_uptime.c \
    + gtk_actions.c \
    + gtk_away_msgs.c \
    + gtk_widget.c \
    + Makefile.am \
    + preferences.c \
    + sizes.h \
    + utility.c \
    + utility.h \
    + utility_rfc822.c \
    + widget.c \
    + widget.h
    +
    +autoprofile_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GLIB_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS) \
    + $(GTK_CFLAGS) \
    + $(GLIB_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/Makefile.mingw Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,37 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for autoprofile plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = autoprofile
    +
    +PP_SRC := \
    + autoaway.c \
    + autoprofile.c \
    + autoreply.c \
    + comp_countdownup.c \
    + comp_executable.c \
    + comp_http.c \
    + comp_logstats.c \
    + comp_logstats_gtk.c \
    + component.c \
    + comp_quotation.c \
    + comp_rss.c \
    + comp_rss_parser.c \
    + comp_rss_xanga.c \
    + comp_textfile.c \
    + comp_timestamp.c \
    + comp_uptime.c \
    + gtk_actions.c \
    + gtk_away_msgs.c \
    + gtk_widget.c \
    + preferences.c \
    + utility.c \
    + utility_rfc822.c \
    + widget.c \
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/autoaway.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,145 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +#include "idle.h"
    +#include "conversation.h"
    +
    +#define AP_IDLE_CHECK_INTERVAL 5
    +
    +static guint check_timeout = 0;
    +static guint pref_cb = 0;
    +static time_t last_active_time = 0;
    +
    +static gboolean is_idle ()
    +{
    + PurpleIdleUiOps *ui_ops;
    + time_t time_idle;
    + const gchar *idle_reporting;
    +
    + ui_ops = purple_idle_get_ui_ops ();
    +
    + idle_reporting = purple_prefs_get_string ("/core/away/idle_reporting");
    + if (!strcmp (idle_reporting, "system") &&
    + (ui_ops != NULL) && (ui_ops->get_time_idle != NULL)) {
    + time_idle = time (NULL) - last_active_time;
    + } else if (!strcmp (idle_reporting, "gaim")) {
    + time_idle = time (NULL) - last_active_time;
    + } else {
    + time_idle = 0;
    + }
    +
    + return (time_idle >
    + (60 * purple_prefs_get_int("/core/away/mins_before_away")));
    +}
    +
    +static gboolean ap_check_idleness (gpointer data)
    +{
    + gboolean auto_away;
    +
    + // ap auto idle
    + // 0 0 0 don't do anything
    + // 0 0 1 ap_use_idleaway ()
    + // 1 0 x don't do anything, we're already away
    + // 1 1 0 ap_dont_use_idleaway ()
    + // 1 1 1 don't do anything
    +
    + if (ap_is_currently_away () && !ap_autoaway_in_use ()) return TRUE;
    + auto_away = purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/away_when_idle");
    +
    + if (is_idle ()) {
    + if (auto_away && !ap_is_currently_away () && !ap_autoaway_in_use ()) {
    + ap_autoaway_enable ();
    + }
    + } else {
    + if (ap_is_currently_away () && ap_autoaway_in_use ()) {
    + ap_autoaway_disable ();
    + }
    + }
    +
    + return TRUE;
    +}
    +
    +void ap_autoaway_touch ()
    +{
    + time (&last_active_time);
    +}
    +
    +
    +static gboolean writing_im_msg_cb (PurpleAccount *account, const char *who,
    + char **message, PurpleConversation *conv, PurpleMessageFlags flags)
    +{
    + ap_autoaway_touch ();
    + ap_check_idleness (NULL);
    + return FALSE;
    +}
    +
    +static void auto_pref_cb (
    + const char *name, PurplePrefType type, gconstpointer val, gpointer data)
    +{
    + if (!purple_prefs_get_bool ("/core/away/away_when_idle")) return;
    +
    + purple_notify_error (NULL, NULL,
    + N_("This preference is disabled"),
    + N_("This preference currently has no effect because AutoProfile is in "
    + "use. To modify this behavior, use the AutoProfile configuration "
    + "menu."));
    +
    + purple_prefs_set_bool ("/core/away/away_when_idle", FALSE);
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Global functions to start it all *
    + *--------------------------------------------------------------------------*/
    +void ap_autoaway_start ()
    +{
    + purple_prefs_set_bool ("/core/away/away_when_idle", FALSE);
    +
    + check_timeout = purple_timeout_add (AP_IDLE_CHECK_INTERVAL * 1000,
    + ap_check_idleness, NULL);
    +
    + purple_signal_connect (purple_conversations_get_handle (), "writing-im-msg",
    + ap_get_plugin_handle (), PURPLE_CALLBACK(writing_im_msg_cb), NULL);
    +
    + pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (),
    + "/core/away/away_when_idle", auto_pref_cb, NULL);
    +
    + ap_autoaway_touch ();
    +}
    +
    +void ap_autoaway_finish ()
    +{
    + // Assumes signals are disconnected globally
    +
    + purple_prefs_disconnect_callback (pref_cb);
    + pref_cb = 0;
    +
    + if (check_timeout > 0) purple_timeout_remove (check_timeout);
    + check_timeout = 0;
    +
    + purple_prefs_set_bool ("/core/away/away_when_idle",
    + purple_prefs_get_bool ("/plugins/gtk/autoprofile/away_when_idle"));
    +}
    +
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/autoprofile.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,861 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +
    +#include "version.h"
    +#include "savedstatuses.h"
    +
    +/* General functions */
    +static void ap_status_changed (
    + const char *, PurplePrefType, gconstpointer, gpointer);
    +static void ap_account_connected (PurpleConnection *);
    +
    +static void ap_delete_legacy_prefs ();
    +
    +static void ap_update_queue_start ();
    +static void ap_update_queue_finish ();
    +
    +/*--------------------------------------------------------------------------
    + * GENERAL VARIABLES
    + *------------------------------------------------------------------------*/
    +
    +static PurplePlugin *plugin_handle = NULL;
    +static PurpleSavedStatus *current_ap_status = NULL;
    +
    +static GStaticMutex update_timeout_mutex = G_STATIC_MUTEX_INIT;
    +static GHashTable *update_timeouts = NULL;
    +
    +static gboolean using_idleaway = FALSE;
    +
    +static GStaticMutex update_queue_mutex = G_STATIC_MUTEX_INIT;
    +static GList *queued_profiles = NULL;
    +static guint update_queue_timeout = 0;
    +
    +/* Functions related to general variables */
    +PurplePlugin *ap_get_plugin_handle () { return plugin_handle; }
    +
    +gboolean ap_is_currently_away () {
    + return current_ap_status != NULL &&
    + purple_savedstatus_get_type (current_ap_status) == PURPLE_STATUS_AWAY;
    +}
    +
    +/*--------------------------------------------------------------------------
    + * REQUIRED GAIM FUNCTIONS- INFO, INITIALIZATION, UNLOADING
    + *------------------------------------------------------------------------*/
    +/* What to do when plugin is loaded */
    +static gboolean plugin_load (PurplePlugin *plugin)
    +{
    + GList *accounts_pref;
    +
    + ap_debug ("general", "AutoProfile is being loaded");
    +
    + plugin_handle = plugin;
    + current_ap_status = purple_savedstatus_new (NULL, PURPLE_STATUS_UNSET);
    + update_timeouts = g_hash_table_new (NULL, NULL);
    +
    + ap_delete_legacy_prefs ();
    +
    + /* The core autoprofile tracking system */
    + purple_prefs_connect_callback (plugin_handle, "/core/savedstatus/current",
    + ap_status_changed, NULL);
    + purple_signal_connect (purple_connections_get_handle (),
    + "signed-on", plugin_handle,
    + PURPLE_CALLBACK (ap_account_connected), NULL);
    +
    + ap_component_start ();
    + ap_gtk_start ();
    +
    + accounts_pref = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/profile_accounts");
    + ap_gtk_set_progress_visible (AP_UPDATE_PROFILE, (accounts_pref != NULL));
    + free_string_list (accounts_pref);
    +
    + ap_update_after_delay (AP_UPDATE_STATUS);
    + ap_update_after_delay (AP_UPDATE_PROFILE);
    +
    + ap_autoaway_start ();
    + ap_autoreply_start ();
    +
    + ap_update_queue_start ();
    +
    + return TRUE;
    +}
    +
    +/* What to do when plugin is unloaded */
    +static gboolean plugin_unload (PurplePlugin *plugin)
    +{
    + ap_update_queue_finish ();
    +
    + ap_autoreply_finish ();
    + ap_autoaway_finish ();
    +
    + /* General vars */
    + using_idleaway = FALSE;
    +
    + ap_update_stop (AP_UPDATE_STATUS);
    + ap_update_stop (AP_UPDATE_PROFILE);
    +
    + /* Disconnect tracking system */
    + purple_signals_disconnect_by_handle (plugin);
    +
    + ap_actions_finish ();
    + ap_gtk_finish ();
    + ap_component_finish ();
    +
    + g_hash_table_destroy (update_timeouts);
    + return TRUE;
    +}
    +
    +/* General information */
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD, /* type */
    + PIDGIN_PLUGIN_TYPE, /* ui_requirement */
    + 0, /* flags */
    + NULL, /* dependencies */
    + PURPLE_PRIORITY_DEFAULT, /* priority */
    +
    + N_("gtk-kluge-autoprofile"), /* id */
    + N_("AutoProfile"), /* name */
    + PP_VERSION, /* version */
    + N_("User profile and status message content generator"),/* summary */
    + /* description */
    + N_("Allows user to place dynamic text into profiles\n"
    + "and status messages, with the text automatically\n"
    + "updated whenever content changes"),
    + /* author */
    + N_("Casey Ho <casey at hkn-berkeley-edu>"
    + "\n\t\t\taim:caseyho"),
    + N_("http://autoprofile.sourceforge.net/"), /* homepage */
    + plugin_load, /* load */
    + plugin_unload, /* unload */
    + NULL, /* destroy */
    +
    + &ui_info, /* ui_info */
    + NULL, /* extra_info */
    + NULL, /* prefs_info */
    + actions /* actions */
    +};
    +
    +/*--------------------------------------------------------------------------
    + * CORE FUNCTIONS
    + *------------------------------------------------------------------------*/
    +
    +static gint get_max_size_status (
    + const PurpleAccount *account, const PurpleStatusPrimitive type) {
    + const char *id;
    +
    + if (account == NULL) {
    + switch (type) {
    + case PURPLE_STATUS_AVAILABLE: return AP_SIZE_AVAILABLE_MAX;
    + case PURPLE_STATUS_AWAY: return AP_SIZE_AWAY_MAX;
    + default: return AP_SIZE_MAXIMUM;
    + }
    + } else {
    + id = purple_account_get_protocol_id (account);
    +
    + switch (type) {
    + case PURPLE_STATUS_AVAILABLE:
    + if (!strcmp (id, "prpl-oscar")) return AP_SIZE_AVAILABLE_AIM;
    + else if (!strcmp (id, "prpl-msn")) return AP_SIZE_AVAILABLE_MSN;
    + else if (!strcmp (id, "prpl-yahoo")) return AP_SIZE_AVAILABLE_YAHOO;
    + else return AP_SIZE_AVAILABLE_MAX;
    + case PURPLE_STATUS_AWAY:
    + if (!strcmp (id, "prpl-oscar")) return AP_SIZE_AWAY_AIM;
    + else return AP_SIZE_AWAY_MAX;
    + default:
    + return AP_SIZE_MAXIMUM;
    + }
    + }
    +}
    +
    +static const char *ap_savedstatus_get_message (
    + const PurpleSavedStatus *status, const PurpleAccount *account)
    +{
    + const PurpleSavedStatusSub *substatus;
    +
    + substatus = purple_savedstatus_get_substatus(status, account);
    + if (substatus != NULL) {
    + return purple_savedstatus_substatus_get_message (substatus);
    + }
    + return purple_savedstatus_get_message (status);
    +}
    +
    +static PurpleStatusPrimitive ap_savedstatus_get_type (
    + const PurpleSavedStatus *status, const PurpleAccount *account)
    +{
    + const PurpleSavedStatusSub *substatus;
    +
    + substatus = purple_savedstatus_get_substatus(status, account);
    + if (substatus != NULL) {
    + return purple_status_type_get_primitive (
    + purple_savedstatus_substatus_get_type (substatus));
    + }
    + return purple_savedstatus_get_type (status);
    +}
    +
    +gchar *ap_get_sample_status_message (PurpleAccount *account)
    +{
    + const PurpleSavedStatus *s;
    + const gchar *message;
    + PurpleStatusPrimitive type;
    +
    + s = (using_idleaway? purple_savedstatus_get_idleaway () :
    + purple_savedstatus_get_current ());
    + message = ap_savedstatus_get_message (s, account);
    + type = ap_savedstatus_get_type (s, account);
    +
    + if (!message) return NULL;
    + return ap_generate (message, get_max_size_status (account, type));
    +}
    +
    +/* Generator helper */
    +static gchar *ap_process_replacement (const gchar *f) {
    + GString *s;
    + struct widget *w;
    + gchar *result;
    +
    + w = ap_widget_find (f);
    + if (w) {
    + result = w->component->generate (w);
    + return result;
    + } else {
    + s = g_string_new ("");
    + g_string_printf (s, "[%s]", f);
    + result = s->str;
    + g_string_free (s, FALSE);
    + return result;
    + }
    +}
    +
    +/* The workhorse generation function! */
    +gchar *ap_generate (const gchar *f, gint max_length) {
    + GString *output;
    + gchar *result;
    + gchar *format, *format_start, *percent_start;
    + gchar *replacement;
    + int state;
    +
    + output = g_string_new ("");
    + format_start = format = purple_utf8_salvage (f);
    +
    + /* When a % has been read (and searching for next %), state is 1
    + * otherwise it is 0
    + */
    + state = 0;
    + percent_start = NULL;
    +
    + while (*format) {
    + if (state == 1) {
    + if (*format == '[') {
    + g_string_append_unichar (output, g_utf8_get_char ("["));
    + *format = '\0';
    + g_string_append (output, percent_start);
    + percent_start = format = format+1;
    + } else if (*format == ']') {
    + *format = '\0';
    + format++;
    + state = 0;
    + replacement = ap_process_replacement (percent_start);
    + percent_start = NULL;
    + g_string_append (output, replacement);
    + free (replacement);
    + } else {
    + format = g_utf8_next_char (format);
    + }
    + } else {
    + if (*format == '\n') {
    + g_string_append (output, "<br>");
    + } else if (*format == '[') {
    + state = 1;
    + percent_start = format+1;
    + } else {
    + g_string_append_unichar (output, g_utf8_get_char (format));
    + }
    + format = g_utf8_next_char (format);
    + }
    + }
    +
    + /* Deal with case where final ] not found */
    + if (state == 1) {
    + g_string_append_unichar (output, g_utf8_get_char ("["));
    + g_string_append (output, percent_start);
    + }
    +
    + /* Set size limit */
    + g_string_truncate (output, max_length);
    +
    + /* Finish up */
    + free (format_start);
    + result = purple_utf8_salvage(output->str);
    + g_string_free (output, TRUE);
    +
    + return result;
    +}
    +
    +void ap_account_enable_profile (const PurpleAccount *account, gboolean enable) {
    + GList *original, *new;
    + gboolean original_status;
    +
    + gchar *username, *protocol_id;
    +
    + GList *node, *tmp;
    + GList *ret = NULL;
    +
    + original_status = ap_account_has_profile_enabled (account);
    + if (original_status == enable) {
    + ap_debug_warn ("profile", "New status identical to original, skipping");
    + return;
    + }
    +
    + original = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/profile_accounts");
    + username = strdup (purple_account_get_username (account));
    + protocol_id = strdup (purple_account_get_protocol_id (account));
    +
    + if (!enable) {
    + /* Remove from the list */
    + ap_debug ("profile", "Disabling profile updates for account");
    +
    + while (original) {
    + if (!strcmp (original->data, username) &&
    + !strcmp (original->next->data, protocol_id)) {
    + node = original;
    + tmp = node->next;
    + original = original->next->next;
    + free (node->data);
    + free (tmp->data);
    + g_list_free_1 (node);
    + g_list_free_1 (tmp);
    + free (username);
    + free (protocol_id);
    + } else {
    + node = original;
    + original = original->next->next;
    + node->next->next = ret;
    + ret = node;
    + }
    + }
    +
    + new = ret;
    + } else {
    + /* Otherwise add on */
    + GList *ret_start, *ret_end;
    +
    + ap_debug ("profile", "enabling profile updates for account");
    +
    + ret_start = (GList *) malloc (sizeof (GList));
    + ret_end = (GList *) malloc (sizeof (GList));
    + ret_start->data = username;
    + ret_start->next = ret_end;
    + ret_end->data = protocol_id;
    + ret_end->next = original;
    +
    + new = ret_start;
    + }
    +
    + purple_prefs_set_string_list (
    + "/plugins/gtk/autoprofile/profile_accounts", new);
    + ap_gtk_set_progress_visible (AP_UPDATE_PROFILE, (new != NULL));
    +
    + free_string_list (new);
    +}
    +
    +gboolean ap_account_has_profile_enabled (const PurpleAccount *account) {
    + GList *accounts_list, *start_list;
    +
    + accounts_list = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/profile_accounts");
    +
    + start_list = accounts_list;
    +
    + /* Search through list of values */
    + while (accounts_list) {
    + // Make sure these things come in pairs
    + if (accounts_list->next == NULL) {
    + ap_debug_error ("is_account_profile_enabled", "invalid account string");
    + free_string_list (start_list);
    + return FALSE;
    + }
    +
    + // Check usernames
    + if (!strcmp ((char *) accounts_list->data, account->username)) {
    + // Check protocol
    + if (!strcmp ((char *) accounts_list->next->data, account->protocol_id))
    + {
    + free_string_list (start_list);
    + return TRUE;
    + }
    + }
    +
    + accounts_list = accounts_list->next->next;
    + }
    +
    + /* Not found, hence it wasn't enabled */
    + free_string_list (start_list);
    + return FALSE;
    +}
    +
    +/* Profiles: Update every so often */
    +static gboolean ap_update_profile () {
    + PurpleAccount *account;
    + const GList *purple_accounts;
    + gboolean account_updated;
    +
    + const char *format;
    + const char *old_info;
    + char *generated_profile;
    +
    + /* Generate the profile text */
    + format = purple_prefs_get_string ("/plugins/gtk/autoprofile/profile");
    +
    + if (format == NULL) {
    + ap_debug_error ("general", "profile is null");
    + return FALSE;
    + }
    +
    + generated_profile = ap_generate (format, AP_SIZE_PROFILE_MAX);
    +
    + // If string is blank, nothing would happen
    + if (*generated_profile == '\0') {
    + free (generated_profile);
    + ap_debug_misc ("general", "empty profile set");
    + generated_profile = strdup (" ");
    + }
    +
    + /* Get all accounts and search through each */
    + account_updated = FALSE;
    + for (purple_accounts = purple_accounts_get_all ();
    + purple_accounts != NULL;
    + purple_accounts = purple_accounts->next) {
    + account = (PurpleAccount *)purple_accounts->data;
    + old_info = purple_account_get_user_info (account);
    +
    + /* Check to see if update option set on account */
    + if (ap_account_has_profile_enabled (account) &&
    + (old_info == NULL || strcmp (old_info, generated_profile))) {
    + purple_account_set_user_info (account, generated_profile);
    + account_updated = TRUE;
    +
    + if (purple_account_is_connected (account)) {
    + g_static_mutex_lock (&update_queue_mutex);
    + if (g_list_find (queued_profiles, account) == NULL) {
    + queued_profiles = g_list_append (queued_profiles, account);
    + }
    + g_static_mutex_unlock (&update_queue_mutex);
    + } else {
    + ap_debug_misc ("general", "account not online, not setting profile");
    + }
    + }
    + }
    +
    + if (account_updated) {
    + ap_gtk_add_message (AP_UPDATE_PROFILE, AP_MESSAGE_TYPE_PROFILE,
    + generated_profile);
    + }
    +
    + free (generated_profile);
    + return account_updated;
    +}
    +
    +static gboolean ap_update_status ()
    +{
    + const PurpleSavedStatus *template_status;
    + GHashTable *substatus_messages;
    + gchar *new_message, *new_substatus_message;
    + const gchar *sample_message, *old_message;
    + const GList *accounts;
    + gboolean updated;
    + PurpleStatusPrimitive old_type, new_type;
    + const PurpleStatusType *substatus_type;
    + PurpleAccount *account;
    + PurpleSavedStatusSub *substatus;
    +
    + template_status = (using_idleaway? purple_savedstatus_get_idleaway () :
    + purple_savedstatus_get_current ());
    + updated = FALSE;
    +
    + /* If there are substatuses */
    + if (purple_savedstatus_has_substatuses (template_status)) {
    + substatus_messages = g_hash_table_new (NULL, NULL);
    + for (accounts = purple_accounts_get_all ();
    + accounts != NULL;
    + accounts = accounts->next)
    + {
    + account = (PurpleAccount *) accounts->data;
    +
    + substatus = purple_savedstatus_get_substatus (template_status, account);
    + if (substatus) {
    + new_type = purple_status_type_get_primitive (
    + purple_savedstatus_substatus_get_type (substatus));
    + sample_message =
    + purple_savedstatus_substatus_get_message (substatus);
    +
    + if (sample_message) {
    + new_substatus_message = ap_generate (sample_message,
    + get_max_size_status (account, new_type));
    + } else {
    + new_substatus_message = NULL;
    + }
    +
    + g_hash_table_insert (substatus_messages, account,
    + new_substatus_message);
    +
    + if (!updated) {
    + old_type = ap_savedstatus_get_type (current_ap_status, account);
    + old_message =
    + ap_savedstatus_get_message (current_ap_status, account);
    +
    + if ((old_type != new_type) ||
    + ((old_message == NULL || new_substatus_message == NULL) &&
    + (old_message != new_substatus_message)) ||
    + (old_message != NULL && new_substatus_message != NULL &&
    + strcmp (old_message, new_substatus_message)))
    + {
    + updated = TRUE;
    + }
    + }
    + }
    + }
    + } else {
    + substatus_messages = NULL;
    + }
    +
    + /* And then the generic main message */
    + sample_message = purple_savedstatus_get_message (template_status);
    + if (sample_message) {
    + new_message = ap_generate (sample_message, get_max_size_status (NULL,
    + purple_savedstatus_get_type (template_status)));
    + } else {
    + new_message = NULL;
    + }
    +
    + new_type = purple_savedstatus_get_type (template_status);
    +
    + if (!updated) {
    + old_type = purple_savedstatus_get_type (current_ap_status);
    + old_message = purple_savedstatus_get_message (current_ap_status);
    +
    + if ((old_type != new_type) ||
    + ((old_message == NULL || new_message == NULL) &&
    + (old_message != new_message)) ||
    + (old_message != NULL && new_message != NULL &&
    + strcmp (old_message, new_message)))
    + {
    + updated = TRUE;
    + }
    + }
    +
    + if (updated) {
    + APMessageType type;
    + PurpleSavedStatus *new_status;
    +
    + new_status = purple_savedstatus_new (NULL, new_type);
    +
    + if (new_message) {
    + purple_savedstatus_set_message (new_status, new_message);
    + }
    +
    + for (accounts = purple_accounts_get_all ();
    + accounts != NULL;
    + accounts = accounts->next) {
    + account = (PurpleAccount *) accounts->data;
    + substatus = purple_savedstatus_get_substatus (template_status, account);
    +
    + if (substatus != NULL) {
    + substatus_type = purple_savedstatus_substatus_get_type (substatus);
    + new_substatus_message = (gchar *)
    + g_hash_table_lookup (substatus_messages, account);
    + purple_savedstatus_set_substatus (
    + new_status, account, substatus_type, new_substatus_message);
    + free (new_substatus_message);
    + }
    +
    + purple_savedstatus_activate_for_account (new_status, account);
    + }
    +
    + current_ap_status = new_status;
    +
    + if (new_type == PURPLE_STATUS_AVAILABLE) type = AP_MESSAGE_TYPE_AVAILABLE;
    + else if (new_type == PURPLE_STATUS_AWAY) type = AP_MESSAGE_TYPE_AWAY;
    + else type = AP_MESSAGE_TYPE_STATUS;
    +
    + ap_gtk_add_message (AP_UPDATE_STATUS, type, new_message);
    + }
    +
    + if (new_message) free (new_message);
    + if (substatus_messages) {
    + g_hash_table_destroy (substatus_messages);
    + }
    +
    + ap_update_queueing ();
    +
    + return updated;
    +}
    +
    +static gboolean ap_update_cb (gpointer data) {
    + gboolean result;
    + guint timeout;
    + guint delay;
    +
    + g_static_mutex_lock (&update_timeout_mutex);
    +
    + /* Start by removing timeout to self no matter what */
    + timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts, data));
    + if (timeout) purple_timeout_remove (timeout);
    +
    + /* In future, check here if widget content has changed? */
    +
    + switch (GPOINTER_TO_INT (data)) {
    + case AP_UPDATE_STATUS:
    + result = ap_update_status ();
    + break;
    + case AP_UPDATE_PROFILE:
    + result = ap_update_profile ();
    + break;
    + default:
    + result = TRUE;
    + }
    +
    + if (!result) {
    + ap_debug ("general", "Content hasn't changed, updating later");
    + delay = AP_SCHEDULE_UPDATE_DELAY;
    + } else {
    + ap_debug ("general", "Content updated");
    + delay =
    + purple_prefs_get_int ("/plugins/gtk/autoprofile/delay_update") * 1000;
    + }
    + timeout = purple_timeout_add (delay, ap_update_cb, data);
    + g_hash_table_insert (update_timeouts, data, GINT_TO_POINTER (timeout));
    +
    + g_static_mutex_unlock (&update_timeout_mutex);
    +
    + return FALSE;
    +}
    +
    +void ap_update (APUpdateType type)
    +{
    + ap_update_cb (GINT_TO_POINTER (type));
    +}
    +
    +void ap_update_after_delay (APUpdateType type)
    +{
    + guint timeout;
    +
    + g_static_mutex_lock (&update_timeout_mutex);
    +
    + timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts,
    + GINT_TO_POINTER (type)));
    + if (timeout) purple_timeout_remove (timeout);
    +
    + timeout = purple_timeout_add (AP_SCHEDULE_UPDATE_DELAY, ap_update_cb,
    + GINT_TO_POINTER (type));
    + g_hash_table_insert (update_timeouts, GINT_TO_POINTER (type),
    + GINT_TO_POINTER (timeout));
    +
    + g_static_mutex_unlock (&update_timeout_mutex);
    +}
    +
    +void ap_update_stop (APUpdateType type)
    +{
    + guint timeout;
    +
    + g_static_mutex_lock (&update_timeout_mutex);
    +
    + timeout = GPOINTER_TO_INT (g_hash_table_lookup (update_timeouts,
    + GINT_TO_POINTER (type)));
    + if (timeout) purple_timeout_remove (timeout);
    +
    + g_hash_table_insert (update_timeouts, GINT_TO_POINTER (type), 0);
    +
    + g_static_mutex_unlock (&update_timeout_mutex);
    +}
    +
    +static void ap_account_connected (PurpleConnection *gc) {
    + ap_debug ("general", "Account connection detected");
    + ap_update_after_delay (AP_UPDATE_PROFILE);
    + ap_update_after_delay (AP_UPDATE_STATUS);
    +}
    +
    +void ap_update_queueing () {
    + if (ap_is_currently_away ()) {
    + if (purple_prefs_get_bool(
    + "/plugins/gtk/autoprofile/queue_messages_when_away")) {
    + purple_prefs_set_string (PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "away");
    + } else {
    + purple_prefs_set_string (PIDGIN_PREFS_ROOT "/conversations/im/hide_new", "never");
    + }
    + }
    +}
    +
    +/* Called whenever current status is changed by Purple's status menu
    + (in buddy list) */
    +static void ap_status_changed (
    + const char *name, PurplePrefType type, gconstpointer val, gpointer data) {
    + ap_debug ("general", "Status change detected");
    +
    + using_idleaway = FALSE;
    + ap_autoaway_touch ();
    + ap_update (AP_UPDATE_STATUS);
    +}
    +
    +void ap_autoaway_enable () {
    + ap_debug ("idle", "Using idleaway");
    +
    + using_idleaway = TRUE;
    + ap_update (AP_UPDATE_STATUS);
    +}
    +
    +void ap_autoaway_disable () {
    + ap_debug ("idle", "Disabling idleaway");
    +
    + using_idleaway = FALSE;
    + ap_update (AP_UPDATE_STATUS);
    +}
    +
    +gboolean ap_autoaway_in_use () {
    + return using_idleaway;
    +}
    +
    +static gboolean ap_update_queue (gpointer data)
    +{
    + PurpleAccount *account = NULL;
    + PurpleConnection *gc = NULL;
    +
    + g_static_mutex_lock (&update_queue_mutex);
    +
    + if (queued_profiles != NULL) {
    + account = (PurpleAccount *) queued_profiles->data;
    + queued_profiles = queued_profiles->next;
    + }
    +
    + g_static_mutex_unlock (&update_queue_mutex);
    +
    + gc = purple_account_get_connection (account);
    + if (gc != NULL) {
    + serv_set_info (gc, purple_account_get_user_info (account));
    + }
    +
    + return TRUE;
    +}
    +
    +static void ap_update_queue_start ()
    +{
    + update_queue_timeout = purple_timeout_add (2000, ap_update_queue, NULL);
    +}
    +
    +static void ap_update_queue_finish ()
    +{
    + purple_timeout_remove (update_queue_timeout);
    + update_queue_timeout = 0;
    +}
    +/*--------------------------------------------------------------------------*
    + * Preferences *
    + *--------------------------------------------------------------------------*/
    +static void ap_delete_legacy_prefs () {
    + if (purple_prefs_exists ("/plugins/gtk/autoprofile/tab_number")) {
    + ap_debug ("general", "Deleting legacy preferences");
    +
    + purple_prefs_remove ("/plugins/gtk/autoprofile/components");
    +
    + purple_prefs_remove ("/plugins/gtk/autoprofile/tab_number");
    +
    + purple_prefs_remove ("/plugins/gtk/autoprofile/accounts/enable_away");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/accounts/enable_profile");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/accounts");
    +
    + purple_prefs_remove ("/plugins/gtk/autoprofile/message_titles");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/message_texts");
    +
    + purple_prefs_remove ("/plugins/gtk/autoprofile/default_profile");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/default_away");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/current_away");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/added_text");
    +
    + purple_prefs_remove ("/plugins/gtk/autoprofile/delay_profile");
    + purple_prefs_remove ("/plugins/gtk/autoprofile/delay_away");
    +
    + purple_prefs_rename ("/plugins/gtk/autoprofile/text_respond",
    + "/plugins/gtk/autoprofile/autorespond/text");
    + purple_prefs_rename ("/plugins/gtk/autoprofile/text_trigger",
    + "/plugins/gtk/autoprofile/autorespond/trigger");
    + purple_prefs_rename ("/plugins/gtk/autoprofile/delay_respond",
    + "/plugins/gtk/autoprofile/autorespond/delay");
    + purple_prefs_rename ("/plugins/gtk/autoprofile/use_trigger",
    + "/plugins/gtk/autoprofile/autorespond/enable");
    + }
    +}
    +
    +static void ap_init_preferences () {
    + ap_debug ("general", "Initializing preference defaults if necessary");
    +
    + /* Adding the folders */
    + purple_prefs_add_none ("/plugins/gtk");
    + purple_prefs_add_none ("/plugins/gtk/autoprofile");
    + purple_prefs_add_none ("/plugins/gtk/autoprofile/widgets");
    + purple_prefs_add_none ("/plugins/gtk/autoprofile/autorespond");
    +
    + /* Behavior-settings */
    + purple_prefs_add_int ("/plugins/gtk/autoprofile/delay_update", 30);
    + purple_prefs_add_string ("/plugins/gtk/autoprofile/show_summary", "always");
    + purple_prefs_add_bool ("/plugins/gtk/autoprofile/queue_messages_when_away",
    + FALSE);
    + purple_prefs_add_bool ("/plugins/gtk/autoprofile/away_when_idle",
    + purple_prefs_get_bool ("/core/away/away_when_idle"));
    +
    + /* Auto-response settings */
    + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/auto_reply",
    + purple_prefs_get_string ("/core/away/auto_reply"));
    + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/text",
    + _("Say the magic word if you want me to talk more!"));
    + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/trigger",
    + _("please"));
    + purple_prefs_add_int ("/plugins/gtk/autoprofile/autorespond/delay", 2);
    + purple_prefs_add_bool ("/plugins/gtk/autoprofile/autorespond/enable", TRUE);
    +
    + /* Profile settings */
    + purple_prefs_add_string_list(
    + "/plugins/gtk/autoprofile/profile_accounts", NULL);
    + purple_prefs_add_string ("/plugins/gtk/autoprofile/profile",
    + _("Get AutoProfile for Purple at <a href=\""
    + "http://autoprofile.sourceforge.net/\">"
    + "autoprofile.sourceforge.net</a><br><br>[Timestamp]"));
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Last Call *
    + *--------------------------------------------------------------------------*/
    +static void init_plugin (PurplePlugin *plugin)
    +{
    + ap_debug ("general", "Initializing AutoProfile");
    +
    + ap_init_preferences ();
    + ap_widget_init ();
    +}
    +
    +PURPLE_INIT_PLUGIN (autoprofile, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/autoprofile.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,110 @@
    +/*--------------------------------------------------------------------------*
    + * 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 *
    + *--------------------------------------------------------------------------*/
    +
    +#ifndef AUTOPROFILE_H
    +#define AUTOPROFILE_H
    +
    +#include "../common/pp_internal.h"
    +
    +#include "sizes.h"
    +#include "widget.h"
    +#include "utility.h"
    +
    +#include "plugin.h"
    +#include "gtkplugin.h"
    +
    +#include "signals.h"
    +#include "prefs.h"
    +#include "util.h"
    +#include "notify.h"
    +
    +#include "string.h"
    +#include "time.h"
    +
    +#define AP_SCHEDULE_UPDATE_DELAY 3000
    +#define AP_GTK_MAX_MESSAGES 50
    +
    +/* Data types */
    +typedef enum
    +{
    + AP_MESSAGE_TYPE_OTHER = -1,
    + AP_MESSAGE_TYPE_PROFILE,
    + AP_MESSAGE_TYPE_AWAY,
    + AP_MESSAGE_TYPE_AVAILABLE,
    + AP_MESSAGE_TYPE_STATUS
    +} APMessageType;
    +
    +typedef enum
    +{
    + AP_UPDATE_UNKNOWN = 0,
    + AP_UPDATE_STATUS,
    + AP_UPDATE_PROFILE
    +} APUpdateType;
    +
    +/* Variable access functions */
    +PurplePlugin *ap_get_plugin_handle ();
    +gboolean ap_is_currently_away ();
    +
    +void ap_account_enable_profile (const PurpleAccount *, gboolean);
    +gboolean ap_account_has_profile_enabled (const PurpleAccount *);
    +
    +/* Core behavior functions */
    +gchar *ap_generate (const char *, gint);
    +gchar *ap_get_sample_status_message (PurpleAccount *account);
    +void ap_update (APUpdateType);
    +void ap_update_after_delay (APUpdateType);
    +void ap_update_stop (APUpdateType);
    +
    +/* Queueing functions */
    +void ap_update_queueing ();
    +
    +/* Auto-away functions */
    +void ap_autoaway_start ();
    +void ap_autoaway_finish ();
    +void ap_autoaway_touch ();
    +void ap_autoaway_enable ();
    +void ap_autoaway_disable ();
    +gboolean ap_autoaway_in_use ();
    +
    +/* Auto-reply functions */
    +void ap_autoreply_start ();
    +void ap_autoreply_finish ();
    +
    +/* Gtk Away Messages */
    +void ap_gtk_start ();
    +void ap_gtk_finish ();
    +void ap_gtk_make_visible ();
    +void ap_gtk_add_message (APUpdateType, APMessageType, const gchar *);
    +void ap_gtk_set_progress_visible (APUpdateType, gboolean);
    +
    +/* Gtk Actions */
    +GList *actions (PurplePlugin *, gpointer);
    +void ap_actions_finish ();
    +
    +/* Preferences */
    +PidginPluginUiInfo ui_info;
    +void ap_preferences_display ();
    +void ap_gtk_prefs_add_summary_option (GtkWidget *);
    +GtkWidget *get_account_page ();
    +
    +#endif /* #ifndef AUTOPROFILE_H */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/autoreply.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,324 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +#include "conversation.h"
    +
    +#define SECS_BEFORE_RESENDING_AUTORESPONSE 600
    +#define SEX_BEFORE_RESENDING_AUTORESPONSE "Only after you're married"
    +#define MILLISECS_BEFORE_PROCESSING_MSG 100
    +
    +static guint pref_cb;
    +
    +static GSList *last_auto_responses = NULL;
    +struct last_auto_response {
    + PurpleConnection *gc;
    + char name[80];
    + time_t sent;
    +};
    +
    +static time_t response_timeout = 0;
    +
    +/*--------------------------------------------------------------------------*
    + * Auto-response utility functions *
    + *--------------------------------------------------------------------------*/
    +static gboolean
    +expire_last_auto_responses(gpointer data)
    +{
    + GSList *tmp, *cur;
    + struct last_auto_response *lar;
    +
    + tmp = last_auto_responses;
    +
    + while (tmp) {
    + cur = tmp;
    + tmp = tmp->next;
    + lar = (struct last_auto_response *)cur->data;
    +
    + if ((time(NULL) - lar->sent) > SECS_BEFORE_RESENDING_AUTORESPONSE) {
    + last_auto_responses = g_slist_remove(last_auto_responses, lar);
    + g_free(lar);
    + }
    + }
    +
    + return FALSE; /* do not run again */
    +}
    +
    +static struct last_auto_response *
    +get_last_auto_response(PurpleConnection *gc, const char *name)
    +{
    + GSList *tmp;
    + struct last_auto_response *lar;
    +
    + /* because we're modifying or creating a lar, schedule the
    + * function to expire them as the pref dictates */
    + purple_timeout_add((SECS_BEFORE_RESENDING_AUTORESPONSE + 5) * 1000,
    + expire_last_auto_responses, NULL);
    +
    + tmp = last_auto_responses;
    +
    + while (tmp) {
    + lar = (struct last_auto_response *)tmp->data;
    +
    + if (gc == lar->gc && !strncmp(name, lar->name, sizeof(lar->name)))
    + return lar;
    +
    + tmp = tmp->next;
    + }
    +
    + lar = (struct last_auto_response *)g_new0(struct last_auto_response, 1);
    + g_snprintf(lar->name, sizeof(lar->name), "%s", name);
    + lar->gc = gc;
    + lar->sent = 0;
    + last_auto_responses = g_slist_append(last_auto_responses, lar);
    +
    + return lar;
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Message send/receive general functionality *
    + *--------------------------------------------------------------------------*/
    +/* Detecting sent message stuff */
    +static void sent_im_msg_cb (PurpleAccount *account, const char *receiver,
    + const char *message)
    +{
    + PurpleConnection *gc;
    + PurplePresence *presence;
    + const gchar *auto_reply_pref;
    +
    + gc = purple_account_get_connection (account);
    + presence = purple_account_get_presence (account);
    +
    + /*
    + * FIXME - If "only auto-reply when away & idle" is set, then shouldn't
    + * this only reset lar->sent if we're away AND idle?
    + */
    + auto_reply_pref =
    + purple_prefs_get_string ("/plugins/gtk/autoprofile/autorespond/auto_reply");
    + if ((gc->flags & PURPLE_CONNECTION_AUTO_RESP) &&
    + !purple_presence_is_available(presence) &&
    + strcmp(auto_reply_pref, "never"))
    + {
    + struct last_auto_response *lar;
    + lar = get_last_auto_response(gc, receiver);
    + lar->sent = time(NULL);
    + }
    +}
    +
    +/* Detecting received message stuff */
    +struct received_im_msg {
    + PurpleAccount *account;
    + char *sender;
    + char *message;
    +};
    +
    +static gint process_received_im_msg (gpointer data)
    +{
    + struct received_im_msg *received_im;
    + PurpleAccount *account;
    + char *sender;
    + char *message;
    + PurpleConnection *gc;
    + PurpleConversation *conv;
    +
    + received_im = (struct received_im_msg *) data;
    + account = received_im->account;
    + sender = received_im->sender;
    + message = received_im->message;
    + free (data);
    +
    + gc = purple_account_get_connection (account);
    +
    + /* search for conversation again in case it was created by other handlers */
    + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
    + sender, gc->account);
    + if (conv == NULL)
    + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, sender);
    +
    + /*
    + * Don't autorespond if:
    + *
    + * - it's not supported on this connection
    + * - we are available
    + * - or it's disabled
    + * - or we're not idle and the 'only auto respond if idle' pref
    + * is set
    + */
    + if (gc->flags & PURPLE_CONNECTION_AUTO_RESP)
    + {
    + PurplePresence *presence;
    + PurpleStatus *status;
    + PurpleStatusType *status_type;
    + PurpleStatusPrimitive primitive;
    + const gchar *auto_reply_pref;
    + char *away_msg = NULL;
    +
    + auto_reply_pref = purple_prefs_get_string(
    + "/plugins/gtk/autoprofile/autorespond/auto_reply");
    +
    + presence = purple_account_get_presence(account);
    + status = purple_presence_get_active_status(presence);
    + status_type = purple_status_get_type(status);
    + primitive = purple_status_type_get_primitive(status_type);
    + if ((primitive == PURPLE_STATUS_AVAILABLE) ||
    + (primitive == PURPLE_STATUS_INVISIBLE) ||
    + (primitive == PURPLE_STATUS_MOBILE) ||
    + !strcmp(auto_reply_pref, "never") ||
    + (!purple_presence_is_idle(presence) &&
    + !strcmp(auto_reply_pref, "awayidle")))
    + {
    + free (sender);
    + free (message);
    + return FALSE;
    + }
    +
    + away_msg = ap_get_sample_status_message (account);
    +
    + if ((away_msg != NULL) && (*away_msg != '\0')) {
    + struct last_auto_response *lar;
    + gboolean autorespond_enable;
    + time_t now = time(NULL);
    +
    + autorespond_enable = purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/autorespond/enable");
    + /*
    + * This used to be based on the conversation window. But um, if
    + * you went away, and someone sent you a message and got your
    + * auto-response, and then you closed the window, and then they
    + * sent you another one, they'd get the auto-response back too
    + * soon. Besides that, we need to keep track of this even if we've
    + * got a queue. So the rest of this block is just the auto-response,
    + * if necessary.
    + */
    + lar = get_last_auto_response(gc, sender);
    + if ((now - lar->sent) >= SECS_BEFORE_RESENDING_AUTORESPONSE) {
    + lar->sent = now;
    + // Send basic autoresponse
    + serv_send_im (gc, sender, away_msg, PURPLE_MESSAGE_AUTO_RESP);
    + purple_conv_im_write (PURPLE_CONV_IM(conv), NULL, away_msg,
    + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP,
    + now);
    +
    + // Send additional hint if enabled
    + if (autorespond_enable) {
    + const gchar *query = purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/autorespond/text");
    + serv_send_im (gc, sender, query, PURPLE_MESSAGE_AUTO_RESP);
    + purple_conv_im_write (PURPLE_CONV_IM (conv), NULL, query,
    + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP,
    + now);
    + }
    +
    + } else if (autorespond_enable &&
    + difftime (time(NULL), response_timeout) >
    + purple_prefs_get_int ("/plugins/gtk/autoprofile/autorespond/delay")) {
    + gchar *text = purple_markup_strip_html (message);
    + if (match_start (text, purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/autorespond/trigger")) == 1) {
    + serv_send_im (gc, sender, away_msg, PURPLE_MESSAGE_AUTO_RESP);
    + purple_conv_im_write (PURPLE_CONV_IM (conv), NULL, away_msg,
    + PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_AUTO_RESP,
    + now);
    +
    + response_timeout = time (NULL);
    + ap_debug ("autorespond", "string matched, responding");
    + }
    + free (text);
    + }
    + }
    +
    + free (away_msg);
    + }
    +
    + free (sender);
    + free (message);
    +
    + return FALSE;
    +}
    +
    +static void received_im_msg_cb (PurpleAccount *account, char *sender,
    + char *message, PurpleConversation *conv, PurpleMessageFlags flags)
    +{
    + struct received_im_msg *received_im;
    +
    + received_im =
    + (struct received_im_msg *) malloc (sizeof (struct received_im_msg));
    + received_im->account = account;
    + received_im->sender = strdup (sender);
    + received_im->message = strdup (message);
    +
    + purple_timeout_add (MILLISECS_BEFORE_PROCESSING_MSG, process_received_im_msg,
    + received_im);
    +}
    +
    +static void auto_pref_cb (
    + const char *name, PurplePrefType type, gconstpointer val, gpointer data)
    +{
    + if (!strcmp (purple_prefs_get_string ("/core/away/auto_reply"), "never"))
    + return;
    +
    + purple_notify_error (NULL, NULL,
    + N_("This preference is disabled"),
    + N_("This preference currently has no effect because AutoProfile is in "
    + "use. To modify this behavior, use the AutoProfile configuration "
    + "menu."));
    +
    + purple_prefs_set_string ("/core/away/auto_reply", "never");
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Global functions *
    + *--------------------------------------------------------------------------*/
    +void ap_autoreply_start ()
    +{
    + purple_prefs_set_string ("/core/away/auto_reply", "never");
    +
    + purple_signal_connect (purple_conversations_get_handle (), "sent-im-msg",
    + ap_get_plugin_handle (), PURPLE_CALLBACK(sent_im_msg_cb), NULL);
    + purple_signal_connect (purple_conversations_get_handle (), "received-im-msg",
    + ap_get_plugin_handle (), PURPLE_CALLBACK(received_im_msg_cb), NULL);
    +
    + pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (),
    + "/core/away/auto_reply", auto_pref_cb, NULL);
    +}
    +
    +void ap_autoreply_finish ()
    +{
    + GSList *tmp;
    +
    + // Assumes signals are disconnected globally
    +
    + purple_prefs_disconnect_callback (pref_cb);
    + pref_cb = 0;
    +
    + purple_prefs_set_string ("/core/away/auto_reply", purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/autorespond/auto_reply"));
    +
    + while (last_auto_responses) {
    + tmp = last_auto_responses->next;
    + g_free (last_auto_responses->data);
    + g_slist_free_1 (last_auto_responses);
    + last_auto_responses = tmp;
    + }
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_countdownup.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,438 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "component.h"
    +#include "gtkprefs.h"
    +#include "utility.h"
    +
    +#include <math.h>
    +
    +static GtkWidget *spin_secs;
    +static GtkWidget *spin_mins;
    +static GtkWidget *spin_hour;
    +static GtkWidget *spin_day;
    +static GtkWidget *spin_month;
    +static GtkWidget *spin_year;
    +
    +/* Generate the time! */
    +char *count_generate (struct widget *w)
    +{
    + double d_secs, d_mins, d_hours, d_days;
    + char *s_secs, *s_mins, *s_hours, *s_days;
    + double difference;
    + int l, s;
    +
    + struct tm *ref_time;
    + char *result;
    +
    + ref_time = (struct tm *) malloc (sizeof (struct tm));
    +
    + ref_time->tm_sec = ap_prefs_get_int (w, "secs");
    + ref_time->tm_min = ap_prefs_get_int (w, "mins");
    + ref_time->tm_hour = ap_prefs_get_int (w, "hour");
    + ref_time->tm_mday = ap_prefs_get_int (w, "day");
    + ref_time->tm_mon = ap_prefs_get_int (w, "month") - 1;
    + ref_time->tm_year = ap_prefs_get_int (w, "year") - 1900;
    + ref_time->tm_isdst = -1;
    +
    + mktime (ref_time);
    +
    + if (ap_prefs_get_int (w, "down") == 1)
    + difference = difftime (mktime (ref_time), time(NULL));
    + else
    + difference = difftime (time(NULL), mktime (ref_time));
    +
    + if (difference < 0) {
    + d_secs = 0;
    + d_mins = 0;
    + d_hours = 0;
    + d_days = 0;
    + } else {
    + d_mins = floor (difference / 60);
    + d_secs = difference - (d_mins * 60);
    + d_hours = floor (d_mins / 60);
    + d_mins = d_mins - (d_hours * 60);
    + d_days = floor (d_hours / 24);
    + d_hours = d_hours - (d_days * 24);
    + }
    +
    + result = (char *)malloc(sizeof (char) * AP_SIZE_MAXIMUM);
    + l = ap_prefs_get_int (w, "large");
    + s = ap_prefs_get_int (w, "small");
    +
    + if (l < s) {
    + g_snprintf(result, AP_SIZE_MAXIMUM,
    + "%.0f days, %.0f hours, %.0f minutes, %.0f seconds",
    + d_days, d_hours, d_mins, d_secs);
    + free (ref_time);
    + return result;
    + }
    +
    + if (l < 3)
    + d_hours = d_hours + (d_days * 24);
    + if (l < 2)
    + d_mins = d_mins + (d_hours * 60);
    + if (l < 1)
    + d_secs = d_secs + (d_mins * 60);
    +
    + if (d_days == 1.0)
    + s_days = g_strdup ("day");
    + else
    + s_days = g_strdup ("days");
    +
    + if (d_hours == 1.0)
    + s_hours = g_strdup ("hour");
    + else
    + s_hours = g_strdup ("hours");
    +
    + if (d_mins == 1.0)
    + s_mins = g_strdup ("minute");
    + else
    + s_mins = g_strdup ("minutes");
    +
    + if (d_secs == 1.0)
    + s_secs = g_strdup ("second");
    + else
    + s_secs = g_strdup ("seconds");
    +
    + switch (l) {
    + case 3:
    + switch (s) {
    + case 3:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
    + d_days, s_days);
    + break;
    + case 2:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s",
    + d_days, s_days, d_hours, s_hours);
    + break;
    + case 1:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s, %.0f %s",
    + d_days, s_days, d_hours, s_hours, d_mins, s_mins);
    + break;
    + case 0:
    + g_snprintf (result, AP_SIZE_MAXIMUM,
    + "%.0f %s, %.0f %s, %.0f %s, %.0f %s",
    + d_days, s_days, d_hours, s_hours, d_mins, s_mins, d_secs, s_secs);
    + break;
    + default:
    + *result = '\0';
    + }
    + break;
    + case 2:
    + switch (s) {
    + case 2:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
    + d_hours, s_hours);
    + break;
    + case 1:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s",
    + d_hours, s_hours, d_mins, s_mins);
    + break;
    + case 0:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s, %.0f %s",
    + d_hours, s_hours, d_mins, s_mins, d_secs, s_secs);
    + break;
    + default:
    + *result = '\0';
    + }
    + break;
    + case 1:
    + switch (s) {
    + case 1:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
    + d_mins, s_mins);
    + break;
    + case 0:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s, %.0f %s",
    + d_mins, s_mins, d_secs, s_secs);
    + break;
    + default:
    + *result = '\0';
    + }
    + break;
    + case 0:
    + g_snprintf (result, AP_SIZE_MAXIMUM, "%.0f %s",
    + d_secs, s_secs);
    + break;
    + default:
    + *result = '\0';
    + }
    +
    + free (s_days);
    + free (s_hours);
    + free (s_mins);
    + free (s_secs);
    + free (ref_time);
    +
    + return result;
    +}
    +
    +static void update_year (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "year", value);
    +}
    +
    +static void update_month (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "month", value);
    +}
    +
    +static void update_day (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "day", value);
    +}
    +
    +static void update_hour (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "hour", value);
    +}
    +
    +static void update_mins (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "mins", value);
    +}
    +
    +static void update_secs (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "secs", value);
    +}
    +
    +static void set_to_current_time (GtkButton *button, struct widget *w)
    +{
    + time_t the_time;
    + struct tm *ref_time;
    +
    + the_time = time(NULL);
    + ref_time = ap_localtime(&the_time);
    + ap_prefs_set_int (w, "year", ref_time->tm_year + 1900);
    + ap_prefs_set_int (w, "month", ref_time->tm_mon + 1);
    + ap_prefs_set_int (w, "day", ref_time->tm_mday);
    + ap_prefs_set_int (w, "hour", ref_time->tm_hour);
    + ap_prefs_set_int (w, "mins", ref_time->tm_min);
    + ap_prefs_set_int (w, "secs", ref_time->tm_sec);
    + free (ref_time);
    +
    + if (spin_secs != NULL) {
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_secs),
    + ap_prefs_get_int (w, "secs"));
    + }
    + if (spin_mins != NULL) {
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_mins),
    + ap_prefs_get_int (w, "mins"));
    + }
    + if (spin_hour != NULL) {
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_hour),
    + ap_prefs_get_int (w, "hour"));
    + }
    + if (spin_day != NULL) {
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_day),
    + ap_prefs_get_int (w, "day"));
    + }
    + if (spin_month != NULL) {
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_month),
    + ap_prefs_get_int (w, "month"));
    + }
    + if (spin_year != NULL) {
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin_year),
    + ap_prefs_get_int (w, "year"));
    + }
    +}
    +
    +GtkWidget *count_menu (struct widget *w)
    +{
    + GtkWidget *vbox, *hbox, *big_hbox, *frame;
    + GtkWidget *label, *spinner, *dropbox, *button;
    + GList *options;
    +
    + big_hbox = gtk_hbox_new (FALSE, 6);
    +
    + frame = pidgin_make_frame (big_hbox, _("Start/end time"));
    + vbox = gtk_vbox_new (FALSE, 6);
    + gtk_container_add (GTK_CONTAINER (frame), vbox);
    +
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Year: "));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
    + spinner = gtk_spin_button_new_with_range (1970, 2035, 1);
    + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "year"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_year), w);
    + spin_year = spinner;
    +
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Month: "));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
    + spinner = gtk_spin_button_new_with_range (1, 12, 1);
    + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "month"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_month), w);
    + spin_month = spinner;
    +
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Day: "));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
    + spinner = gtk_spin_button_new_with_range (1, 31, 1);
    + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "day"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_day), w);
    + spin_day = spinner;
    +
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Hour: "));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
    + spinner = gtk_spin_button_new_with_range (0, 23, 1);
    + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "hour"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_hour), w);
    + spin_hour = spinner;
    +
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Minutes: "));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
    + spinner = gtk_spin_button_new_with_range (0, 59, 1);
    + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "mins"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_mins), w);
    + spin_mins = spinner;
    +
    + hbox = gtk_hbox_new (FALSE, 12);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Seconds: "));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
    + spinner = gtk_spin_button_new_with_range (0, 59, 1);
    + gtk_box_pack_end (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "secs"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_secs), w);
    + spin_secs = spinner;
    +
    + hbox = gtk_hbox_new (FALSE, 12);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    + button = gtk_button_new_with_label ("Set to current time");
    +
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (set_to_current_time), w);
    + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    +
    + frame = pidgin_make_frame (big_hbox, _("Which way"));
    + vbox = gtk_vbox_new (FALSE, 6);
    + gtk_container_add (GTK_CONTAINER (frame), vbox);
    +
    + options = g_list_append (NULL, (char *) _("Count down to stop date"));
    + options = g_list_append (options, GINT_TO_POINTER(1));
    + options = g_list_append (options, (char *)
    + _("Count time since start date"));
    + options = g_list_append (options, GINT_TO_POINTER(0));
    +
    + dropbox = ap_prefs_dropdown_from_list (w, vbox, NULL,
    + PURPLE_PREF_INT, "down", options);
    + g_list_free (options);
    +
    + options = g_list_append (NULL, (char *) _("Days"));
    + options = g_list_append (options, GINT_TO_POINTER(3));
    + options = g_list_append (options, (char *) _("Hours"));
    + options = g_list_append (options, GINT_TO_POINTER(2));
    + options = g_list_append (options, (char *) _("Minutes"));
    + options = g_list_append (options, GINT_TO_POINTER(1));
    + options = g_list_append (options, (char *) _("Seconds"));
    + options = g_list_append (options, GINT_TO_POINTER(0));
    +
    + dropbox = ap_prefs_dropdown_from_list (w, vbox,
    + _("Largest units displayed"), PURPLE_PREF_INT, "large", options);
    + dropbox = ap_prefs_dropdown_from_list (w, vbox,
    + _("Smallest units displayed"), PURPLE_PREF_INT, "small", options);
    + g_list_free (options);
    +
    + return big_hbox;
    +}
    +
    +/* Init prefs */
    +void count_init (struct widget *w) {
    + time_t the_time;
    + struct tm *ref_time;
    +
    + the_time = time(NULL);
    + ref_time = ap_localtime(&the_time);
    +
    + ap_prefs_add_int (w, "down", 1);
    + ap_prefs_add_int (w, "small", 0);
    + ap_prefs_add_int (w, "large", 3);
    + ap_prefs_add_int (w, "year",
    + ref_time->tm_year + 1900);
    + ap_prefs_add_int (w, "month",
    + ref_time->tm_mon + 1);
    + ap_prefs_add_int (w, "day",
    + ref_time->tm_mday);
    + ap_prefs_add_int (w, "hour",
    + ref_time->tm_hour);
    + ap_prefs_add_int (w, "mins",
    + ref_time->tm_min);
    + ap_prefs_add_int (w, "secs",
    + ref_time->tm_sec);
    + free (ref_time);
    +}
    +
    +struct component count =
    +{
    + N_("Countdown timer"),
    + N_("Given a date, shows amount of time until it (or since it)"),
    + "Timer",
    + &count_generate,
    + &count_init,
    + NULL,
    + NULL,
    + NULL,
    + &count_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_executable.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,169 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "component.h"
    +#include "utility.h"
    +
    +#include <string.h>
    +
    +/*---------- EXECUTABLE: STDOUT from a program ----------*/
    +static GtkWidget *file_selector;
    +static GtkWidget *file_entry;
    +
    +/* Read file into string and return */
    +char *executable_generate (struct widget *w)
    +{
    + char *text, *text_start;
    + int max;
    + gboolean exec;
    + GError *return_error;
    +
    + max = ap_prefs_get_int (w, "max_size");
    + exec = g_spawn_command_line_sync (ap_prefs_get_string (w, "command"),
    + &text_start, NULL, NULL, &return_error);
    +
    + if (!exec) {
    + /* Excution failed */
    + ap_debug ("executable", "command failed to execute");
    + return g_strdup (_("[ERROR: command failed to execute]"));
    + }
    +
    + if (strlen (text_start) < max)
    + text = text_start + strlen(text_start);
    + else
    + text = text_start + max;
    +
    + /* Should back off only if the last item is newline */
    + /* Gets rid of the extra <BR> in output */
    + text--;
    + if (*text != '\n')
    + text++;
    +
    + *text = '\0';
    + return text_start;
    +}
    +
    +void executable_filename (GtkWidget *widget, gpointer user_data) {
    + const gchar *selected_filename;
    +
    + selected_filename = gtk_file_selection_get_filename (
    + GTK_FILE_SELECTION (file_selector));
    +
    + ap_prefs_set_string ((struct widget *) user_data, "command",
    + selected_filename);
    + gtk_entry_set_text (GTK_ENTRY (file_entry), selected_filename);
    +}
    +
    +/* Creates and pops up file selection dialog for fortune file */
    +void executable_selection (GtkWidget *widget, struct widget *w) {
    + const char *cur_file;
    +
    + /* Create the selector */
    + file_selector = gtk_file_selection_new (
    + "Select the location of the program");
    +
    + cur_file = ap_prefs_get_string (w, "command");
    + if (strlen (cur_file) > 1) {
    + gtk_file_selection_set_filename (
    + GTK_FILE_SELECTION (file_selector), cur_file);
    + }
    +
    + g_signal_connect (GTK_OBJECT(
    + GTK_FILE_SELECTION(file_selector)->ok_button),
    + "clicked", G_CALLBACK (executable_filename), w);
    +
    + /* Destroy dialog box when the user clicks button. */
    + g_signal_connect_swapped (GTK_OBJECT(
    + GTK_FILE_SELECTION(file_selector)->ok_button),
    + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector);
    +
    + g_signal_connect_swapped (GTK_OBJECT (
    + GTK_FILE_SELECTION (file_selector)->cancel_button),
    + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector);
    +
    + /* Display dialog */
    + gtk_widget_show (file_selector);
    +}
    +
    +static gboolean executable_update (GtkWidget *widget, GdkEventFocus *evt,
    + gpointer data)
    +{
    + ap_prefs_set_string ((struct widget *) data, "command",
    + gtk_entry_get_text (GTK_ENTRY (file_entry)));
    + return FALSE;
    +}
    +
    +/* Create the menu */
    +GtkWidget *executable_menu (struct widget *w)
    +{
    + GtkWidget *ret = gtk_vbox_new (FALSE, 5);
    + GtkWidget *hbox, *label, *button;
    +
    + label = gtk_label_new (
    + _("Specify the command line you wish to execute"));
    + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    + /* Text entry to type in program name */
    + file_entry = gtk_entry_new ();
    + gtk_box_pack_start (GTK_BOX (hbox), file_entry, FALSE, FALSE, 0);
    + gtk_entry_set_text (GTK_ENTRY (file_entry),
    + ap_prefs_get_string (w, "command"));
    + g_signal_connect (G_OBJECT (file_entry), "focus-out-event",
    + G_CALLBACK (executable_update), w);
    + /* Button to bring up file select dialog */
    + button = gtk_button_new_with_label ("Browse for program");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (executable_selection), w);
    +
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    +
    + ap_prefs_labeled_spin_button (w, ret,
    + _("Max characters to read from output: "), "max_size",
    + 1, AP_SIZE_MAXIMUM, NULL);
    +
    + return ret;
    +}
    +
    +void executable_init (struct widget *w) {
    + ap_prefs_add_string (w, "command", "date");
    + ap_prefs_add_int (w, "max_size", 1000);
    +}
    +
    +struct component executable =
    +{
    + N_("Command Line"),
    + N_("Reproduces standard output of running a program on the command line"),
    + "Command",
    + &executable_generate,
    + &executable_init,
    + NULL,
    + NULL,
    + NULL,
    + &executable_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_http.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,204 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "component.h"
    +
    +static GHashTable *refresh_timeouts = NULL;
    +
    +/*---------- HTTP: HTTP requested Data ----------*/
    +static void http_response (PurpleUtilFetchUrlData *reuqest_data, gpointer data, const char *c, gsize len, const gchar *error_message)
    +{
    + struct widget *w;
    + w = (struct widget *) data;
    +
    + // Invalid URL!
    + if (c == NULL) {
    + ap_prefs_set_string (w, "http_data",
    + _("[AutoProfile error: Invalid URL or no internet connection]"));
    + return;
    + }
    +
    + w = (struct widget *) data;
    + ap_prefs_set_string (w, "http_data", c);
    +}
    +
    +static char* http_generate (struct widget *w)
    +{
    + const char *result, *url;
    +
    + url = ap_prefs_get_string (w, "http_url");
    + if (!url || url[0] == '\0') {
    + return g_strdup (_("[AutoProfile error: No URL specified]"));
    + }
    +
    + result = ap_prefs_get_string (w, "http_data");
    + if (result == NULL) return g_strdup ("");
    + return g_strdup (result);
    +}
    +
    +static gboolean http_refresh_update (gpointer user_data)
    +{
    + struct widget *w;
    + char *http_url;
    +
    + w = (struct widget *) user_data;
    + http_url = g_strdup (ap_prefs_get_string (w, "http_url"));
    +
    + if( http_url && (http_url[0] != '\0') ) {
    + purple_util_fetch_url(http_url, TRUE, NULL, FALSE, http_response, w);
    + } else {
    + ap_prefs_set_string (w, "http_data", "");
    + }
    +
    + free (http_url);
    + return TRUE;
    +}
    +
    +static void http_load (struct widget *w)
    +{
    + gpointer http_refresh_timeout;
    +
    + if (refresh_timeouts == NULL) {
    + refresh_timeouts = g_hash_table_new (NULL, NULL);
    + }
    +
    + http_refresh_update (w);
    + http_refresh_timeout = GINT_TO_POINTER (g_timeout_add (
    + ap_prefs_get_int (w, "http_refresh_mins") * 60 * 1000,
    + http_refresh_update, w));
    + g_hash_table_insert (refresh_timeouts, w, http_refresh_timeout);
    +}
    +
    +static void http_unload (struct widget *w)
    +{
    + gpointer http_refresh_timeout;
    +
    + http_refresh_timeout = g_hash_table_lookup (refresh_timeouts, w);
    + g_source_remove (GPOINTER_TO_INT (http_refresh_timeout));
    + g_hash_table_remove (refresh_timeouts, w);
    +}
    +
    +static void http_init (struct widget *w)
    +{
    + ap_prefs_add_string (w, "http_url", "");
    + ap_prefs_add_string (w, "http_data", "");
    + ap_prefs_add_int (w, "http_refresh_mins", 1);
    +}
    +
    +static gboolean http_url_update (GtkWidget *widget, GdkEventFocus *evt,
    + gpointer data)
    +{
    + struct widget *w = (struct widget *) data;
    + ap_prefs_set_string (w, "http_url",
    + gtk_entry_get_text (GTK_ENTRY (widget)));
    +
    + return FALSE;
    +}
    +
    +static gboolean http_refresh_mins_update (GtkWidget *widget, gpointer data)
    +{
    + struct widget *w;
    + gpointer timeout;
    + int minutes;
    +
    + minutes = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
    +
    + w = (struct widget *) data;
    + ap_prefs_set_int (w, "http_refresh_mins", minutes);
    +
    + // Kill the current timer and run a new one
    + timeout = g_hash_table_lookup (refresh_timeouts, w);
    + g_source_remove (GPOINTER_TO_INT(timeout));
    + timeout = GINT_TO_POINTER (g_timeout_add (minutes * 60 * 1000,
    + http_refresh_update, w));
    + g_hash_table_replace (refresh_timeouts, w, timeout);
    +
    + return FALSE;
    +}
    +
    +static void http_data_update (GtkWidget *w, gpointer data) {
    + http_refresh_update (data);
    +}
    +
    +static GtkWidget *http_menu (struct widget *w)
    +{
    + GtkWidget *ret = gtk_vbox_new (FALSE, 5);
    + GtkWidget *label, *hbox, *button, *spinner;
    + GtkWidget *http_url_entry;
    +
    + label = gtk_label_new (_("Select URL with source content"));
    + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + // URL Entry
    + http_url_entry = gtk_entry_new ();
    + gtk_box_pack_start (GTK_BOX (hbox), http_url_entry, TRUE, TRUE, 0);
    + gtk_entry_set_text (GTK_ENTRY (http_url_entry),
    + ap_prefs_get_string (w, "http_url"));
    + g_signal_connect (G_OBJECT (http_url_entry), "focus-out-event",
    + G_CALLBACK (http_url_update), w);
    +
    + // Update Now!
    + button = gtk_button_new_with_label (_("Fetch page now!"));
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (http_data_update), w);
    +
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + label = gtk_label_new (_("Delay"));
    + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
    +
    + spinner = gtk_spin_button_new_with_range (1, 60, 1);
    + gtk_box_pack_start (GTK_BOX (hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "http_refresh_mins"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (http_refresh_mins_update), w);
    +
    + label = gtk_label_new (_("minutes between page fetches"));
    + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
    +
    + return ret;
    +}
    +
    +struct component http =
    +{
    + N_("Webpage"),
    + N_("Data fetched from an internet URL using HTTP"),
    + "Webpage",
    + &http_generate,
    + &http_init,
    + &http_load,
    + &http_unload,
    + NULL,
    + &http_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_logstats.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,1042 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +#include "log.h"
    +#include "account.h"
    +#include "conversation.h"
    +#include "utility.h"
    +#include "util.h"
    +
    +#include "comp_logstats.h"
    +
    +#include <ctype.h>
    +#include <string.h>
    +
    +struct conversation_time {
    + time_t *start_time;
    + char *name;
    +};
    +
    +/* Represents data about a particular 24 hour period in the logs */
    +struct log_date {
    + int year; // The year
    + int month; // The month
    + int day; // The day
    + int received_msgs; // # msgs received
    + int received_words; // # words received
    + int sent_msgs; // # msgs sent
    + int sent_words; // # words sent
    + GSList *conversation_times; // List of conversation_time pointers
    +};
    +
    +/* List of struct log_dates
    + This is SORTED by most recent first */
    +static GSList *dates = NULL;
    +
    +/* Hashtable of log_dates */
    +static GHashTable *dates_table = NULL;
    +
    +/* Is the current line part of a message sent or received? */
    +static gboolean receiving = FALSE;
    +/* Shortcut vars */
    +static char *cur_receiver = NULL;
    +static char *cur_sender = NULL;
    +
    +/* Implements GCompareFunc */
    +static gint conversation_time_compare (gconstpointer x, gconstpointer y) {
    + const struct conversation_time *a = x;
    + const struct conversation_time *b = y;
    +
    + if (difftime (*(a->start_time), *(b->start_time)) == 0.0) {
    + if (!strcmp (a->name, b->name))
    + return 0;
    + }
    +
    + return -1;
    +}
    +
    +/* Implements GCompareFunc */
    +static gint log_date_compare (gconstpointer x, gconstpointer y)
    +{
    + const struct log_date *a = y;
    + const struct log_date *b = x;
    +
    + if (a->year == b->year) {
    + if (a->month == b->month) {
    + if (a->day == b->day)
    + return 0;
    + else
    + return a->day - b->day;
    + } else {
    + return a->month - b->month;
    + }
    + } else {
    + return a->year - b->year;
    + }
    +}
    +
    +/* Implements GHashFunc */
    +static guint log_date_hash (gconstpointer key)
    +{
    + const struct log_date *d = key;
    + return ((d->year * 365) + (d->month * 12) + (d->day));
    +}
    +
    +/* Implements GEqualFunc */
    +static gboolean log_date_equal (gconstpointer x, gconstpointer y)
    +{
    + const struct log_date *a = y;
    + const struct log_date *b = x;
    +
    + if (a->year == b->year &&
    + a->month == b->month &&
    + a->day == b->day) {
    + return TRUE;
    + }
    + return FALSE;
    +}
    +
    +/* Returns the struct log_date associated with a particular date.
    + Will MODIFY list of dates and insert sorted if not yet created */
    +static struct log_date *get_date (int year, int month, int day)
    +{
    + struct log_date *cur_date;
    + gpointer *node;
    +
    + cur_date = (struct log_date *)malloc(sizeof(struct log_date));
    + cur_date->year = year;
    + cur_date->month = month;
    + cur_date->day = day;
    +
    + if ((node = g_hash_table_lookup (dates_table, cur_date))) {
    + free (cur_date);
    + return (struct log_date *)node;
    + } else {
    + g_hash_table_insert (dates_table, cur_date, cur_date);
    + cur_date->received_msgs = 0;
    + cur_date->received_words = 0;
    + cur_date->sent_msgs = 0;
    + cur_date->sent_words = 0;
    + cur_date->conversation_times = NULL;
    + return cur_date;
    + }
    +}
    +
    +/* Like get_date, except specific to the current date */
    +static struct log_date *get_today ()
    +{
    + time_t the_time;
    + struct tm *cur_time;
    +
    + time (&the_time);
    + cur_time = localtime (&the_time);
    +
    + return get_date (cur_time->tm_year, cur_time->tm_mon, cur_time->tm_mday);
    +}
    +
    +static int string_word_count (const char *line)
    +{
    + int count, state;
    +
    + count = 0;
    + state = 0;
    +
    + /* If state is 1, currently processing a word */
    + while (*line) {
    + if (state == 0) {
    + if (!isspace (*line))
    + state = 1;
    + } else {
    + if (isspace (*line)) {
    + state = 0;
    + count++;
    + }
    + }
    + line++;
    + }
    +
    + if (state == 1)
    + count++;
    +
    + return count;
    +}
    +
    +/* Figure out if a person is yourself or someone else */
    +static gboolean is_self (PurpleAccount *a, const char *name) {
    + GList *accounts, *aliases, *aliases_start;
    + PurpleAccount *account;
    +
    + char *normalized;
    + const char *normalized_alias;
    +
    + if (cur_sender && !strcmp (cur_sender, name)) {
    + return TRUE;
    + }
    +
    + if (cur_receiver && !strcmp (cur_receiver, name)) {
    + return FALSE;
    + }
    +
    + normalized = strdup (purple_normalize (a, name));
    + accounts = purple_accounts_get_all ();
    +
    + aliases_start = aliases = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases");
    +
    + while (aliases) {
    + normalized_alias = purple_normalize (a, (char *)aliases->data);
    +
    + if (!strcmp (normalized, normalized_alias)) {
    + free_string_list (aliases_start);
    + free (normalized);
    +
    + if (cur_sender)
    + free (cur_sender);
    + cur_sender = strdup (name);
    +
    + return TRUE;
    + }
    +
    + aliases = aliases->next;
    + }
    +
    + free_string_list (aliases_start);
    +
    + while (accounts) {
    + account = (PurpleAccount *)accounts->data;
    + if (!strcmp (normalized, purple_account_get_username (account))) {
    + free (normalized);
    + if (cur_sender)
    + free (cur_sender);
    + cur_sender = strdup (name);
    + return TRUE;
    + }
    + accounts = accounts->next;
    + }
    +
    + free (normalized);
    +
    + if (cur_receiver)
    + free (cur_receiver);
    + cur_receiver = strdup (name);
    + return FALSE;
    +}
    +
    +/* Parses a line of a conversation */
    +static void parse_line (PurpleLog *cur_log, char *l, struct log_date *d)
    +{
    + char *cur_line, *cur_line_start;
    + char *name;
    + char *message;
    +
    + char *line = l;
    +
    + if (strlen (line) > 14 && *line == ' ')
    + line++;
    +
    + if (strlen (line) > 13 &&
    + *line == '(' &&
    + isdigit (*(line + 1)) &&
    + isdigit (*(line + 2)) &&
    + *(line + 3) == ':' &&
    + isdigit (*(line + 4)) &&
    + isdigit (*(line + 5)) &&
    + *(line + 6) == ':' &&
    + isdigit (*(line + 7)) &&
    + isdigit (*(line + 8)) &&
    + *(line + 9) == ')' &&
    + isspace (*(line + 10))) {
    + cur_line_start = cur_line = line + 11;
    + while (*cur_line) {
    + if (*cur_line == ':') {
    + *cur_line = '\0';
    + name = cur_line_start;
    + message = ++cur_line;
    +
    + receiving = !is_self (cur_log->account, name);
    +
    + if (receiving) {
    + d->received_msgs++;
    + d->received_words += string_word_count (message);
    + } else {
    + d->sent_msgs++;
    + d->sent_words += string_word_count (message);
    + }
    +
    + return;
    + }
    + cur_line++;
    + }
    +
    + }
    +
    + if (receiving) {
    + d->received_words += string_word_count (line);
    + } else {
    + d->sent_words += string_word_count (line);
    + }
    +}
    +
    +/* Parses a conversation if hasn't been handled yet */
    +static void parse_log (PurpleLog *cur_log)
    +{
    + struct log_date *the_date;
    + struct tm *the_time;
    + struct conversation_time *conv_time;
    +
    + PurpleLogReadFlags flags;
    + char *content, *cur_content, *cur_content_start, *temp;
    +
    + the_time = localtime (&(cur_log->time));
    + the_date = get_date (the_time->tm_year, the_time->tm_mon, the_time->tm_mday);
    +
    + /* Check for old log and if no conflicts, add to list */
    + conv_time = (struct conversation_time *)malloc (
    + sizeof (struct conversation_time));
    + conv_time->start_time = (time_t *)malloc (sizeof(time_t));
    + *(conv_time->start_time) = cur_log->time;
    + conv_time->name = strdup (cur_log->name);
    +
    + if (g_slist_find_custom (the_date->conversation_times, conv_time,
    + conversation_time_compare)) {
    + /* We already processed this! Halt! */
    + free (conv_time->start_time);
    + free (conv_time->name);
    + free (conv_time);
    + return;
    + }
    +
    + the_date->conversation_times = g_slist_prepend (the_date->conversation_times,
    + conv_time);
    +
    + /* Start rolling the counters! */
    + temp = purple_log_read (cur_log, &flags);
    + if (!strcmp ("html", cur_log->logger->id)) {
    + content = purple_markup_strip_html (temp);
    + free (temp);
    + } else {
    + content = temp;
    + }
    +
    + cur_content_start = cur_content = content;
    +
    + /* Splits the conversation into lines (each line may not necessarily
    + be a seperate message */
    + while (*cur_content) {
    + if (*cur_content == '\n') {
    + *cur_content = '\0';
    + parse_line (cur_log, cur_content_start, the_date);
    + cur_content_start = cur_content + 1;
    + }
    + cur_content++;
    + }
    +
    + parse_line (cur_log, cur_content_start, the_date);
    +
    + free (content);
    +}
    +
    +/* Get names of users in logs */
    +static GList *logstats_get_names (PurpleLogType type, PurpleAccount *account)
    +{
    + GDir *dir;
    + const char *prpl;
    + GList *ret;
    + const char *filename;
    + char *path, *me, *tmp;
    +
    + ret = NULL;
    +
    + if (type == PURPLE_LOG_CHAT)
    + me = g_strdup_printf ("%s.chat", purple_normalize(account,
    + purple_account_get_username(account)));
    + else
    + me = g_strdup (purple_normalize(account,
    + purple_account_get_username(account)));
    +
    + /* Get the old logger names */
    + path = g_build_filename(purple_user_dir(), "logs", NULL);
    + if (!(dir = g_dir_open(path, 0, NULL))) {
    + g_free(path);
    + return ret;
    + }
    +
    + while ((filename = g_dir_read_name (dir))) {
    + if (purple_str_has_suffix (filename, ".log")) {
    + tmp = strdup (filename);
    + *(tmp + strlen (filename) - 4) = '\0';
    + if (!string_list_find (ret, tmp))
    + ret = g_list_prepend (ret, strdup (tmp));
    + free (tmp);
    + }
    + }
    +
    + g_dir_close (dir);
    + g_free (path);
    +
    + /* Get the account-specific names */
    + prpl = PURPLE_PLUGIN_PROTOCOL_INFO
    + (purple_find_prpl (purple_account_get_protocol_id(account)))->list_icon(
    + account, NULL);
    +
    + path = g_build_filename(purple_user_dir(), "logs", prpl, me, NULL);
    + g_free (me);
    +
    + if (!(dir = g_dir_open(path, 0, NULL))) {
    + g_free(path);
    + return ret;
    + }
    +
    + while ((filename = g_dir_read_name (dir))) {
    + if (!string_list_find (ret, filename))
    + ret = g_list_prepend (ret, strdup (filename));
    + }
    +
    + g_dir_close (dir);
    + g_free (path);
    +
    + return ret;
    +}
    +
    +/* On load, reads in all logs and initializes stats database */
    +static void logstats_read_logs ()
    +{
    + GList *accounts, *logs, *logs_start, *names, *names_start;
    + PurpleLog *cur_log;
    +
    + accounts = purple_accounts_get_all();
    +
    + ap_debug ("logstats", "parsing log files");
    +
    + while (accounts) {
    + names_start = names = logstats_get_names (PURPLE_LOG_IM,
    + (PurpleAccount *)accounts->data);
    +
    + while (names) {
    + logs_start = purple_log_get_logs (PURPLE_LOG_IM, (char *)names->data,
    + (PurpleAccount *)accounts->data);
    + logs = logs_start;
    +
    + while (logs) {
    + cur_log = (PurpleLog *)logs->data;
    + parse_log (cur_log);
    + purple_log_free (cur_log);
    + logs = logs->next;
    + }
    +
    + g_list_free (logs_start);
    + names = names->next;
    + }
    +
    + free_string_list (names_start);
    + accounts = accounts->next;
    + }
    +
    + /* Cleanup */
    +
    + ap_debug ("logstats", "finished parsing log files");
    +}
    +
    +/* Implements GHFunc */
    +static void add_element (gpointer key, gpointer value, gpointer data)
    +{
    + dates = g_slist_insert_sorted (dates, value, log_date_compare);
    +}
    +
    +/* Updates GList against hashtable */
    +static void logstats_update_dates ()
    +{
    + g_slist_free (dates);
    + dates = NULL;
    + g_hash_table_foreach (dates_table, add_element, NULL);
    +}
    +
    +/*--------------------- Total calculations -------------------*/
    +static int get_total (const char *field)
    +{
    + GSList *cur_day;
    + int count;
    + struct log_date *d;
    +
    + cur_day = dates;
    + count = 0;
    + while (cur_day) {
    + d = (struct log_date *)cur_day->data;
    + if (!strcmp (field, "received_msgs")) {
    + count += d->received_msgs;
    + } else if (!strcmp (field, "received_words")) {
    + count += d->received_words;
    + } else if (!strcmp (field, "sent_msgs")) {
    + count += d->sent_msgs;
    + } else if (!strcmp (field, "sent_words")) {
    + count += d->sent_words;
    + } else if (!strcmp (field, "num_convos")) {
    + count += g_slist_length (d->conversation_times);
    + }
    +
    + cur_day = cur_day->next;
    + }
    +
    + return count;
    +}
    +
    +static int get_recent_total (const char *field, int hours)
    +{
    + GSList *cur_day;
    + int count;
    + struct log_date *d;
    + time_t cur_day_time;
    +
    + cur_day = dates;
    + count = 0;
    +
    + while (cur_day) {
    + d = (struct log_date *)cur_day->data;
    + cur_day_time = purple_time_build (d->year + 1900, d->month + 1, d->day,
    + 0, 0, 0);
    + if (difftime (time (NULL), cur_day_time) > (double) hours * 60.0 * 60.0)
    + break;
    +
    + if (!strcmp (field, "received_msgs")) {
    + count += d->received_msgs;
    + } else if (!strcmp (field, "sent_msgs")) {
    + count += d->sent_msgs;
    + } else if (!strcmp (field, "num_convos")) {
    + count += g_slist_length (d->conversation_times);
    + }
    +
    + cur_day = cur_day->next;
    + }
    +
    + return count;
    +}
    +
    +static int num_days_since_start ()
    +{
    + GSList *first_day;
    + double difference;
    + struct log_date *d;
    +
    + first_day = g_slist_last (dates);
    +
    + if (!first_day)
    + return 0;
    +
    + d = (struct log_date *)first_day->data;
    +
    + difference = difftime (
    + time (NULL), purple_time_build (d->year + 1900, d->month + 1, d->day,
    + 0, 0, 0));
    +
    + return (int) difference / (60.0 * 60.0 * 24.0);
    +}
    +
    +static struct log_date *get_max_date (const char *field)
    +{
    + struct log_date *max_date, *cur_date;
    + int max_so_far, cur_max;
    + GSList *cur_day;
    +
    + max_so_far = 0;
    + max_date = NULL;
    + cur_day = dates;
    +
    + while (cur_day) {
    + cur_date = (struct log_date *)cur_day->data;
    + if (!strcmp (field, "conversations")) {
    + cur_max = g_slist_length (cur_date->conversation_times);
    + } else if (!strcmp (field, "received")) {
    + cur_max = cur_date->received_msgs;
    + } else if (!strcmp (field, "sent")) {
    + cur_max = cur_date->sent_msgs;
    + } else if (!strcmp (field, "total")) {
    + cur_max = cur_date->sent_msgs + cur_date->received_msgs;
    + } else {
    + cur_max = 0;
    + }
    +
    + if (cur_max >= max_so_far) {
    + max_date = cur_date;
    + max_so_far = cur_max;
    + }
    +
    + cur_day = cur_day->next;
    + }
    +
    + return max_date;
    +}
    +
    +static char *date_string (const char *field)
    +{
    + struct log_date *d;
    + char *output;
    + struct tm *t_struct;
    + time_t t;
    + GSList *last_day;
    +
    + last_day = g_slist_last (dates);
    +
    + if (!last_day)
    + return NULL;
    +
    + if (!strcmp (field, "first")) {
    + d = (struct log_date *) last_day->data;
    + } else {
    + d = get_max_date (field);
    + }
    +
    + if (!d)
    + return NULL;
    +
    + output = (char *)malloc (sizeof(char) * AP_SIZE_MAXIMUM);
    + t_struct = (struct tm *)malloc(sizeof(struct tm));
    + t_struct->tm_year = d->year;
    + t_struct->tm_mon = d->month;
    + t_struct->tm_mday = d->day;
    + t_struct->tm_sec = 0;
    + t_struct->tm_min = 0;
    + t_struct->tm_hour = 0;
    + t = mktime (t_struct);
    + free (t_struct);
    + t_struct = localtime (&t);
    +
    + strftime (output, AP_SIZE_MAXIMUM - 1, "%a %b %d, %Y", t_struct);
    + return output;
    +}
    +
    +static int get_max (const char *field)
    +{
    + struct log_date *max_date = get_max_date (field);
    +
    + if (!max_date)
    + return 0;
    +
    + if (!strcmp (field, "conversations")) {
    + return g_slist_length (max_date->conversation_times);
    + } else if (!strcmp (field, "received")) {
    + return max_date->received_msgs;
    + } else if (!strcmp (field, "sent")) {
    + return max_date->sent_msgs;
    + } else if (!strcmp (field, "total")) {
    + return max_date->sent_msgs + max_date->received_msgs;
    + } else {
    + ap_debug ("logstats", "get-max: invalid paramater");
    + return 0;
    + }
    +
    +}
    +
    +
    +/*--------------------- Signal handlers ----------------------*/
    +static void logstats_received_im (PurpleAccount *account, char *sender,
    + char *message, int flags)
    +{
    + struct log_date *the_date;
    +
    + the_date = get_today ();
    + the_date->received_msgs++;
    + the_date->received_words += string_word_count (message);
    +
    + receiving = TRUE;
    +}
    +
    +static void logstats_sent_im (PurpleAccount *account, const char *receiver,
    + const char *message)
    +{
    + struct log_date *the_date;
    +
    + the_date = get_today ();
    + the_date->sent_msgs++;
    + the_date->sent_words += string_word_count (message);
    +
    + receiving = FALSE;
    +}
    +
    +static void logstats_conv_created (PurpleConversation *conv)
    +{
    + struct log_date *the_date;
    + struct conversation_time *the_time;
    +
    + if (conv->type == PURPLE_CONV_TYPE_IM) {
    + the_time = malloc (sizeof(struct conversation_time));
    + the_time->name = strdup (conv->name);
    + the_time->start_time = malloc (sizeof(time_t));
    + time (the_time->start_time);
    +
    + the_date = get_today ();
    + the_date->conversation_times = g_slist_prepend (
    + the_date->conversation_times, the_time);
    +
    + logstats_update_dates ();
    + }
    +}
    +
    +/*--------------------------- Main functions -------------------------*/
    +
    +/* Component load */
    +void logstats_load (struct widget *w)
    +{
    + int count;
    + char *msg;
    +
    + if (!purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled")) {
    + return;
    + }
    +
    + /* Initialize database */
    + dates_table = g_hash_table_new (log_date_hash, log_date_equal);
    + logstats_read_logs ();
    + logstats_update_dates ();
    +
    + /* Debug */
    + msg = (char *)malloc (sizeof(char) * AP_SIZE_MAXIMUM);
    + count = get_total ("received_msgs");
    + g_snprintf (msg, AP_SIZE_MAXIMUM, "received msg total is %d", count);
    + ap_debug ("logstats", msg);
    + count = get_total ("sent_msgs");
    + g_snprintf (msg, AP_SIZE_MAXIMUM, "sent msg total is %d", count);
    + ap_debug ("logstats", msg);
    + count = get_total ("received_words");
    + g_snprintf (msg, AP_SIZE_MAXIMUM, "received word total is %d", count);
    + ap_debug ("logstats", msg);
    + count = get_total ("sent_words");
    + g_snprintf (msg, AP_SIZE_MAXIMUM, "sent word total is %d", count);
    + ap_debug ("logstats", msg);
    + count = get_total ("num_convos");
    + g_snprintf (msg, AP_SIZE_MAXIMUM, "num conversations is %d", count);
    + ap_debug ("logstats", msg);
    + count = g_slist_length (dates);
    + g_snprintf (msg, AP_SIZE_MAXIMUM, "num days with conversations is %d", count);
    + ap_debug ("logstats", msg);
    +
    + free(msg);
    +
    + /* Connect signals */
    + purple_signal_connect (purple_conversations_get_handle (),
    + "received-im-msg", ap_get_plugin_handle (),
    + PURPLE_CALLBACK (logstats_received_im), NULL);
    + purple_signal_connect (purple_conversations_get_handle (),
    + "sent-im-msg", ap_get_plugin_handle (),
    + PURPLE_CALLBACK (logstats_sent_im), NULL);
    + purple_signal_connect (purple_conversations_get_handle (),
    + "conversation-created", ap_get_plugin_handle (),
    + PURPLE_CALLBACK (logstats_conv_created), NULL);
    +}
    +
    +/* Component unload */
    +void logstats_unload (struct widget *w)
    +{
    + struct log_date *cur_date;
    + struct conversation_time *cur_time;
    + GSList *temp;
    +
    + if (!purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled")) {
    + return;
    + }
    +
    + /* Disconnect signals */
    + purple_signal_disconnect (purple_conversations_get_handle (),
    + "received-im-msg", ap_get_plugin_handle (),
    + PURPLE_CALLBACK (logstats_received_im));
    + purple_signal_disconnect (purple_conversations_get_handle (),
    + "sent-im-msg", ap_get_plugin_handle (),
    + PURPLE_CALLBACK (logstats_sent_im));
    + purple_signal_disconnect (purple_conversations_get_handle (),
    + "conversation-created", ap_get_plugin_handle (),
    + PURPLE_CALLBACK (logstats_conv_created));
    +
    + logstats_update_dates ();
    +
    + /* Free all the memory */
    + while (dates) {
    + cur_date = (struct log_date *)dates->data;
    + while (cur_date->conversation_times) {
    + temp = cur_date->conversation_times;
    + cur_time = (struct conversation_time *)temp->data;
    + cur_date->conversation_times = temp->next;
    + free (cur_time->start_time);
    + free (cur_time->name);
    + free (cur_time);
    + g_slist_free_1 (temp);
    + }
    + free (cur_date);
    + temp = dates;
    + dates = dates->next;
    + g_slist_free_1 (temp);
    + }
    +
    + if (cur_receiver) {
    + free (cur_receiver);
    + cur_receiver = NULL;
    + }
    + if (cur_sender) {
    + free (cur_sender);
    + cur_sender = NULL;
    + }
    + g_hash_table_destroy (dates_table);
    + dates_table = NULL;
    +}
    +
    +/* Generate the output */
    +static char *logstats_generate (struct widget *w)
    +{
    + char *buf, *output, *date;
    + int state;
    + const char *format;
    +
    + if (!purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled")) {
    + return NULL;
    + }
    +
    + format = purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/components/logstat/format");
    +
    + output = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM);
    + *output = '\0';
    + buf = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM);
    + *buf = '\0';
    +
    + state = 0;
    +
    + while (*format) {
    + if (state == 1) {
    + switch (*format) {
    + case '%':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format);
    + break;
    + case 'R':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("received_msgs"));
    + break;
    + case 'r':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("received_words"));
    + break;
    + case 'S':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("sent_msgs"));
    + break;
    + case 's':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("sent_words"));
    + break;
    + case 'T':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + get_total ("sent_msgs") + get_total ("received_msgs"));
    + break;
    + case 't':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + get_total ("sent_words") + get_total ("received_words"));
    + break;
    + case 'D':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + num_days_since_start ());
    + break;
    + case 'd':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, g_slist_length (dates));
    + break;
    + case 'N':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_total ("num_convos"));
    + break;
    + case 'n':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("num_convos") / (double) g_slist_length (dates));
    + break;
    + case 'i':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("conversations"));
    + break;
    + case 'I':
    + date = date_string ("conversations");
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
    + free (date);
    + break;
    + case 'j':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("sent"));
    + break;
    + case 'J':
    + date = date_string ("sent");
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
    + free (date);
    + break;
    + case 'k':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("received"));
    + break;
    + case 'K':
    + date = date_string ("received");
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
    + free (date);
    + break;
    + case 'l':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_max ("total"));
    + break;
    + case 'L':
    + date = date_string ("total");
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
    + free (date);
    + break;
    + case 'f':
    + date = date_string ("first");
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%s", output, date);
    + free (date);
    + break;
    + case 'u':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("received_words") / (double) get_total ("received_msgs"));
    + break;
    + case 'v':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("sent_words") / (double) get_total ("sent_msgs"));
    + break;
    + case 'w':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) (get_total ("received_words") + get_total ("sent_words")) / (double) (get_total("received_msgs") + get_total ("sent_msgs")));
    + break;
    + case 'U':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("received_msgs") / (double) get_total ("num_convos"));
    + break;
    + case 'V':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("sent_msgs") / (double) get_total ("num_convos"));
    + break;
    + case 'W':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) (get_total ("received_msgs") + get_total ("sent_msgs")) / (double) get_total("num_convos"));
    + break;
    + case 'x':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("received_words") / (double) g_slist_length (dates));
    + break;
    + case 'y':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("sent_words") / (double) g_slist_length (dates));
    + break;
    + case 'z':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + ((double) get_total ("received_words") + (double) get_total ("sent_words")) / (double) g_slist_length (dates));
    + break;
    + case 'X':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("received_msgs") / (double) g_slist_length (dates));
    + break;
    + case 'Y':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) get_total ("sent_msgs") / (double) g_slist_length (dates));
    + break;
    + case 'Z':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.2f", output,
    + (double) (get_total ("received_msgs") + get_total ("sent_msgs")) / (double) g_slist_length (dates));
    + break;
    + case 'p':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%.1f", output,
    + 100.0 * (double) g_slist_length (dates) / (double) num_days_since_start ());
    + break;
    + case 'a':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + ((struct log_date *) dates->data)->received_msgs);
    + break;
    + case 'b':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + ((struct log_date *) dates->data)->sent_msgs);
    + break;
    + case 'c':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + g_slist_length (((struct log_date *) dates->data)->conversation_times));
    + break;
    + case 'e':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + ((struct log_date *) dates->data)->sent_msgs + ((struct log_date *) dates->data)->received_msgs);
    + break;
    + case 'A':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("received_msgs", 24 * 7));
    + break;
    + case 'B':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("sent_msgs", 24 * 7));
    + break;
    + case 'C':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output, get_recent_total ("num_convos", 24 * 7));
    + break;
    + case 'E':
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%d", output,
    + get_recent_total ("received_msgs", 24 * 7) + get_recent_total ("received_msgs", 24 * 7));
    + break;
    + default:
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format);
    + break;
    + }
    +
    + strcpy (output, buf);
    + format++;
    + state = 0;
    + } else {
    + if (*format == '%') {
    + state = 1;
    + } else {
    + g_snprintf (buf, AP_SIZE_MAXIMUM, "%s%c", output, *format);
    + strcpy (output, buf);
    + }
    + format++;
    + }
    +
    + }
    +
    + free (buf);
    + return output;
    +}
    +
    +/* Initialize preferences */
    +static void logstats_init (struct widget *w)
    +{
    + purple_prefs_add_none ("/plugins/gtk/autoprofile/components/logstat");
    + purple_prefs_add_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled", FALSE);
    + purple_prefs_add_string (
    + "/plugins/gtk/autoprofile/components/logstat/format", "");
    + purple_prefs_add_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases", NULL);
    +}
    +
    +/* The heart of the component */
    +static char *identifiers [7] = {
    + N_("logs"),
    + N_("log"),
    + N_("stat"),
    + N_("stats"),
    + N_("logstats"),
    + N_("log statistics"),
    + NULL
    +};
    +
    +struct component logstats =
    +{
    + N_("Purple log statistics"),
    + N_("Display various statistics about your message and system logs"),
    + identifiers,
    + logstats_generate,
    + logstats_init,
    + logstats_load,
    + logstats_unload,
    + NULL,
    + logstats_prefs
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_logstats.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,28 @@
    +/*--------------------------------------------------------------------------*
    + * 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 *
    + *--------------------------------------------------------------------------*/
    +
    +void logstats_load (struct widget *w);
    +void logstats_unload (struct widget *w);
    +GtkWidget *logstats_prefs (struct widget *w);
    +
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_logstats_gtk.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,355 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +#include "comp_logstats.h"
    +#include "request.h"
    +
    +GtkWidget *checkbox = NULL;
    +
    +GtkListStore *alias_list = NULL;
    +GtkWidget *alias_view = NULL;
    +
    +/* General callbacks from main preferences */
    +static void logstats_response_cb (GtkDialog *dialog, gint id,
    + GtkWidget *widget)
    +{
    + purple_prefs_set_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled", TRUE);
    + logstats_load (NULL);
    + gtk_widget_set_sensitive (widget, TRUE);
    +
    + gtk_widget_destroy (GTK_WIDGET(dialog));
    +}
    +
    +static void toggle_enable (GtkButton *button, gpointer data)
    +{
    + GtkWidget *popup, *vbox, *label;
    + vbox = data;
    +
    + if (purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled")) {
    + logstats_unload (NULL);
    + purple_prefs_set_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled", FALSE);
    + gtk_widget_set_sensitive (vbox, FALSE);
    + } else {
    + popup = gtk_dialog_new_with_buttons (
    + "Enable stats for logs", NULL, 0,
    + GTK_STOCK_OK, 42, NULL);
    + g_signal_connect (G_OBJECT(popup), "response",
    + G_CALLBACK(logstats_response_cb), vbox);
    +
    + label = gtk_label_new(NULL);
    + gtk_label_set_markup(GTK_LABEL(label),
    + "\nEnabling this component will have some minor side effects. Doing so "
    + "will cause Purple to take slightly longer to start up because it must "
    + "parse a large amount of data to gather statistics. On average, this "
    + "can take slightly over a second for every 100,000 messages in your "
    + "logs.\n\nThe time from when you press the OK button to the time "
    + "when this dialog vanishes is a good approximation of how much extra "
    + "time will elapse before the login screen is shown.\n"
    + );
    + gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
    + gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
    + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(popup)->vbox), label,
    + FALSE, FALSE, 0);
    +
    + gtk_widget_show_all (popup);
    + }
    +}
    +
    +static gboolean logstat_format (GtkWidget *widget, GdkEventFocus *event,
    + gpointer data)
    +{
    + purple_prefs_set_string (
    + "/plugins/gtk/autoprofile/components/logstat/format",
    + gtk_entry_get_text (GTK_ENTRY (widget)));
    + return FALSE;
    +}
    +
    +static void new_alias (gpointer data, PurpleRequestFields *fields)
    +{
    + GtkTreeIter iter;
    + GList *aliases;
    +
    + const char *alias;
    +
    + alias = purple_request_fields_get_string (fields, "alias");
    + aliases = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases");
    +
    + aliases = g_list_append (aliases, strdup (alias));
    + purple_prefs_set_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases", aliases);
    + free_string_list (aliases);
    +
    + gtk_list_store_insert (alias_list, &iter, 0);
    + gtk_list_store_set (alias_list, &iter,
    + 0, alias, -1);
    +}
    +
    +static void alias_add (GtkButton *button, gpointer data)
    +{
    + PurpleRequestFields *fields;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + fields = purple_request_fields_new();
    +
    + group = purple_request_field_group_new(NULL);
    + purple_request_fields_add_group(fields, group);
    +
    + field = purple_request_field_string_new("alias", _("Alias"),
    + NULL, FALSE);
    + purple_request_field_set_required(field, TRUE);
    + purple_request_field_set_type_hint(field, "alias");
    + purple_request_field_group_add_field(group, field);
    +
    + purple_request_fields(purple_get_blist(), _("Add Alias"),
    + NULL,
    + _("Type in the alias that you use"),
    + fields,
    + _("OK"), G_CALLBACK(new_alias),
    + _("Cancel"), NULL,
    + NULL, NULL, NULL, NULL);
    +}
    +
    +static void alias_delete (GtkButton *button, gpointer data)
    +{
    + GtkTreeSelection *selection;
    + GtkTreeIter iter;
    + char *alias;
    +
    + GList *aliases, *aliases_start, *new_aliases;
    +
    + selection = gtk_tree_view_get_selection (
    + GTK_TREE_VIEW (alias_view));
    +
    + if (!gtk_tree_selection_get_selected (selection, NULL, &iter))
    + return;
    +
    + gtk_tree_model_get (GTK_TREE_MODEL (alias_list), &iter,
    + 0, &alias, -1);
    + gtk_list_store_remove (alias_list, &iter);
    +
    + aliases = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases");
    + aliases_start = aliases;
    +
    + new_aliases = NULL;
    +
    + while (aliases) {
    + if (strcmp ((char *)aliases->data, alias)) {
    + new_aliases = g_list_append (new_aliases, aliases->data);
    + }
    +
    + aliases = aliases->next;
    + }
    +
    + purple_prefs_set_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases", new_aliases);
    +
    + free_string_list (aliases_start);
    + g_list_free (new_aliases);
    + free (alias);
    +}
    +
    +static void alias_what (GtkButton *button, gpointer data)
    +{
    + purple_notify_formatted (NULL, _("Aliases"), _("What this list is for"), NULL,
    + _("Logs in Purple are stored verbatim with what you see on the screen. "
    + "The names of the people in the conversation (both yourself and your "
    + "buddy) are shown with their given aliases as opposed to actual screen "
    + "names. If you have given yourself an alias in a conversation, list "
    + "it using this dialog. If you do not, messages written by you will "
    + "be incorrectly identified as received instead of sent.<br><br>Correct "
    + "capitalization and whitespace are not required for detection to "
    + "work.<br><br>You must disable/re-enable log stats to refresh the "
    + "database after an alias change."),
    + NULL, NULL);
    +}
    +
    +/* The main window */
    +GtkWidget *logstats_prefs (struct widget *w)
    +{
    + GtkWidget *ret, *vbox, *hbox;
    + GtkWidget *label, *button, *entry, *sw;
    +
    + GtkCellRenderer *renderer;
    + GtkTreeSelection *selection;
    + GtkTreeViewColumn *col;
    + GtkTreeIter iter;
    + GList *aliases, *aliases_start;
    +
    + ret = gtk_vbox_new (FALSE, 6);
    +
    + /* Checkbox for enabling/disabling */
    + checkbox = gtk_check_button_new_with_mnemonic (
    + "Enable statistics for logs");
    + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(checkbox),
    + purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled"));
    + gtk_box_pack_start (GTK_BOX(ret), checkbox, FALSE, FALSE, 0);
    +
    + vbox = gtk_vbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(ret), vbox, TRUE, TRUE, 0);
    +
    + /* Le format string */
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label), "<b>Format string for output</b>");
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0);
    +
    + entry = gtk_entry_new ();
    + gtk_box_pack_start (GTK_BOX(vbox), entry, FALSE, FALSE, 0);
    + gtk_entry_set_max_length (GTK_ENTRY(entry), 1000);
    + gtk_entry_set_text (GTK_ENTRY(entry), purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/components/logstat/format"));
    + g_signal_connect (G_OBJECT (entry), "focus-out-event",
    + G_CALLBACK (logstat_format), NULL);
    +
    + label = gtk_label_new (_(
    + "%R\tTotal messages received\n"
    + "%r\tTotal words received\n"
    + "%S\tTotal messages sent\n"
    + "%s\tTotal words sent\n"
    + "%T\tTotal messages sent/received\n"
    + "%t\tTotal words sent/received\n"
    + "%D\tNumber of days since first logged conversation\n"
    + "%d\tNumber of days with logged conversations\n"
    + "%N\tNumber of logged conversations\n"
    + "%n\tAverage number of conversations per day with logs\n"
    + "%i\tMost conversations in a single day\n"
    + "%I\tDate with most conversations\n"
    + "%j\tMost messages sent in a single day\n"
    + "%J\tDate with most messages sent\n"
    + "%k\tMost messages received in a single day\n"
    + "%K\tDate with most messages received\n"
    + "%l\tMost total messages sent/received in a single day\n"
    + "%L\tDate with most total messages sent/received\n"
    + "%f\tDate of first logged conversation\n"
    + "%u\tAverage words per message received\n"
    + "%v\tAverage words per message sent\n"
    + "%w\tAverage words per message sent/received\n"
    + "%U\tAverage messages received per conversation\n"
    + "%V\tAverage messages sent per conversation\n"
    + "%W\tAverage messages sent/received per conversation\n"
    + "%x\tAverage words received per day with logs\n"
    + "%y\tAverage words sent per day with logs\n"
    + "%z\tAverage words sent/received per day with logs\n"
    + "%X\tAverage messages received per day with logs\n"
    + "%Y\tAverage messages sent per day with logs\n"
    + "%Z\tAverage messages sent/received per day with logs\n"
    + "%p\tPercentage of days with logs\n"
    + "%a\tNumber of messages received today\n"
    + "%b\tNumber of messages sent today\n"
    + "%c\tNumber of conversations started today\n"
    + "%e\tNumber of messages sent/received today\n"
    + "%A\tNumber of messages received in last week\n"
    + "%B\tNumber of messages sent in last week\n"
    + "%C\tNumber of conversations started in last week\n"
    + "%E\tNumber of messages sent/received in last week\n"
    + "%%\t%"));
    +
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + sw = gtk_scrolled_window_new (NULL,NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
    + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
    + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE , 0);
    + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label);
    +
    + /* Aliases */
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label), "<b>Personal aliases</b>");
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0);
    +
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label),
    + "You need this if you have an alias for your own screen name,\n"
    + "else IM's you sent will be incorrectly counted as received");
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(vbox), label, FALSE, FALSE, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 3);
    + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label (_("Add alias"));
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK (alias_add), NULL);
    + gtk_box_pack_start (GTK_BOX(hbox), button, TRUE, TRUE, 0);
    + button = gtk_button_new_with_label (_("Delete alias"));
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK (alias_delete), NULL);
    + gtk_box_pack_start (GTK_BOX(hbox), button, TRUE, TRUE, 0);
    + button = gtk_button_new_with_label (_("?"));
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK (alias_what), NULL);
    + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    +
    + sw = gtk_scrolled_window_new (0, 0);
    + gtk_box_pack_start (GTK_BOX(vbox), sw, FALSE, FALSE, 0);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
    + GTK_POLICY_NEVER, GTK_POLICY_NEVER);
    + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw),
    + GTK_SHADOW_IN);
    +
    + alias_list = gtk_list_store_new (1, G_TYPE_STRING);
    + alias_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (alias_list));
    + gtk_container_add (GTK_CONTAINER(sw), alias_view);
    +
    + renderer = gtk_cell_renderer_text_new ();
    + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW(alias_view), FALSE);
    + selection = gtk_tree_view_get_selection (
    + GTK_TREE_VIEW (alias_view));
    + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
    +
    + col = gtk_tree_view_column_new_with_attributes (
    + _("Alias"), renderer, "text", 0, NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW(alias_view), col);
    +
    + aliases = purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/components/logstat/aliases");
    + aliases_start = aliases;
    +
    + while (aliases) {
    + gtk_list_store_append (alias_list, &iter);
    + gtk_list_store_set (alias_list, &iter,
    + 0, (char *)aliases->data, -1);
    + aliases = aliases->next;
    + }
    + free_string_list (aliases_start);
    +
    + /* Finish up the checkbox stuff */
    + g_signal_connect (G_OBJECT(checkbox), "clicked",
    + G_CALLBACK(toggle_enable), vbox);
    + if (!purple_prefs_get_bool (
    + "/plugins/gtk/autoprofile/components/logstat/enabled")) {
    + gtk_widget_set_sensitive (vbox, FALSE);
    + } else {
    + gtk_widget_set_sensitive (vbox, TRUE);
    + }
    +
    + return ret;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_quotation.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,602 @@
    +/*----------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +#include "request.h"
    +
    +#include "component.h"
    +#include "utility.h"
    +
    +enum {
    + QUOTATION_LIST_STORE = 1,
    + QUOTATION_FILE_SELECTOR,
    + QUOTATION_TREE_VIEW
    +};
    +
    +/*--------------------------------------------------------------------------*
    + * Menu related things *
    + *--------------------------------------------------------------------------*/
    +static void append_quote (struct widget *w, GtkListStore *ls, gchar *quote)
    +{
    + GString *s;
    + GtkTreeIter iter;
    + gchar *quote_tmp;
    + GtkWidget *treeview;
    + GtkTreeSelection *selection;
    +
    + gtk_list_store_append (ls, &iter);
    +
    + quote_tmp = purple_markup_strip_html (quote);
    + s = g_string_new ("");
    + g_string_printf (s, "%ld bytes", g_utf8_strlen (quote, -1));
    +
    + gtk_list_store_set (ls, &iter,
    + 0, quote_tmp,
    + 1, quote,
    + 2, s->str,
    + -1);
    + g_free (quote_tmp);
    + g_string_free (s, TRUE);
    +
    + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW);
    + if (treeview == NULL) return;
    +
    + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
    + gtk_tree_selection_select_iter (selection, &iter);
    +}
    +
    +static void file_dialog_cb (GtkWidget *dialog, int response, struct widget *w)
    +{
    + GtkWidget *checkbox;
    + gchar *filename;
    + GList *quotes, *quotes_start, *new_quotes;
    + gboolean include_html;
    +
    + GtkListStore *ls;
    +
    + switch (response) {
    + case GTK_RESPONSE_ACCEPT:
    + ls = ap_widget_get_data (w, QUOTATION_LIST_STORE);
    + if (ls == NULL) break;
    +
    + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER(dialog));
    + checkbox = gtk_file_chooser_get_extra_widget (GTK_FILE_CHOOSER(dialog));
    + g_object_get (checkbox, "active", &include_html, NULL);
    +
    + quotes = ap_prefs_get_string_list (w, "quotes");
    + new_quotes = read_fortune_file (filename, !include_html);
    + g_free (filename);
    +
    + quotes = g_list_concat (quotes, new_quotes);
    + ap_prefs_set_string_list (w, "quotes", quotes);
    +
    + quotes_start = quotes;
    +
    + for (quotes = new_quotes; quotes != NULL; quotes = quotes->next) {
    + append_quote (w, ls, quotes->data);
    + }
    +
    + free_string_list (quotes_start);
    + break;
    + case GTK_RESPONSE_CANCEL:
    + case GTK_RESPONSE_DELETE_EVENT:
    + break;
    + }
    +
    + ap_widget_set_data (w, QUOTATION_FILE_SELECTOR, NULL);
    + gtk_widget_destroy (dialog);
    +}
    +
    +static void quotation_explain_fortune_file (GtkMenuItem *item, gpointer data)
    +{
    + purple_notify_formatted (NULL, _("Fortune files"),
    + _("A quick definition of a fortune file"), NULL,
    + _("A fortune file is a simple text file with a number of quotes. "
    + "The following is an example:<br><br>"
    + "<b>\"Glory is fleeing, but obscurity is forver.\"<br>"
    + "- Napoleon Bonaparte (1769-1821)<br>"
    + "%<br>"
    + "Blagggghhhh!<br>"
    + "%<br>"
    + "Yet another quote<br>"
    + "%<br></b><br>"
    + "Quotes can have any sort of text within them. They end when there "
    + "is a newline followed by a percent sign \"%\" on the next line.<br>"
    + "<br>Fortune files with pre-selected quotes can be found on the"
    + "internet."),
    + NULL, NULL);
    +}
    +
    +static void quotation_select_import_file (GtkMenuItem *item, struct widget *w)
    +{
    + GtkWidget *dialog;
    + GtkWidget *checkbox;
    +
    + dialog = gtk_file_chooser_dialog_new (
    + _("Select fortune file to import quotes from"),
    + NULL,
    + GTK_FILE_CHOOSER_ACTION_OPEN,
    + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
    + NULL);
    + g_signal_connect (G_OBJECT(dialog), "response", G_CALLBACK (file_dialog_cb),
    + w);
    + ap_widget_set_data (w, QUOTATION_FILE_SELECTOR, dialog);
    +
    + checkbox = gtk_check_button_new_with_label (
    + _("Interpret bracketed text (such as \"<br>\") as HTML tags"));
    + gtk_file_chooser_set_extra_widget (GTK_FILE_CHOOSER(dialog), checkbox);
    +
    + gtk_widget_show_all (dialog);
    +}
    +
    +static void quotation_edit_dialog_cb (struct widget *w, const char *quote)
    +{
    + GtkWidget *treeview;
    + GtkTreeSelection *selection;
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    +
    + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW);
    + if (treeview == NULL) return;
    +
    + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
    +
    + if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
    + GString *s;
    + gchar *quote_tmp, *old_quote;
    + GList *start, *node;
    +
    + gtk_tree_model_get (model, &iter, 1, &old_quote, -1);
    + start = ap_prefs_get_string_list (w, "quotes");
    + /* FIXME: this could grab the wrong quote, if two quotes are identical */
    + for (node = start; node != NULL; node = node->next) {
    + if (!strcmp ((char *) node->data, old_quote)) {
    + /* Update saved prefs */
    + g_free (node->data);
    + node->data = strdup (quote);
    +
    + ap_prefs_set_string_list (w, "quotes", start);
    +
    + free_string_list (start);
    + g_free (old_quote);
    +
    + /* Update list store */
    + quote_tmp = purple_markup_strip_html (quote);
    + s = g_string_new ("");
    + g_string_printf (s, "%ld bytes", g_utf8_strlen (quote, -1));
    +
    + gtk_list_store_set (GTK_LIST_STORE (model), &iter,
    + 0, quote_tmp,
    + 1, quote,
    + 2, s->str,
    + -1);
    + g_free (quote_tmp);
    + g_string_free (s, TRUE);
    + return;
    + }
    + }
    +
    + free_string_list (start);
    + g_free (old_quote);
    + } else {
    + purple_notify_error (NULL, NULL,
    + N_("Unable to edit quote"),
    + N_("No quote is currently selected"));
    + }
    +
    +}
    +
    +static void quotation_edit_dialog (struct widget *w, const gchar *quote)
    +{
    + purple_request_input (ap_get_plugin_handle (), NULL,
    + _("Edit quote"), NULL,
    + quote,
    + TRUE, FALSE, "html",
    + _("Save"), G_CALLBACK(quotation_edit_dialog_cb),
    + _("Cancel"), NULL, NULL, NULL, NULL,
    + w);
    +}
    +
    +static void quotation_edit (GtkWidget *button, struct widget *w)
    +{
    + GtkWidget *treeview;
    + GtkTreeSelection *selection;
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    + gchar *quote;
    +
    + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW);
    + if (treeview == NULL) return;
    +
    + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
    +
    + if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
    + gtk_tree_model_get (model, &iter, 1, &quote, -1);
    + quotation_edit_dialog (w, quote);
    + g_free (quote);
    + } else {
    + purple_notify_error (NULL, NULL,
    + N_("Unable to edit quote"),
    + N_("No quote is currently selected"));
    + }
    +}
    +
    +static void quotation_create (GtkWidget *button, struct widget *w)
    +{
    + GtkListStore *ls;
    + GList *quotes;
    +
    + ls = ap_widget_get_data (w, QUOTATION_LIST_STORE);
    + if (ls == NULL) return;
    +
    + append_quote (w, ls, "");
    +
    + quotes = ap_prefs_get_string_list (w, "quotes");
    + quotes = g_list_append (quotes, strdup (""));
    + ap_prefs_set_string_list (w, "quotes", quotes);
    +
    + free_string_list (quotes);
    +
    + quotation_edit_dialog (w, "");
    +}
    +
    +static void quotation_delete (GtkWidget *button, struct widget *w)
    +{
    + GtkWidget *treeview;
    + GtkTreeSelection *selection;
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    + gchar *quote;
    +
    + GList *start, *node;
    +
    + treeview = (GtkWidget *) ap_widget_get_data (w, QUOTATION_TREE_VIEW);
    + if (treeview == NULL) return;
    +
    + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(treeview));
    +
    + if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
    + gtk_tree_model_get (model, &iter, 1, &quote, -1);
    + start = ap_prefs_get_string_list (w, "quotes");
    +
    + /* FIXME: this could grab the wrong quote, if two quotes are identical */
    + for (node = start; node != NULL; node = node->next) {
    + if (!strcmp ((char *) node->data, quote)) {
    + start = g_list_remove_link (start, node);
    + g_list_free_1 (node);
    + g_free (node->data);
    + ap_prefs_set_string_list (w, "quotes", start);
    +
    + free_string_list (start);
    + g_free (quote);
    +
    + gtk_list_store_remove (GTK_LIST_STORE(model), &iter);
    + return;
    + }
    + }
    +
    + free_string_list (start);
    + g_free (quote);
    + } else {
    + purple_notify_error (NULL, NULL,
    + N_("Unable to delete quote"),
    + N_("No quote is currently selected"));
    + }
    +}
    +
    +static void quotation_delete_all_cb (struct widget *w)
    +{
    + GtkListStore *ls;
    +
    + ls = ap_widget_get_data (w, QUOTATION_LIST_STORE);
    + if (ls == NULL) return;
    +
    + gtk_list_store_clear (ls);
    +
    + ap_prefs_set_string_list (w, "quotes", NULL);
    +}
    +
    +static void quotation_delete_all (GtkMenuItem *item, struct widget *w)
    +{
    + purple_request_ok_cancel (ap_get_plugin_handle (),
    + NULL, _("Delete all quotes?"), NULL, 0, NULL, NULL,
    + NULL, w, G_CALLBACK(quotation_delete_all_cb), NULL);
    +}
    +
    +static void quotation_more_menu (GtkWidget *button, struct widget *w)
    +{
    + GtkWidget *menu;
    + GtkWidget *menu_item;
    +
    + menu = gtk_menu_new ();
    +
    + menu_item = gtk_menu_item_new_with_label (_("Delete all quotes"));
    + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item);
    + g_signal_connect (G_OBJECT(menu_item), "activate",
    + G_CALLBACK(quotation_delete_all), w);
    +
    + menu_item = gtk_separator_menu_item_new ();
    + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item);
    +
    + menu_item = gtk_menu_item_new_with_label (
    + _("Import quotes from from fortune file"));
    + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item);
    + g_signal_connect (G_OBJECT(menu_item), "activate",
    + G_CALLBACK(quotation_select_import_file), w);
    +
    + menu_item = gtk_menu_item_new_with_label (
    + _("What is a fortune file?"));
    + gtk_menu_shell_append (GTK_MENU_SHELL(menu), menu_item);
    + g_signal_connect (G_OBJECT(menu_item), "activate",
    + G_CALLBACK(quotation_explain_fortune_file), NULL);
    +
    + gtk_menu_popup (GTK_MENU(menu), NULL, NULL, NULL, w, 0,
    + gtk_get_current_event_time ());
    + gtk_widget_show_all (menu);
    +}
    +
    +static void quotation_rate_changed (GtkSpinButton *spinner, struct widget *w)
    +{
    + int value = gtk_spin_button_get_value_as_int (spinner);
    + ap_prefs_set_int (w, "update_rate", value);
    +}
    +
    +static void quotation_force_change (GtkButton *button, struct widget *w)
    +{
    + ap_prefs_set_int (w, "current_index",
    + ap_prefs_get_int (w, "current_index") + 1);
    +}
    +
    +
    +
    +static gboolean
    +search_func(GtkTreeModel *model, gint column, const gchar *key,
    + GtkTreeIter *iter, gpointer search_data)
    +{
    + gboolean result;
    + char *haystack;
    +
    + gtk_tree_model_get (model, iter, 1, &haystack, -1);
    + result = (purple_strcasestr(haystack, key) == NULL);
    + g_free(haystack);
    +
    + return result;
    +}
    +
    +static void menu_destroy_cb (GtkWidget *widget, struct widget *w)
    +{
    + GtkWidget *file_selector;
    +
    + ap_widget_set_data (w, QUOTATION_LIST_STORE, NULL);
    + ap_widget_set_data (w, QUOTATION_TREE_VIEW, NULL);
    +
    + file_selector = (GtkWidget *) ap_widget_get_data (w, QUOTATION_FILE_SELECTOR);
    + if (file_selector != NULL) {
    + file_dialog_cb (file_selector, GTK_RESPONSE_DELETE_EVENT, w);
    + }
    +}
    +
    +static GtkWidget *quotation_menu (struct widget *w)
    +{
    + GtkWidget *ret, *hbox;
    + GtkWidget *button, *label, *spinner;
    + GtkWidget *sw;
    + GList *quotes, *quotes_start;
    +
    + GtkWidget *treeview;
    + GtkListStore *ls;
    + GtkTreeViewColumn *col;
    + GtkCellRenderer *rend;
    +
    + ret = gtk_vbox_new (FALSE, 6);
    + g_signal_connect (G_OBJECT(ret), "destroy", G_CALLBACK (menu_destroy_cb), w);
    +
    + /* The main view */
    + 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(ret), sw, TRUE, TRUE, 0);
    +
    + ls = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
    + ap_widget_set_data (w, QUOTATION_LIST_STORE, ls);
    +
    + treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL(ls));
    + ap_widget_set_data (w, QUOTATION_TREE_VIEW, treeview);
    +
    + rend = gtk_cell_renderer_text_new ();
    + col = gtk_tree_view_column_new_with_attributes (_("Size"),
    + rend, "text", 2, NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), col);
    + g_object_set (G_OBJECT(rend),
    + "cell-background-set", TRUE,
    + "cell-background", "gray",
    + NULL);
    +
    + rend = gtk_cell_renderer_text_new ();
    + col = gtk_tree_view_column_new_with_attributes (_("Quotes"),
    + rend, "text", 0, NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW(treeview), col);
    +
    + /* Enable CTRL+F searching */
    + gtk_tree_view_set_search_column (GTK_TREE_VIEW(treeview), 0);
    + gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW(treeview),
    + search_func, NULL, NULL);
    +
    + gtk_container_add (GTK_CONTAINER(sw), treeview);
    +
    + /* Add in the original quotes */
    + quotes_start = ap_prefs_get_string_list (w, "quotes");
    +
    + for (quotes = quotes_start; quotes != NULL; quotes = quotes->next) {
    + append_quote (w, ls, quotes->data);
    + }
    + free_string_list (quotes_start);
    +
    + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW(treeview), TRUE);
    +
    + /* Bottom buttons */
    + hbox = gtk_hbutton_box_new ();
    + gtk_button_box_set_layout (GTK_BUTTON_BOX(hbox),
    + GTK_BUTTONBOX_SPREAD);
    +
    + gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label (_("New quote"));
    + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK(quotation_create), w);
    +
    + button = gtk_button_new_with_label (_("Edit"));
    + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK(quotation_edit), w);
    +
    + button = gtk_button_new_with_label (_("Delete"));
    + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK(quotation_delete), w);
    +
    + button = gtk_button_new_with_label (_("More..."));
    + gtk_box_pack_start (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK(quotation_more_menu), w);
    +
    + /* Separator */
    + gtk_box_pack_start (GTK_BOX(ret), gtk_hseparator_new (), FALSE, FALSE, 0);
    +
    + /* Behavior selection */
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0);
    +
    + label = gtk_label_new (_("Change quote every "));
    + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    + spinner = gtk_spin_button_new_with_range (0, G_MAXINT, 1);
    + gtk_box_pack_start (GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON(spinner),
    + ap_prefs_get_int (w, "update_rate"));
    + g_signal_connect (G_OBJECT(spinner), "value-changed",
    + G_CALLBACK(quotation_rate_changed), w);
    +
    + label = gtk_label_new (_("hours (0: always show a new quote)"));
    + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label (_("Change quote now"));
    + gtk_box_pack_end (GTK_BOX(hbox), button, FALSE, FALSE, 0);
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (quotation_force_change), w);
    +
    + return ret;
    +}
    +
    +
    +/*--------------------------------------------------------------------------*
    + * Core quotation things *
    + *--------------------------------------------------------------------------*/
    +static gchar *quotation_generate (struct widget *w)
    +{
    + GList *quotes;
    + gchar *ret;
    + int num_quotes, index;
    + time_t cur_time, old_time;
    + char *time_string;
    + struct tm *t;
    +
    + index = ap_prefs_get_int (w, "current_index");
    + quotes = ap_prefs_get_string_list (w, "quotes");
    +
    + /* Sanity check the quotes */
    + num_quotes = g_list_length (quotes);
    +
    + if (num_quotes == 0) {
    + return strdup (_("[ERROR: no quotes available]"));
    + }
    +
    + /* Increment index if time has elapsed */
    + old_time = purple_str_to_time (ap_prefs_get_string (w, "last_update"), TRUE,
    + NULL, NULL, NULL);
    + cur_time = time (NULL);
    +
    + if (difftime (cur_time, old_time) >
    + 60.0 * 60.0 * (double) ap_prefs_get_int (w, "update_rate"))
    + {
    + ap_debug ("quote", "time interval elapsed, moving to new quote");
    +
    + time_string = (char *)malloc(1000);
    + t = ap_gmtime (&cur_time);
    + strftime (time_string, 999, "%Y-%m-%dT%H:%M:%S+00:00", t);
    + free (t);
    + ap_prefs_set_string (w, "last_update", time_string);
    + free (time_string);
    +
    + index++;
    + ap_prefs_set_int (w, "current_index", index);
    + }
    +
    + /* Wrap around when last quote is reached */
    + if (index >= num_quotes) {
    + index = 0;
    + ap_prefs_set_int (w, "current_index", 0);
    + }
    +
    + /* Choose and output the quote */
    + ret = strdup((gchar *) g_list_nth_data (quotes, index));
    + free_string_list (quotes);
    + return ret;
    +}
    +
    +static void quotation_init (struct widget *w)
    +{
    + time_t the_time;
    + char *time_string;
    +
    + time_string = (char *)malloc(1000);
    + the_time = time(NULL);
    + strftime (time_string, 999, "%Y-%m-%dT%H:%M:%S+00:00", gmtime (&the_time));
    +
    + ap_prefs_add_string_list (w, "quotes", NULL);
    +
    + ap_prefs_add_int (w, "current_index", 0);
    + ap_prefs_add_int (w, "update_rate", 0);
    + ap_prefs_add_string (w, "last_update", time_string);
    +
    + free (time_string);
    +}
    +
    +
    +struct component quotation =
    +{
    + N_("Quotes"),
    + N_("Displays a quotation from a provided selection"),
    + "Quote",
    + &quotation_generate,
    + &quotation_init,
    + NULL,
    + NULL,
    + NULL,
    + &quotation_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_rss.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,477 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "comp_rss.h"
    +#include "autoprofile.h"
    +
    +#include "gtkimhtml.h"
    +
    +#include <ctype.h>
    +
    +static GtkWidget *entry_username = NULL;
    +static GtkWidget *entry_url = NULL;
    +
    +GHashTable *rss_entries = NULL;
    +static GHashTable *rss_timeouts = NULL;
    +GStaticMutex rss_mutex = G_STATIC_MUTEX_INIT;
    +
    +/* Core functions */
    +static char *get_rss_data (struct widget *w, const char *field, int index,
    + struct tm **time)
    +{
    + GList *tmp;
    + const struct rss_entry *e;
    + char *ret;
    + int max;
    +
    + g_static_mutex_lock (&rss_mutex);
    + tmp = (GList *) g_hash_table_lookup (rss_entries, w);
    +
    + if (index < 0 ) {
    + g_static_mutex_unlock (&rss_mutex);
    + return strdup (_("[ERROR: Invalid entry number]"));
    + }
    +
    + if (tmp == NULL) {
    + g_static_mutex_unlock (&rss_mutex);
    + return strdup (_("[ERROR: No data, invalid URL/account?]"));
    + }
    +
    + if (index != 0) {
    + while (index-- != 1) {
    + tmp = tmp->next;
    + if (tmp == NULL) {
    + g_static_mutex_unlock (&rss_mutex);
    + return strdup (_("[ERROR: Insufficient number of entries]"));
    + }
    + }
    + }
    +
    + e = (struct rss_entry *) tmp->data;
    +
    + if (!strcmp (field, "link")) {
    + if (e->url)
    + ret = strdup (e->url);
    + else
    + ret = NULL;
    + } else if (!strcmp (field, "title")) {
    + if (e->title)
    + ret = strdup (e->title);
    + else
    + ret = NULL;
    + } else if (!strcmp (field, "entry")) {
    + if (e->entry) {
    + max = ap_prefs_get_int (w, "entry_limit");
    + ret = strdup (e->entry);
    + if (max < g_utf8_strlen (ret, -1)) {
    + gchar *tmp = g_utf8_offset_to_pointer (ret, max);
    + *tmp = '\0';
    + }
    + } else {
    + ret = NULL;
    + }
    + } else if (!strcmp (field, "time")) {
    + *time = e->t;
    + ret = NULL;
    + } else {
    + ret = NULL;
    + }
    +
    + g_static_mutex_unlock (&rss_mutex);
    + return ret;
    +}
    +
    +static char *rss_generate (struct widget *w)
    +{
    + GString *output;
    + gchar *result;
    +
    + char *tmp, *time_tmp;
    + int state;
    + int count;
    + const char *format;
    + char fmt_char [3];
    + struct tm *time;
    +
    + fmt_char[0] = '%';
    + fmt_char[2] = '\0';
    +
    + format = ap_prefs_get_string (w, "format");
    + output = g_string_new ("");
    + time_tmp = (char *)malloc (sizeof(char)*AP_SIZE_MAXIMUM);
    +
    + state = 0;
    + count = 0;
    +
    + while (*format) {
    + if (state == 1) {
    + if (isdigit (*format)) {
    + count = (count * 10) + (int) *format - 48;
    + format++;
    + } else {
    + switch (*format) {
    + case 'H':
    + case 'I':
    + case 'p':
    + case 'M':
    + case 'S':
    + case 'a':
    + case 'A':
    + case 'b':
    + case 'B':
    + case 'm':
    + case 'd':
    + case 'j':
    + case 'W':
    + case 'w':
    + case 'y':
    + case 'Y':
    + case 'z':
    + time = NULL;
    + tmp = get_rss_data (w, "time", count, &time);
    + if (time) {
    + fmt_char[1] = *format;
    + strftime (time_tmp, AP_SIZE_MAXIMUM, fmt_char, time);
    + g_string_append_printf (output, "%s", time_tmp);
    + }
    + break;
    + case 'l':
    + tmp = get_rss_data (w, "link", count, NULL);
    + if (tmp) {
    + g_string_append_printf (output, "%s", tmp);
    + free (tmp);
    + }
    + break;
    + case 't':
    + tmp = get_rss_data (w, "title", count, NULL);
    + if (tmp) {
    + g_string_append_printf (output, "%s", tmp);
    + free (tmp);
    + }
    + break;
    + case 'e':
    + tmp = get_rss_data (w, "entry", count, NULL);
    + if (tmp) {
    + g_string_append_printf (output, "%s", tmp);
    + free (tmp);
    + }
    + break;
    + case '%':
    + g_string_append_printf (output, "%c", *format);
    + break;
    + default:
    + g_string_append_unichar (output, g_utf8_get_char (format));
    + break;
    + }
    + format = g_utf8_next_char (format);
    + state = 0;
    + }
    + } else {
    + if (*format == '%') {
    + state = 1;
    + count = 0;
    + } else {
    + g_string_append_unichar (output, g_utf8_get_char (format));
    + }
    + format = g_utf8_next_char (format);
    + }
    + }
    +
    + result = output->str;
    + g_string_free (output, FALSE);
    + return result;
    +}
    +
    +static gboolean rss_update (gpointer data)
    +{
    + parse_rss ((struct widget *) data);
    + return TRUE;
    +}
    +
    +static void rss_load (struct widget *w)
    +{
    + gpointer rss_timeout;
    +
    + g_static_mutex_lock (&rss_mutex);
    + if (!rss_entries) {
    + rss_entries = g_hash_table_new (NULL, NULL);
    + }
    + if (!rss_timeouts) {
    + rss_timeouts = g_hash_table_new (NULL, NULL);
    + }
    +
    + rss_timeout = GINT_TO_POINTER (g_timeout_add (
    + ap_prefs_get_int (w, "update_rate") * 60 * 1000,
    + rss_update, w));
    + g_hash_table_insert (rss_timeouts, w, rss_timeout);
    +
    + g_static_mutex_unlock (&rss_mutex);
    +
    + rss_update (w);
    +}
    +
    +static void rss_unload (struct widget *w)
    +{
    + gpointer rss_timeout;
    +
    + g_static_mutex_lock (&rss_mutex);
    + rss_timeout = g_hash_table_lookup (rss_timeouts, w);
    + g_source_remove (GPOINTER_TO_INT (rss_timeout));
    + g_hash_table_remove (rss_timeouts, w);
    +
    + g_static_mutex_unlock (&rss_mutex);
    +}
    +
    +static void rss_init (struct widget *w)
    +{
    + ap_prefs_add_int (w, "type", RSS_XANGA);
    + ap_prefs_add_string (w, "location", "");
    + ap_prefs_add_string (w, "username", "");
    + ap_prefs_add_string (w, "format",
    + "My <a href=\"%l\">blog</a> was most recently updated on "
    + "%1B %1d at %I:%M %p");
    + ap_prefs_add_int (w, "update_rate", 5);
    + ap_prefs_add_int (w, "entry_limit", 1000);
    +}
    +
    +/* GUI functions */
    +static gboolean update_refresh_rate (GtkWidget *widget, GdkEventFocus *evt,
    + struct widget *w)
    +{
    + gpointer timeout;
    + int minutes;
    +
    + minutes = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget));
    + ap_prefs_set_int (w, "update_rate", minutes);
    +
    + // Kill the current timer and run a new one
    + g_static_mutex_lock (&rss_mutex);
    + timeout = g_hash_table_lookup (rss_timeouts, w);
    + g_source_remove (GPOINTER_TO_INT(timeout));
    + timeout = GINT_TO_POINTER (g_timeout_add (minutes * 60 * 1000,
    + rss_update, w));
    + g_hash_table_replace (rss_timeouts, w, timeout);
    + g_static_mutex_unlock (&rss_mutex);
    +
    + return FALSE;
    +}
    +
    +static void rss_data_update (GtkWidget *widget, struct widget *w)
    +{
    + rss_update (w);
    +}
    +
    +static void sensitivity_cb (const char *name, PurplePrefType type,
    + gconstpointer val, gpointer data)
    +{
    + int real_val = GPOINTER_TO_INT (val);
    +
    + if (real_val == RSS_XANGA || real_val == RSS_LIVEJOURNAL) {
    + gtk_widget_set_sensitive (entry_username, TRUE);
    + gtk_widget_set_sensitive (entry_url, FALSE);
    + } else {
    + gtk_widget_set_sensitive (entry_username, FALSE);
    + gtk_widget_set_sensitive (entry_url, TRUE);
    + }
    +}
    +
    +static GtkWidget *entry;
    +
    +static void event_cb (GtkWidget *widget, struct widget *w)
    +{
    + ap_prefs_set_string (w, "format",
    + gtk_imhtml_get_markup (GTK_IMHTML(entry)));
    +}
    +static void formatting_toggle_cb (GtkIMHtml *imhtml,
    + GtkIMHtmlButtons buttons, struct widget *w)
    +{
    + ap_prefs_set_string (w, "format",
    + gtk_imhtml_get_markup (GTK_IMHTML(entry)));
    +
    +}
    +
    +static void formatting_clear_cb (GtkIMHtml *imhtml,
    + struct widget *w)
    +{
    + ap_prefs_set_string (w, "format",
    + gtk_imhtml_get_markup (GTK_IMHTML(entry)));
    +}
    +
    +static GtkWidget *rss_menu (struct widget *w)
    +{
    + GtkWidget *ret;
    + GList *options;
    + GtkWidget *label, *hbox, *button, *spinner, *sw;
    + GtkWidget *entry_window, *toolbar;
    + GtkTextBuffer *text_buffer;
    +
    + int value;
    + gchar *pref;
    +
    + ret = gtk_vbox_new (FALSE, 5);
    +
    + /* Format string */
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label), "<b>Format string for output</b>");
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX(ret), hbox, TRUE, TRUE, 0);
    +
    + entry_window = pidgin_create_imhtml (TRUE, &entry, &toolbar, &sw);
    + gtk_box_pack_start (GTK_BOX (hbox), entry_window, TRUE, TRUE, 0);
    + gtk_imhtml_append_text_with_images (GTK_IMHTML(entry),
    + ap_prefs_get_string (w, "format"),
    + 0, NULL);
    + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
    + g_signal_connect (G_OBJECT (text_buffer), "changed",
    + G_CALLBACK (event_cb), w);
    + g_signal_connect_after(G_OBJECT(entry), "format_function_toggle",
    + G_CALLBACK(formatting_toggle_cb), w);
    + g_signal_connect_after(G_OBJECT(entry), "format_function_clear",
    + G_CALLBACK(formatting_clear_cb), w);
    +
    + label = gtk_label_new (_(
    + "The following options can be specified with a numerical modifier\n"
    + "(i.e. \"%e\" can also be written \"%1e\" or \"%2e\"). The optional\n"
    + "number specifies which entry to get the data for. \"1\" refers to the\n"
    + "most recent entry, \"2\" refers to the second-most recent entry, and so\n"
    + "forth. \"1\" is used if no number is specified.\n\n"
    + "%e\tStarting text of the entry.\n"
    + "%l\tLink to the specific entry.\n"
    + "%t\tTitle of entry (Xanga incompatible)\n"
    +
    + "\nTime of entry:\n"
    + "%H\thour of entry(24-hour clock)\n"
    + "%I\thour (12-hour clock)\n"
    + "%p\tAM or PM\n"
    + "%M\tminute\n"
    + "%S\tsecond\n"
    + "%a\tabbreviated weekday name\n"
    + "%A\tfull weekday name\n"
    + "%b\tabbreviated month name\n"
    + "%B\tfull month name\n"
    + "%m\tmonth (numerical)\n"
    + "%d\tday of the month\n"
    + "%j\tday of the year\n"
    + "%W\tweek number of the year\n"
    + "%w\tweekday (numerical)\n"
    + "%y\tyear without century\n"
    + "%Y\tyear with century\n"
    + "%z\ttime zone name, if any\n"
    + "%%\t%"));
    +
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + sw = gtk_scrolled_window_new (NULL,NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
    + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
    + gtk_box_pack_start (GTK_BOX (hbox), sw, TRUE, TRUE , 0);
    + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label);
    +
    + /* Type/URL/Username selection */
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label), "<b>RSS/Blog location</b>");
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX(ret), hbox, FALSE, FALSE, 0);
    +
    + /* Dropdown */
    + options = g_list_append (NULL, (char *) _("Xanga"));
    + options = g_list_append (options, GINT_TO_POINTER(RSS_XANGA));
    + options = g_list_append (options, (char *) _("LiveJournal"));
    + options = g_list_append (options, GINT_TO_POINTER(RSS_LIVEJOURNAL));
    + options = g_list_append (options, (char *) _("RSS 2.0"));
    + options = g_list_append (options, GINT_TO_POINTER(RSS_2));
    + ap_prefs_dropdown_from_list (w, hbox, NULL, PURPLE_PREF_INT, "type", options);
    + g_list_free (options);
    +
    + pref = ap_prefs_get_pref_name (w, "type");
    + purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    + sensitivity_cb, w);
    + free (pref);
    +
    + /* Username/URL fields */
    + entry_username = ap_prefs_labeled_entry (w, hbox, _("Username:"),
    + "username", NULL);
    + entry_url = ap_prefs_labeled_entry (w, hbox, _("URL of feed:"),
    + "location", NULL);
    +
    + value = ap_prefs_get_int (w, "type");
    + if (value == RSS_XANGA || value == RSS_LIVEJOURNAL) {
    + gtk_widget_set_sensitive (entry_username, TRUE);
    + gtk_widget_set_sensitive (entry_url, FALSE);
    + } else {
    + gtk_widget_set_sensitive (entry_username, FALSE);
    + gtk_widget_set_sensitive (entry_url, TRUE);
    + }
    +
    + /* Other options */
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label), "<b>Other options</b>");
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(ret), label, FALSE, FALSE, 0);
    +
    + /* # of chars to display from description */
    + ap_prefs_labeled_spin_button (w, ret,
    + "Max characters to show in entry (%e)", "entry_limit", 1,
    + AP_SIZE_MAXIMUM - 1, NULL);
    +
    + /* Update rate selection */
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + label = gtk_label_new (_("Minutes between checks for updates:"));
    + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
    +
    + spinner = gtk_spin_button_new_with_range (1, 60, 1);
    + gtk_box_pack_start (GTK_BOX(hbox), spinner, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner),
    + ap_prefs_get_int (w, "update_rate"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_refresh_rate), w);
    +
    + button = gtk_button_new_with_label ("Fetch data now!");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (rss_data_update), w);
    + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
    +
    + return ret;
    +}
    +
    +/* Le end */
    +struct component rss =
    +{
    + N_("RSS / Blogs"),
    + N_("Information taken from an RSS feed (Xanga and LiveJournal capable)"),
    + "RSS",
    + &rss_generate,
    + &rss_init,
    + &rss_load,
    + &rss_unload,
    + NULL,
    + &rss_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_rss.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,52 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "component.h"
    +#include "time.h"
    +
    +#define MAX_USERNAME_LENGTH 1000
    +
    +struct rss_entry {
    + struct tm *t;
    + char *title;
    + char *entry;
    + char *url;
    + char *comments;
    +};
    +
    +typedef enum
    +{
    + RSS_UNKNOWN = -1,
    + RSS_XANGA,
    + RSS_LIVEJOURNAL,
    + RSS_2
    +} RSS_TYPE;
    +
    +extern GHashTable *rss_entries;
    +extern GStaticMutex rss_mutex;
    +
    +void parse_rss (struct widget *);
    +void parse_xanga_rss (struct widget *, gchar *);
    +GMarkupParser rss_parser;
    +
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_rss_parser.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,350 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "comp_rss.h"
    +#include "utility.h"
    +
    +#include <string.h>
    +
    +static gboolean parsing_rss = FALSE;
    +static gboolean parsing_item = FALSE;
    +
    +static gboolean item_title = FALSE;
    +static gboolean item_link = FALSE;
    +static gboolean item_description = FALSE;
    +static gboolean item_comments = FALSE;
    +static gboolean item_pubdate = FALSE;
    +
    +/* Get URL of RSS feed */
    +static char *get_url (struct widget *w)
    +{
    + int type;
    + char *ret;
    + const char *username;
    + GString *s;
    +
    + type = ap_prefs_get_int (w, "type");
    + s = g_string_new ("");
    +
    + switch (type) {
    + case RSS_LIVEJOURNAL:
    + username = ap_prefs_get_string (w, "username");
    + g_string_append_printf (s,
    + "http://www.livejournal.com/users/%s/data/rss", username);
    + break;
    + case RSS_XANGA:
    + username = ap_prefs_get_string (w, "username");
    + g_string_append_printf (s, "http://www.xanga.com/%s/rss", username);
    + break;
    + case RSS_2:
    + g_string_append_printf (s, "%s", ap_prefs_get_string (w, "location"));
    + break;
    + default:
    + break;
    + }
    +
    + ret = s->str;
    + g_string_free (s, FALSE);
    + return ret;
    +}
    +
    +/* Utility functions */
    +static void free_entries (struct widget *w) {
    + GList *tmp, *entries;
    + struct rss_entry *e;
    +
    + entries = (GList *) g_hash_table_lookup (rss_entries, w);
    +
    + while (entries) {
    + e = (struct rss_entry *) entries->data;
    + if (e->title)
    + free (e->title);
    + if (e->entry)
    + free (e->entry);
    + if (e->url)
    + free (e->url);
    + if (e->comments)
    + free (e->comments);
    + if (e->t)
    + free (e->t);
    +
    + free (e);
    + tmp = entries;
    + entries = entries->next;
    + g_list_free_1 (tmp);
    + }
    + g_hash_table_replace (rss_entries, w, NULL);
    +}
    +
    +/* Date parsing functions */
    +static struct tm *parse_date_rfc822 (const char *date_string)
    +{
    + time_t t, gmt_time, local_time;
    + struct tm *ret, *result;
    +
    + local_time = time(NULL);
    + gmt_time = time(NULL);
    + gmt_time = mktime(gmtime(&gmt_time));
    +
    + // TODO: Change this to GDate
    +
    + t = rfc_parse_date_time (date_string);
    + // if (rfc_parse_was_gmt ()) {
    + // FIXME: Handle time zones
    + // } else {
    + ret = (struct tm *) malloc (sizeof (struct tm));
    + result = localtime(&t);
    + ret->tm_sec = result->tm_sec;
    + ret->tm_min = result->tm_min;
    + ret->tm_hour = result->tm_hour;
    + ret->tm_mday = result->tm_mday;
    + ret->tm_mon = result->tm_mon;
    + ret->tm_year = result->tm_year;
    + //}
    +
    + return ret;
    +}
    +
    +/* XML Parser Callbacks */
    +static void start_element_handler (GMarkupParseContext *context,
    + const gchar *element_name,
    + const gchar **attribuate_names,
    + const gchar **attribute_values,
    + gpointer data, GError **error)
    +{
    + struct rss_entry *e;
    + GList *entries;
    + struct widget *w = (struct widget *) data;
    +
    + //printf ("start:%s\n", element_name);
    +
    + if (!parsing_rss && !strcmp (element_name, "rss"))
    + parsing_rss = TRUE;
    +
    + else if (parsing_rss && !parsing_item &&
    + !strcmp (element_name, "item")) {
    + parsing_item = TRUE;
    + e = (struct rss_entry *) malloc (sizeof(struct rss_entry));
    + entries = (GList *) g_hash_table_lookup (rss_entries, w);
    + entries = g_list_prepend (entries, e);
    + g_hash_table_replace (rss_entries, w, entries);
    + e->t = NULL;
    + e->title = NULL;
    + e->entry = NULL;
    + e->url = NULL;
    + e->comments = NULL;
    + }
    +
    + else if (parsing_item && !strcmp (element_name, "title"))
    + item_title = TRUE;
    + else if (parsing_item && !strcmp (element_name, "link"))
    + item_link = TRUE;
    + else if (parsing_item && !strcmp (element_name, "description"))
    + item_description = TRUE;
    + else if (parsing_item && !strcmp (element_name, "comments"))
    + item_comments = TRUE;
    + else if (parsing_item && !strcmp (element_name, "pubDate"))
    + item_pubdate = TRUE;
    +}
    +
    +static void end_element_handler (GMarkupParseContext *context,
    + const gchar *element_name,
    + gpointer data, GError **error)
    +{
    + struct widget *w = (struct widget *) w;
    + //printf ("end:%s\n", element_name);
    +
    + if (!strcmp (element_name, "rss"))
    + parsing_rss = FALSE;
    + else if (!strcmp (element_name, "item"))
    + parsing_item = FALSE;
    +
    + else if (!strcmp (element_name, "title"))
    + item_title = FALSE;
    + else if (!strcmp (element_name, "link"))
    + item_link = FALSE;
    + else if (!strcmp (element_name, "description"))
    + item_description = FALSE;
    + else if (!strcmp (element_name, "comments"))
    + item_comments = FALSE;
    + else if (!strcmp (element_name, "pubDate"))
    + item_pubdate = FALSE;
    +}
    +
    +static void text_handler (GMarkupParseContext *context,
    + const gchar *text, gsize text_len,
    + gpointer data, GError **error)
    +{
    + struct rss_entry *e;
    + GList *entries;
    + struct widget *w = (struct widget *) data;
    +
    + entries = (GList *) g_hash_table_lookup (rss_entries, w);
    +
    + if (entries == NULL) {
    + return;
    + }
    +
    + e = (struct rss_entry *) entries->data;
    +
    + if (item_link) {
    + if (e->url) {
    + free (e->url);
    + }
    + e->url = g_strdup (text);
    + }
    +
    + else if (item_description) {
    + if (e->entry) {
    + free (e->entry);
    + }
    + e->entry = purple_unescape_html (text);
    +
    + // If there is a standard format for Xanga titles (there really isn't)
    + // it will probably be devised from the actual content. Will be placed
    + // here if there is proven demand.
    + }
    +
    + else if (item_comments) {
    + if (e->comments) {
    + free (e->comments);
    + }
    + e->comments = g_strdup (text);
    + }
    +
    + else if (item_title) {
    + if (e->title) {
    + free (e->title);
    + }
    + e->title = g_strdup (text);
    + }
    +
    + else if (item_pubdate) {
    + if (e->t) {
    + free (e->t);
    + }
    + e->t = parse_date_rfc822 (text);
    + }
    +}
    +
    +/* Final parser variable */
    +GMarkupParser rss_parser =
    +{
    + start_element_handler,
    + end_element_handler,
    + text_handler,
    + NULL,
    + NULL
    +};
    +
    +/* Callback for HTTP data fetcher */
    +static void url_callback (gpointer data, const char *text, size_t size)
    +{
    + GMarkupParseContext *context;
    + gchar *filtered_text;
    + GError *err = NULL;
    + GList *entries;
    + struct widget *w = (struct widget *) data;
    +
    + /* Make sure URL exists/connected to Internet */
    + if (text == NULL) {
    + ap_debug ("rss", "error, unable to fetch page via internet");
    + return;
    + }
    +
    + parsing_rss = FALSE;
    + parsing_item = FALSE;
    +
    + item_title = FALSE;
    + item_link = FALSE;
    + item_description = FALSE;
    + item_comments = FALSE;
    + item_pubdate = FALSE;
    +
    + g_static_mutex_lock (&rss_mutex);
    +
    + free_entries (w);
    +
    + // Sanity checking
    + filtered_text = purple_utf8_salvage (text);
    +
    + gchar *convert = purple_utf8_try_convert ("<");
    + gchar *next = g_utf8_strchr (filtered_text, 10, g_utf8_get_char (convert));
    + free (convert);
    +
    + if (next == NULL) {
    + free (filtered_text);
    + // TODO: error out
    + g_static_mutex_unlock (&rss_mutex);
    + return;
    + }
    +
    + if (ap_prefs_get_int (w, "type") == RSS_XANGA) {
    + parse_xanga_rss (w, filtered_text);
    + entries = (GList *) g_hash_table_lookup (rss_entries, w);
    + entries = g_list_reverse (entries);
    + g_hash_table_replace (rss_entries, w, entries);
    + g_static_mutex_unlock (&rss_mutex);
    + free (filtered_text);
    + return;
    + }
    +
    + context = g_markup_parse_context_new (&rss_parser, 0, w, NULL);
    +
    + if (!g_markup_parse_context_parse (context, next, size, &err)) {
    + g_markup_parse_context_free (context);
    + ap_debug ("rss", "error, unable to start parser");
    + ap_debug ("rss", err->message);
    + free (filtered_text);
    + return;
    + }
    +
    + if (!g_markup_parse_context_end_parse (context, &err)) {
    + g_markup_parse_context_free (context);
    + ap_debug ("rss", "error, unable to end parser");
    + free (filtered_text);
    + return;
    + }
    +
    + g_markup_parse_context_free (context);
    +
    + entries = (GList *) g_hash_table_lookup (rss_entries, w);
    + entries = g_list_reverse (entries);
    + g_hash_table_replace (rss_entries, w, entries);
    + g_static_mutex_unlock (&rss_mutex);
    +
    + free (filtered_text);
    +}
    +
    +void parse_rss (struct widget *w)
    +{
    + char *url;
    +
    + url = get_url (w);
    + if (strcmp (url, "") != 0) {
    + purple_util_fetch_url (url, TRUE, NULL, FALSE, url_callback, w);
    + }
    + free (url);
    +}
    +
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_rss_xanga.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,117 @@
    +/*--------------------------------------------------------------------------*
    + * 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 <stdlib.h>
    +
    +#include <time.h>
    +#include <string.h>
    +#include <stdio.h>
    +
    +#include "comp_rss.h"
    +#include <glib.h>
    +
    +static gchar *convert_char;
    +
    +
    +static gchar *find_next_tag (gchar *text) {
    + *convert_char = '<';
    + return g_utf8_strchr (text, -1, g_utf8_get_char (convert_char));
    +}
    +
    +static gchar *find_end_of_tag (gchar *text) {
    + *convert_char = '>';
    + return g_utf8_strchr (text, -1, g_utf8_get_char (convert_char));
    +}
    +
    +static gboolean starts_with (gchar *text, gchar letter) {
    + *convert_char = letter;
    + return (g_utf8_strchr (text, 1, g_utf8_get_char (convert_char)) == text);
    +}
    +
    +void parse_xanga_rss (struct widget *w, gchar *text) {
    + gchar *next_tag, *end_prev_tag;
    + gchar *tag_first_char, *tag_second_char;
    + gboolean is_item;
    +
    + convert_char = (gchar *) malloc (sizeof(gchar) * 2);
    + *(convert_char+1) = '\0';
    +
    + end_prev_tag = text;
    + is_item = FALSE;
    +
    + rss_parser.start_element (NULL, "rss", NULL, NULL, w, NULL);
    +
    + while ((next_tag = find_next_tag (end_prev_tag)) != NULL) {
    + tag_first_char = g_utf8_next_char (next_tag);
    + tag_second_char = g_utf8_next_char (tag_first_char);
    +
    + if (is_item) {
    + if (starts_with (tag_first_char, 't')) rss_parser.start_element (
    + NULL, "title", NULL, NULL, w, NULL);
    + else if (starts_with (tag_first_char, 'l')) rss_parser.start_element (
    + NULL, "link", NULL, NULL, w, NULL);
    + else if (starts_with (tag_first_char, 'p')) rss_parser.start_element (
    + NULL, "pubDate", NULL, NULL, w, NULL);
    + else if (starts_with (tag_first_char, 'd')) rss_parser.start_element (
    + NULL, "description", NULL, NULL, w, NULL);
    + else if (starts_with (tag_first_char, 'c')) rss_parser.start_element (
    + NULL, "comments", NULL, NULL, w, NULL);
    + else if (starts_with (tag_first_char, '/')) {
    + *next_tag = '\0';
    + rss_parser.text (NULL, end_prev_tag, -1, w, NULL);
    +
    + if (starts_with (tag_second_char, 't'))
    + rss_parser.end_element (NULL, "title", w, NULL);
    + else if (starts_with (tag_second_char, 'l'))
    + rss_parser.end_element (NULL, "link", w, NULL);
    + else if (starts_with (tag_second_char, 'p'))
    + rss_parser.end_element (NULL, "pubDate", w, NULL);
    + else if (starts_with (tag_second_char, 'd'))
    + rss_parser.end_element (NULL, "description", w, NULL);
    + else if (starts_with (tag_second_char, 'c'))
    + rss_parser.end_element (NULL, "comments", w, NULL);
    + else if (starts_with (tag_second_char, 'i')) {
    + rss_parser.end_element (NULL, "item", w, NULL);
    + is_item = FALSE;
    + } else {
    + // TODO: WARN USER IN THIS CASE
    + }
    + }
    + } else {
    + if (starts_with (tag_first_char, 'i') &&
    + starts_with (tag_second_char, 't')) {
    + is_item = TRUE;
    + rss_parser.start_element (NULL, "item", NULL, NULL, w, NULL);
    + }
    + }
    +
    + if ((next_tag = find_end_of_tag (tag_first_char)) == NULL) {
    + // TODO: NOTIFY USER THAT WE REACHED END
    + return;
    + }
    + end_prev_tag = g_utf8_next_char (next_tag);
    + }
    +
    + free (convert_char);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_textfile.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,268 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "component.h"
    +
    +#include <string.h>
    +
    +/*---------- TEXT FILE: Text from a file ----------*/
    +static GtkWidget *file_entry;
    +static GtkWidget *file_selector;
    +
    +/* Read file into string and return */
    +char *text_file_generate (struct widget *w)
    +{
    + gchar *text, *salvaged, *converted;
    + const gchar *filename;
    + int max = ap_prefs_get_int (w, "text_size");
    +
    + text = NULL;
    + filename = ap_prefs_get_string (w, "text_file");
    +
    + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
    + return g_strdup (_("[ERROR: File does not exist]"));
    + }
    +
    + if (!g_file_get_contents (filename, &text, NULL, NULL)) {
    + return g_strdup (_("[ERROR: Unable to open file]"));
    + }
    +
    + converted = purple_utf8_try_convert (text);
    + if (converted != NULL) {
    + g_free (text);
    + text = converted;
    + }
    +
    + if (strlen (text) > max) {
    + *(text+max) = '\0';
    + }
    +
    + salvaged = purple_utf8_salvage (text);
    + g_free (text);
    +
    + return salvaged;
    +}
    +
    +static gboolean text_file_update (GtkWidget *widget, GdkEventFocus *evt,
    + struct widget *w)
    +{
    + ap_prefs_set_string (w, "text_file",
    + gtk_entry_get_text (GTK_ENTRY (file_entry)));
    + return FALSE;
    +}
    +
    +static void text_file_filename (GtkWidget *widget, gpointer user_data) {
    + const gchar *selected_filename;
    + struct widget *w = (struct widget *) user_data;
    +
    + selected_filename = gtk_file_selection_get_filename (
    + GTK_FILE_SELECTION (file_selector));
    +
    + ap_prefs_set_string (w, "text_file", selected_filename);
    + gtk_entry_set_text (GTK_ENTRY (file_entry), selected_filename);
    +}
    +
    +/* Creates and pops up file selection dialog for fortune file */
    +static void text_file_selection (GtkWidget *widget, gpointer user_data) {
    + const char *cur_file;
    + struct widget *w = (struct widget *) user_data;
    +
    + /* Create the selector */
    + file_selector = gtk_file_selection_new (
    + "Select a text file with content");
    +
    + cur_file = ap_prefs_get_string (w, "text_file");
    + if (cur_file && strlen (cur_file) > 1) {
    + gtk_file_selection_set_filename (
    + GTK_FILE_SELECTION (file_selector), cur_file);
    + }
    +
    + g_signal_connect (GTK_OBJECT(
    + GTK_FILE_SELECTION(file_selector)->ok_button),
    + "clicked", G_CALLBACK (text_file_filename), w);
    +
    + /* Destroy dialog box when the user clicks button. */
    + g_signal_connect_swapped (GTK_OBJECT(
    + GTK_FILE_SELECTION(file_selector)->ok_button),
    + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector);
    +
    + g_signal_connect_swapped (GTK_OBJECT (
    + GTK_FILE_SELECTION (file_selector)->cancel_button),
    + "clicked", G_CALLBACK (gtk_widget_destroy), (gpointer) file_selector);
    +
    + /* Display dialog */
    + gtk_widget_show (file_selector);
    +}
    +
    +/* Pop up message with instructions */
    +static void text_file_info_button (GtkButton *button, gpointer data)
    +{
    + if (!strcmp ((char *) data, "itunes")) {
    + purple_notify_formatted (NULL, _("iTunes"), _("Current song in iTunes"), NULL,
    + _("Get TuneCam from <a href=\""
    + "http://www.soft-o-mat.com/productions.shtml\">"
    + "http://www.soft-o-mat.com/productions.shtml</a> and start it.<br>"
    + "Create a html file that contains the following text:<br><br>&lt;tc"
    + "&gt;artist&lt;/tc&gt; - &lt;tc&gt;title&lt;/tc&gt;<br><br>and "
    + "press the \"T\" button. Import the html file as a template for "
    + "the \"File Track\" and whatever else you see fit. Then select "
    + "the \"G\" button and choose the location of the output file which "
    + "will be used in this component"),
    + NULL, NULL);
    + } else if (!strcmp ((char *) data, "xmms")) {
    + purple_notify_formatted (NULL, _("XMMS"), _("Current song in XMMS"), NULL,
    + _("Included in the misc folder of AutoProfile is a script called \""
    + "xmms_currenttrack\". Install this script in your $PATH and give it "
    + "executable permissions, and specify the program using a pipe.<br><br>"
    + "Alternatively, in XMMS, go to Options->Preferences->Effects/General "
    + "Plugins.<br>Configure the \"Song Change\" plugin. In the song change"
    + " command box, put<br><br>echo \"%s\" > /path/to/output/file<br><br>"
    + "and be sure to enable the plugin. Select the file location in "
    + "AutoProfile and you should be done"),
    + NULL, NULL);
    + } else if (!strcmp ((char *) data, "wmp")) {
    + purple_notify_formatted (NULL, _("Windows Media Player"),
    + _("Current song in Windows Media Player"), NULL,
    + _("Download NowPlaying, a plugin for WMP from <a href=\""
    + "http://www.wmplugins.com/ItemDetail.aspx?ItemID=357\">"
    + "http://www.wmplugins.com/ItemDetail.aspx?ItemID=357</a> and follow "
    + "the included installation instructions.<br>Set the output filename "
    + "to the file you choose in this component"),
    + NULL, NULL);
    + } else if (!strcmp ((char *) data, "amip")) {
    + purple_notify_formatted (NULL, _("iTunes/Winamp/Foobar/Apollo/QCD"),
    + _("Current song in iTunes/Winamp/Foobar/Apollo/QCD"), NULL,
    + _("Get the version of AMIP associated with your player from <a href=\""
    + "http://amip.tools-for.net/\">"
    + "http://amip.tools-for.net/</a> and install/"
    + "enable it.<br>"
    + "Check the box \"Write song info to file\", play with the settings, "
    + "and set the file in this component to be the file in the AMIP "
    + "options."),
    + NULL, NULL);
    + }
    +}
    +
    +/* Create the menu */
    +GtkWidget *text_file_menu (struct widget *w)
    +{
    + GtkWidget *ret = gtk_vbox_new (FALSE, 5);
    + GtkWidget *hbox, *label, *button;
    +
    + label = gtk_label_new (_("Select text file with source content"));
    + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    + /* Text entry to type in file name */
    + file_entry = gtk_entry_new ();
    + gtk_box_pack_start (GTK_BOX (hbox), file_entry, FALSE, FALSE, 0);
    + gtk_entry_set_text (GTK_ENTRY (file_entry),
    + ap_prefs_get_string (w, "text_file"));
    + g_signal_connect (G_OBJECT (file_entry), "focus-out-event",
    + G_CALLBACK (text_file_update), w);
    + /* Button to bring up file select dialog */
    + button = gtk_button_new_with_label ("Browse ...");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (text_file_selection), w);
    +
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    +
    + ap_prefs_labeled_spin_button (w, ret,
    + _("Max characters to read from file:"), "text_size",
    + 1, AP_SIZE_MAXIMUM - 1, NULL);
    +
    + gtk_box_pack_start (GTK_BOX (ret),
    + gtk_hseparator_new (), 0, 0, 0);
    +
    + /* Windows */
    + label = gtk_label_new (_("Windows users: Play the current song in:"));
    + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label ("iTunes/Winamp/Foobar/Apollo/QCD");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (text_file_info_button), "amip");
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label ("Windows Media Player");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (text_file_info_button), "wmp");
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + /* *nix */
    + label = gtk_label_new (_("*nix users: Play the current song in:"));
    + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label ("XMMS");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (text_file_info_button), "xmms");
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    +
    + /* OS X */
    + label = gtk_label_new (_("OS X users: Play the current song in:"));
    + gtk_box_pack_start (GTK_BOX (ret), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (ret), hbox, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_with_label ("iTunes");
    + g_signal_connect (G_OBJECT (button), "clicked",
    + G_CALLBACK (text_file_info_button), "itunes");
    + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    +
    + return ret;
    +}
    +
    +void text_file_init (struct widget *w) {
    + ap_prefs_add_string (w, "text_file", "");
    + ap_prefs_add_int (w, "text_size", AP_SIZE_MAXIMUM - 1);
    +}
    +
    +struct component text =
    +{
    + N_("Text File / Songs"),
    + N_("Copies text from file that external programs "
    + "(e.g. XMMS, Winamp, iTunes) can modify on a regular basis"),
    + "File",
    + &text_file_generate,
    + &text_file_init,
    + NULL,
    + NULL,
    + NULL,
    + &text_file_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_timestamp.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,142 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "component.h"
    +#include "utility.h"
    +
    +#include "gtkimhtml.h"
    +
    +/*---------- TIMESTAMP: Display time at creation ---------------------------*/
    +static char *timestamp_generate (struct widget *w) {
    + struct tm *cur_time;
    + char *ret;
    +
    + time_t *general_time = (time_t *)malloc(sizeof(time_t));
    + time (general_time);
    + cur_time = ap_localtime (general_time);
    + free (general_time);
    +
    + ret = (char *)malloc (AP_SIZE_MAXIMUM);
    + *ret = '\0';
    +
    + strftime (ret, AP_SIZE_MAXIMUM - 1,
    + ap_prefs_get_string (w, "timestamp_format"),
    + cur_time);
    +
    + free (cur_time);
    + return ret;
    +}
    +
    +static void timestamp_init (struct widget *w) {
    + ap_prefs_add_string (w, "timestamp_format",
    + "Automatically created at %I:%M %p");
    +}
    +
    +static GtkWidget *entry;
    +
    +static void event_cb (GtkWidget *widget, struct widget *w)
    +{
    + ap_prefs_set_string (w, "timestamp_format",
    + gtk_imhtml_get_markup (GTK_IMHTML(entry)));
    +}
    +
    +static void formatting_toggle_cb (GtkIMHtml *imhtml,
    + GtkIMHtmlButtons buttons, struct widget *w)
    +{
    + ap_prefs_set_string (w, "timestamp_format",
    + gtk_imhtml_get_markup (GTK_IMHTML(entry)));
    +
    +}
    +
    +static void formatting_clear_cb (GtkIMHtml *imhtml,
    + struct widget *w)
    +{
    + ap_prefs_set_string (w, "timestamp_format",
    + gtk_imhtml_get_markup (GTK_IMHTML(entry)));
    +}
    +
    +
    +static GtkWidget *timestamp_menu (struct widget *w)
    +{
    + GtkWidget *ret = gtk_vbox_new (FALSE, 5);
    + GtkWidget *label, *sw;
    +
    + GtkWidget *entry_window, *toolbar;
    + GtkTextBuffer *text_buffer;
    +
    + entry_window = pidgin_create_imhtml (TRUE, &entry, &toolbar, &sw);
    + gtk_box_pack_start (GTK_BOX (ret), entry_window, FALSE, FALSE, 0); gtk_imhtml_append_text_with_images (GTK_IMHTML(entry),
    + ap_prefs_get_string (w, "timestamp_format"),
    + 0, NULL);
    + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry));
    + g_signal_connect (G_OBJECT (text_buffer), "changed",
    + G_CALLBACK (event_cb), w);
    + g_signal_connect_after(G_OBJECT(entry), "format_function_toggle",
    + G_CALLBACK(formatting_toggle_cb), w);
    + g_signal_connect_after(G_OBJECT(entry), "format_function_clear",
    + G_CALLBACK(formatting_clear_cb), w);
    +
    + label = gtk_label_new (_(
    + "Insert the following characters where time is to be displayed:\n\n"
    + "%H\thour (24-hour clock)\n"
    + "%I\thour (12-hour clock)\n"
    + "%p\tAM or PM\n"
    + "%M\tminute\n"
    + "%S\tsecond\n"
    + "%a\tabbreviated weekday name\n"
    + "%A\tfull weekday name\n"
    + "%b\tabbreviated month name\n"
    + "%B\tfull month name\n"
    + "%m\tmonth (numerical)\n"
    + "%d\tday of the month\n"
    + "%j\tday of the year\n"
    + "%W\tweek number of the year\n"
    + "%w\tweekday (numerical)\n"
    + "%y\tyear without century\n"
    + "%Y\tyear with century\n"
    + "%z\ttime zone name, if any\n"
    + "%%\t%" ));
    + sw = gtk_scrolled_window_new (NULL,NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
    + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
    + gtk_box_pack_start (GTK_BOX (ret), sw, TRUE, TRUE , 0);
    + gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(sw), label);
    +
    + return ret;
    +}
    +
    +struct component timestamp =
    +{
    + N_("Timestamp"),
    + N_("Displays custom text showing when message was created"),
    + "Timestamp",
    + &timestamp_generate,
    + &timestamp_init,
    + NULL,
    + NULL,
    + NULL,
    + &timestamp_menu
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_uptime.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,100 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "component.h"
    +#include "utility.h"
    +
    +#include <string.h>
    +
    +/*---------- UPTIME: Display the computer uptime --*/
    +char *uptime_generate (struct widget *w) {
    + gboolean exec;
    + char *out, *line, *working;
    + char *p_character, *colon_character, *comma_character, *m_character;
    + GError *return_error;
    +
    + line = N_("uptime");
    +
    + exec = g_spawn_command_line_sync (line,
    + &out, NULL, NULL, &return_error);
    + /* Parse the output */
    + if (exec) {
    + /* Buffer length for safety */
    + working = (char *)malloc (strlen (out)+7+8+8+1);
    + strcpy (working, "Uptime:");
    + /* Break into minutes, hours, and everything else */
    + p_character = strchr (out, 'p');
    + m_character = strchr (p_character, 'm');
    +
    + /* Uptime format including "pm" */
    + if (m_character != NULL && m_character == p_character + 1) {
    + p_character = strchr (m_character, 'p');
    + m_character = strchr (p_character, 'm');
    + }
    +
    + /* Uptime if < 1 hour */
    + if (m_character != NULL && *(m_character+1) == 'i') {
    + *m_character = '\0';
    + p_character++;
    + strcat (working, p_character);
    + strcat (working, "minutes");
    +
    + /* General uptime */
    + } else {
    + colon_character = strchr (p_character, ':');
    + comma_character = strchr (colon_character, ',');
    + p_character++;
    + *colon_character++ = '\0';
    + *comma_character = '\0';
    + /* Yank it all together */
    + strcat (working, p_character);
    + strcat (working, " hours, ");
    + strcat (working, colon_character);
    + strcat (working, " minutes");
    + }
    +
    + free (out);
    + return working;
    + } else {
    + ap_debug ("uptime", "command failed to execute");
    + return g_strdup(_("[ERROR: failed to execute uptime command]"));
    + return NULL;
    + }
    +}
    +
    +struct component uptime =
    +{
    + N_("Uptime"),
    + N_("Show how long your computer has been running"),
    + "Uptime",
    + &uptime_generate,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/component.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,87 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "component.h"
    +
    +#include <string.h>
    +
    +static GList *components = NULL;
    +
    +static GList *get_components ();
    +
    +void ap_component_start () {
    + if (components) g_list_free (components);
    + components = get_components ();
    +
    + ap_widget_start ();
    +}
    +void ap_component_finish () {
    + ap_widget_finish ();
    +
    + g_list_free (components);
    + components = NULL;
    +}
    +
    +GList *ap_component_get_components () {
    + return components;
    +}
    +
    +struct component *ap_component_get_component (const gchar *identifier) {
    + GList *comps;
    + struct component *cur_comp;
    +
    + for (comps = components; comps != NULL; comps = comps->next) {
    + cur_comp = (struct component *) comps->data;
    +
    + if (!strcmp (cur_comp->identifier, identifier))
    + return cur_comp;
    + }
    +
    + return NULL;
    +}
    +
    +static GList *get_components ()
    +{
    + GList *ret = NULL;
    + /* Add each listed component */
    +
    + /*
    + ret = g_list_append (ret, &logstats);
    + */
    +
    + ret = g_list_append (ret, &text);
    + ret = g_list_append (ret, &quotation);
    + ret = g_list_append (ret, &rss);
    + ret = g_list_append (ret, &timestamp);
    + ret = g_list_append (ret, &http);
    + ret = g_list_append (ret, &count);
    +
    + #ifndef _WIN32
    + ret = g_list_append (ret, &executable);
    + ret = g_list_append (ret, &uptime);
    + #endif
    +
    + /* Return */
    + return ret;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/component.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,73 @@
    +/*--------------------------------------------------------------------------*
    + * 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 *
    + *--------------------------------------------------------------------------*/
    +
    +#ifndef _AP_COMPONENT_H_
    +#define _AP_COMPONENT_H_
    +
    +#include "widget.h"
    +
    +#include "sizes.h"
    +
    +#include "prefs.h"
    +#include "notify.h"
    +#include "util.h"
    +
    +#include "pidgin.h"
    +#include "gtkutils.h"
    +
    +/* A component is composed of code that generates some sort of content,
    + and a widget is a specific _instance_ of a component */
    +struct widget;
    +
    +struct component {
    + char *name;
    + char *description;
    + char *identifier;
    + char *(*generate)(struct widget *);
    + void (*init_pref)(struct widget *);
    + void (*load)(struct widget *);
    + void (*unload)(struct widget *);
    + gboolean (*has_content_changed)(struct widget *);
    + GtkWidget *(*pref_menu)(struct widget *);
    +};
    +
    +void ap_component_start ();
    +void ap_component_finish ();
    +
    +GList *ap_component_get_components ();
    +struct component *ap_component_get_component (const gchar *);
    +
    +/* TEMP
    +extern struct component logstats;
    +*/
    +
    +extern struct component count;
    +extern struct component executable;
    +extern struct component http;
    +extern struct component quotation;
    +extern struct component rss;
    +extern struct component text;
    +extern struct component timestamp;
    +extern struct component uptime;
    +
    +#endif /* _AP_COMPONENT_H_ */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/gtk_actions.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,341 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +#include "gtkimhtml.h"
    +#include "gtkimhtmltoolbar.h"
    +
    +static GtkWidget *current_profile = NULL;
    +static GtkWidget *accounts_dialog = NULL;
    +static GtkWidget *content_win = NULL;
    +
    +/*--------------------------------------------------------------------------*
    + * Accounts edit popup window *
    + *--------------------------------------------------------------------------*/
    +static void accounts_response_cb (GtkWidget *d, int response, gpointer data)
    +{
    + gtk_widget_destroy (accounts_dialog);
    + accounts_dialog = NULL;
    +}
    +
    +static void display_accounts_dialog () {
    + GtkWidget *label;
    +
    + if (accounts_dialog != NULL) {
    + gtk_window_present (GTK_WINDOW (accounts_dialog));
    + return;
    + }
    +
    + accounts_dialog = gtk_dialog_new_with_buttons (_("Edit Profile Accounts"),
    + NULL, GTK_DIALOG_NO_SEPARATOR, NULL, NULL);
    + gtk_dialog_set_has_separator (GTK_DIALOG(accounts_dialog), TRUE);
    +
    + gtk_dialog_add_button (GTK_DIALOG(accounts_dialog), GTK_STOCK_OK,
    + GTK_RESPONSE_OK);
    +
    + label = gtk_label_new ("");
    + gtk_label_set_markup (GTK_LABEL (label),
    + _("<b>No accounts currently enabled:</b> You have not yet specified\n "
    + "what accounts AutoProfile should set the profile for. Until you\n "
    + "check one of the boxes below, AutoProfile will effectively do\n "
    + "nothing."));
    + gtk_box_pack_start (GTK_BOX(GTK_DIALOG(accounts_dialog)->vbox), label,
    + FALSE, FALSE, 0);
    + gtk_box_pack_start (GTK_BOX(GTK_DIALOG(accounts_dialog)->vbox),
    + get_account_page (), TRUE, TRUE, 0);
    +
    + g_signal_connect (G_OBJECT(accounts_dialog), "response",
    + G_CALLBACK(accounts_response_cb), NULL);
    + gtk_window_set_default_size (GTK_WINDOW(accounts_dialog), 400, 450);
    + gtk_widget_show_all (accounts_dialog);
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Profile edit window *
    + *--------------------------------------------------------------------------*/
    +/* Callbacks for refreshing profile preview window */
    +static void refresh_preview (GtkWidget *preview) {
    + // TODO: See if a delay timeout is necessary here
    +
    + gchar *output, *input;
    +
    + if (preview == NULL || current_profile == NULL) return;
    +
    + gtk_imhtml_clear (GTK_IMHTML(preview));
    + input = gtk_imhtml_get_markup ((GtkIMHtml *) current_profile);
    + output = ap_generate (input, AP_SIZE_PROFILE_MAX);
    + gtk_imhtml_append_text (GTK_IMHTML(preview), output,
    + GTK_IMHTML_NO_SCROLL);
    + free (input);
    + free (output);
    +}
    +
    +static void refresh_cb (GtkWidget *widget, gpointer data)
    +{
    + refresh_preview ((GtkWidget *) data);
    +}
    +
    +static void event_cb (GtkWidget *widget, gpointer data)
    +{
    + refresh_preview ((GtkWidget *) data);
    +}
    +
    +static void formatting_toggle_cb (GtkIMHtml *imhtml,
    + GtkIMHtmlButtons buttons, gpointer data)
    +{
    + refresh_preview ((GtkWidget *) data);
    +}
    +
    +static void formatting_clear_cb (GtkIMHtml *imhtml, gpointer data)
    +{
    + refresh_preview ((GtkWidget *) data);
    +}
    +
    +static void revert_cb (GtkWidget *button, GtkWidget *imhtml)
    +{
    + gtk_imhtml_clear (GTK_IMHTML(imhtml));
    + gtk_imhtml_append_text_with_images (GTK_IMHTML(imhtml),
    + purple_prefs_get_string ("/plugins/gtk/autoprofile/profile"),
    + 0, NULL);
    +}
    +
    +static void save_cb (GtkWidget *button, GtkWidget *imhtml)
    +{
    + gchar *new_text;
    +
    + if (imhtml == NULL) return;
    +
    + new_text = gtk_imhtml_get_markup ((GtkIMHtml *) imhtml);
    + purple_prefs_set_string ("/plugins/gtk/autoprofile/profile", new_text);
    + free (new_text);
    +
    + if (NULL == purple_prefs_get_string_list (
    + "/plugins/gtk/autoprofile/profile_accounts")) {
    + // If no accounts set, ask for one!
    + display_accounts_dialog ();
    + }
    +}
    +
    +/* Profile edit window */
    +static GtkWidget *get_profile_page ()
    +{
    + GtkTreeSelection *sel;
    +
    + GtkWidget *ret;
    + GtkWidget *hbox, *vbox, *dialog_box, *preview, *edit_window;
    + GtkWidget *label, *sw, *toolbar, *bbox;
    + GtkWidget *refresh_button, *revert_button, *save_button;
    + GtkTextBuffer *text_buffer;
    +
    + ret = gtk_vbox_new (FALSE, 6);
    +
    + /* Preview window */
    + dialog_box = gtk_vbox_new (FALSE, 4);
    + gtk_container_set_border_width (GTK_CONTAINER(dialog_box), 6);
    + gtk_box_pack_start (GTK_BOX(ret), dialog_box, TRUE, TRUE, 0);
    +
    + hbox = gtk_hbox_new (FALSE, 0);
    + gtk_box_pack_start (GTK_BOX(dialog_box), hbox, FALSE, FALSE, 0);
    +
    + label = gtk_label_new ("");
    + gtk_label_set_markup (GTK_LABEL(label), _("<b>Preview</b>"));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    + refresh_button = gtk_button_new_with_label (_("Refresh"));
    + gtk_box_pack_end (GTK_BOX(hbox), refresh_button, FALSE, FALSE, 0);
    +
    + 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(dialog_box), sw, TRUE, TRUE, 0);
    +
    + preview = gtk_imhtml_new (NULL, NULL);
    + gtk_container_add (GTK_CONTAINER(sw), preview);
    + pidgin_setup_imhtml (preview);
    + gtk_imhtml_append_text (GTK_IMHTML(preview),
    + purple_prefs_get_string ("/plugins/gtk/autoprofile/profile"),
    + GTK_IMHTML_NO_SCROLL);
    +
    + /* Separator */
    + gtk_box_pack_start (GTK_BOX(ret), gtk_hseparator_new (), FALSE, FALSE, 0);
    +
    + /* Edit window */
    + dialog_box = gtk_vbox_new (FALSE, 6);
    + gtk_container_set_border_width (GTK_CONTAINER(dialog_box), 6);
    + gtk_box_pack_start (GTK_BOX(ret), dialog_box, TRUE, TRUE, 0);
    +
    + label = gtk_label_new ("");
    + gtk_label_set_markup (GTK_LABEL(label),
    + _("<b>Edit</b> (Drag widgets into profile / "
    + "Use shift+enter to insert a new line)"));
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + gtk_box_pack_start (GTK_BOX(dialog_box), label, FALSE, FALSE, 0);
    +
    + /* Widget list */
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(dialog_box), hbox, TRUE, TRUE, 0);
    +
    + vbox = gtk_vbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
    + get_widget_list (vbox, &sel);
    +
    + /* Button bar */
    + bbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
    +
    + revert_button = gtk_button_new_with_label (_("Revert"));
    + gtk_box_pack_start (GTK_BOX(bbox), revert_button, TRUE, TRUE, 0);
    + save_button = gtk_button_new_with_label (_("Save profile"));
    + gtk_box_pack_start (GTK_BOX(bbox), save_button, TRUE, TRUE, 0);
    +
    + edit_window = pidgin_create_imhtml (TRUE, &current_profile, &toolbar,
    + &sw);
    + gtk_box_pack_start (GTK_BOX(hbox), edit_window, TRUE, TRUE, 0);
    +
    + /* Finish */
    + g_signal_connect (G_OBJECT(save_button), "clicked",
    + G_CALLBACK(save_cb), current_profile);
    + g_signal_connect (G_OBJECT(revert_button), "clicked",
    + G_CALLBACK(revert_cb), current_profile);
    + g_signal_connect (G_OBJECT (refresh_button), "clicked",
    + G_CALLBACK (refresh_cb), preview);
    +
    + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (current_profile));
    + g_signal_connect (G_OBJECT (text_buffer), "changed",
    + G_CALLBACK (event_cb), preview);
    + g_signal_connect_after(G_OBJECT(current_profile), "format_function_toggle",
    + G_CALLBACK(formatting_toggle_cb), preview);
    + g_signal_connect_after(G_OBJECT(current_profile), "format_function_clear",
    + G_CALLBACK(formatting_clear_cb), preview);
    +
    + revert_cb (revert_button, current_profile);
    + refresh_cb (refresh_button, preview);
    +
    + return ret;
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * General edit window *
    + *--------------------------------------------------------------------------*/
    +static void
    +ap_edit_content_destroy (GtkWidget *button, GtkWidget *window)
    +{
    + if (content_win) {
    + gtk_widget_destroy (content_win);
    + done_with_widget_list ();
    + content_win = NULL;
    + current_profile = NULL;
    + }
    +}
    +
    +static void ap_edit_content_show ()
    +{
    + GtkWidget *vbox;
    + GtkWidget *bbox;
    + GtkWidget *notebook;
    + GtkWidget *button;
    +
    + if (content_win) {
    + gtk_window_present (GTK_WINDOW(content_win));
    + return;
    + }
    +
    + /* Create the window */
    + content_win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    + gtk_window_set_role (GTK_WINDOW(content_win), "ap_edit_content");
    + gtk_window_set_title (GTK_WINDOW(content_win), _("Edit Content"));
    + gtk_window_set_default_size (GTK_WINDOW(content_win), 700, 550);
    + gtk_container_set_border_width (GTK_CONTAINER(content_win), 6);
    +
    + g_signal_connect (G_OBJECT(content_win), "destroy",
    + G_CALLBACK(ap_edit_content_destroy), NULL);
    +
    + vbox = gtk_vbox_new (FALSE, 6);
    + gtk_container_add (GTK_CONTAINER(content_win), vbox);
    +
    + /* The notebook */
    + notebook = gtk_notebook_new ();
    + gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0);
    +
    + gtk_notebook_append_page (GTK_NOTEBOOK(notebook),
    + ap_widget_get_config_page (), gtk_label_new (_("Widgets")));
    + gtk_notebook_append_page (GTK_NOTEBOOK(notebook), get_profile_page (),
    + gtk_label_new (_("Info/profile")));
    +
    + /* The buttons to press! */
    + bbox = gtk_hbutton_box_new ();
    + gtk_box_set_spacing (GTK_BOX(bbox), PIDGIN_HIG_BOX_SPACE);
    + gtk_button_box_set_layout (GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
    + gtk_box_pack_start (GTK_BOX(vbox), bbox, FALSE, FALSE, 0);
    +
    + button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
    + g_signal_connect (G_OBJECT(button), "clicked",
    + G_CALLBACK(ap_edit_content_destroy), NULL);
    + gtk_box_pack_start (GTK_BOX(bbox), button, FALSE, FALSE, 0);
    +
    + gtk_widget_show_all (content_win);
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * The actions themselves *
    + *--------------------------------------------------------------------------*/
    +static void edit_content (PurplePluginAction *action)
    +{
    + ap_edit_content_show ();
    +}
    +
    +static void edit_preferences (PurplePluginAction *action)
    +{
    + ap_preferences_display ();
    +}
    +
    +static void make_visible (PurplePluginAction *action)
    +{
    + ap_gtk_make_visible ();
    +}
    +
    +/* Return the actions */
    +GList *actions (PurplePlugin *plugin, gpointer context)
    +{
    + PurplePluginAction *act;
    + GList *list = NULL;
    +
    + act = purple_plugin_action_new (_("Edit Content"), edit_content);
    + list = g_list_append (list, act);
    + act = purple_plugin_action_new (_("Preferences"), edit_preferences);
    + list = g_list_append (list, act);
    + act = purple_plugin_action_new (_("Show summary"), make_visible);
    + list = g_list_append (list, act);
    +
    + return list;
    +}
    +
    +void ap_actions_finish ()
    +{
    + if (content_win) ap_edit_content_destroy (NULL, NULL);
    + if (accounts_dialog) accounts_response_cb (NULL, 0, NULL);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/gtk_away_msgs.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,486 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +
    +#include "gtkdialogs.h"
    +#include "gtkimhtml.h"
    +#include "gtkprefs.h"
    +
    +/* VARIABLE DEFINITIONS */
    +static guint queue_pref_cb = 0;
    +static guint sound_pref_cb = 0;
    +static gboolean ap_previously_away = FALSE;
    +
    +/* The list containing data on generated profiles / status messages */
    +static GtkListStore *message_list = NULL;
    +
    +/* The general window */
    +static GtkWidget *dialog = NULL;
    +
    +/* Progress bars */
    +typedef struct _ap_progress_bar {
    + APUpdateType type;
    + GtkWidget *bar;
    + guint timeout;
    +} APProgressBar;
    +static GHashTable *progress_bars = NULL;
    +
    +/*--------------------------------------------------------------------------*
    + * Callback functions *
    + *--------------------------------------------------------------------------*/
    +static void hide_cb (GtkButton *button, gpointer data) {
    + gtk_widget_hide_all (dialog);
    +}
    +
    +static void queue_cb (
    + const char *name, PurplePrefType type, gconstpointer val, gpointer data)
    +{
    + ap_update_queueing ();
    +}
    +
    +static void sound_cb (
    + const char *name, PurplePrefType type, gconstpointer val, gpointer data)
    +{
    + GtkWidget *button;
    + gboolean value;
    +
    + button = (GtkWidget *) data;
    + value = purple_prefs_get_bool ("/core/sound/while_away");
    +
    + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(button), value);
    +}
    +
    +static void update_summary_visibility ()
    +{
    + const gchar *summary_pref;
    +
    + // Decide whether or not to show window
    + summary_pref = purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/show_summary");
    +
    + if (!strcmp (summary_pref, "always")) {
    + gtk_widget_show_all (dialog);
    + } else if (!strcmp (summary_pref, "away") && ap_is_currently_away ()) {
    + gtk_widget_show_all (dialog);
    + } else {
    + gtk_widget_hide_all (dialog);
    + }
    +
    + ap_previously_away = ap_is_currently_away ();
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Displayed message stuff *
    + *--------------------------------------------------------------------------*/
    +static void display_diff_msg (GtkTreeSelection *select, gpointer data)
    +{
    + GtkTreeModel *model;
    + GtkTreeIter iter;
    + const gchar *string;
    + GtkWidget *imhtml = (GtkWidget *) data;
    +
    + if (gtk_tree_selection_get_selected (select, &model, &iter))
    + {
    + gtk_tree_model_get (model, &iter, 3, &string, -1);
    + gtk_imhtml_clear (GTK_IMHTML(imhtml));
    + if (string != NULL) {
    + gtk_imhtml_append_text (GTK_IMHTML(imhtml), string,
    + GTK_IMHTML_NO_SCROLL);
    + gtk_imhtml_append_text (GTK_IMHTML(imhtml), "<BR>",
    + GTK_IMHTML_NO_SCROLL);
    + }
    + }
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Progress bar stuff *
    + *--------------------------------------------------------------------------*/
    +static APProgressBar *progress_create (APUpdateType type,
    + GtkWidget *container)
    +{
    + APProgressBar *progress_bar;
    +
    + progress_bar = (APProgressBar *) malloc (sizeof (APProgressBar));
    + progress_bar->timeout = 0;
    + progress_bar->type = type;
    + progress_bar->bar = gtk_progress_bar_new ();
    + gtk_progress_bar_set_bar_style (GTK_PROGRESS_BAR(progress_bar->bar), GTK_PROGRESS_CONTINUOUS);
    + gtk_box_pack_start (GTK_BOX(container), progress_bar->bar, FALSE, FALSE, 0);
    + if (type == AP_UPDATE_PROFILE) {
    + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar),
    + _("no updates made to profile"));
    + } else if (type == AP_UPDATE_STATUS) {
    + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar),
    + _("no updates made to status"));
    + }
    +
    + g_hash_table_insert (progress_bars, GINT_TO_POINTER(type), progress_bar);
    +
    + return progress_bar;
    +}
    +
    +static void progress_update_stop (APProgressBar *progress_bar) {
    + if (progress_bar->timeout) {
    + purple_timeout_remove (progress_bar->timeout);
    + progress_bar->timeout = 0;
    + }
    + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(progress_bar->bar), 1.0);
    + if (progress_bar->type == AP_UPDATE_PROFILE) {
    + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar),
    + _("waiting for new profile content"));
    + } else if (progress_bar->type == AP_UPDATE_STATUS) {
    + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar),
    + _("waiting for new status content"));
    + }
    +}
    +
    +#define BAH 500
    +
    +static gboolean progress_update (gpointer data) {
    + int total_milliseconds;
    + int seconds_remaining;
    + double fraction_increment;
    + double cur_fraction;
    + double result;
    + GString *text;
    + APProgressBar *progress_bar = (APProgressBar *) data;
    +
    + // Update fraction on bar
    + total_milliseconds =
    + purple_prefs_get_int ("/plugins/gtk/autoprofile/delay_update") * 1000;
    + fraction_increment = BAH / ((double) total_milliseconds);
    + cur_fraction = gtk_progress_bar_get_fraction (
    + GTK_PROGRESS_BAR(progress_bar->bar));
    + result = cur_fraction + fraction_increment;
    + if (result >= 1) {
    + progress_update_stop (progress_bar);
    + return FALSE;
    + }
    + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR(progress_bar->bar), result);
    +
    + // Update text on bar
    + seconds_remaining = (int)
    + (((double) total_milliseconds / 1000) -
    + (cur_fraction * (double) total_milliseconds / 1000));
    + text = g_string_new ("");
    + if (progress_bar->type == AP_UPDATE_PROFILE) {
    + g_string_printf (text, _("next profile update in %d seconds"),
    + seconds_remaining);
    + } else if (progress_bar->type == AP_UPDATE_STATUS) {
    + g_string_printf (text, _("next status update in %d seconds"),
    + seconds_remaining);
    + }
    + gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress_bar->bar), text->str);
    + g_string_free (text, TRUE);
    +
    + return TRUE;
    +}
    +
    +static void ap_gtk_timeout_start (APUpdateType type) {
    + APProgressBar *progress_bar;
    +
    + progress_bar = g_hash_table_lookup (progress_bars, GINT_TO_POINTER(type));
    + if (progress_bar->timeout) {
    + purple_timeout_remove (progress_bar->timeout);
    + }
    + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress_bar->bar), 0);
    + progress_bar->timeout =
    + purple_timeout_add (BAH, progress_update, progress_bar);
    + progress_update (progress_bar);
    +}
    +
    +void ap_gtk_set_progress_visible (APUpdateType type, gboolean visible)
    +{
    + APProgressBar *progress_bar;
    +
    + progress_bar = g_hash_table_lookup (progress_bars, GINT_TO_POINTER(type));
    + if (visible) gtk_widget_show (progress_bar->bar);
    + else gtk_widget_hide (progress_bar->bar);
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Create the main window *
    + *--------------------------------------------------------------------------*/
    +static void create_dialog () {
    + GtkTreeViewColumn *column;
    + GtkCellRenderer *renderer;
    + GtkTreeSelection *selection;
    + GtkWidget *message_list_view;
    +
    + GtkWidget *vbox, *vpane, *hbox, *config_vbox;
    + GtkWidget *sw, *imhtml, *msg_window, *button;
    +
    + imhtml = gtk_imhtml_new (NULL, NULL);
    +
    + /* Create main display window */
    + PIDGIN_DIALOG(dialog);
    + gtk_window_set_title (GTK_WINDOW(dialog), _("AutoProfile Summary"));
    + gtk_widget_realize (dialog);
    +
    + vbox = gtk_vbox_new (FALSE, 5);
    + gtk_container_add (GTK_CONTAINER (dialog), vbox);
    + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
    +
    + /* Set up progress bar container */
    + progress_create (AP_UPDATE_PROFILE, vbox);
    + progress_create (AP_UPDATE_STATUS, vbox);
    +
    + /* Set up list of past away messages */
    + vpane = gtk_vpaned_new ();
    + gtk_box_pack_start (GTK_BOX(vbox), vpane, TRUE, TRUE, 0);
    +
    + message_list = gtk_list_store_new (4,
    + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
    + message_list_view = gtk_tree_view_new_with_model (
    + GTK_TREE_MODEL (message_list));
    + renderer = gtk_cell_renderer_text_new ();
    +
    + column = gtk_tree_view_column_new_with_attributes (
    + _("Time"), renderer, "markup", 0, NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW (message_list_view), column);
    + gtk_tree_view_column_set_sort_column_id (column, 0);
    +
    + column = gtk_tree_view_column_new_with_attributes (
    + _("Type"), renderer, "markup", 1, NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW (message_list_view), column);
    + gtk_tree_view_column_set_sort_column_id (column, 1);
    +
    + renderer = gtk_cell_renderer_text_new ();
    + g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
    +
    + column = gtk_tree_view_column_new_with_attributes (
    + _("Text"), renderer, "markup", 2, NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW (message_list_view), column);
    + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
    +
    + sw = gtk_scrolled_window_new (NULL, NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
    + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
    + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw),
    + GTK_SHADOW_IN);
    +
    + gtk_container_add (GTK_CONTAINER (sw), message_list_view);
    + gtk_paned_add1 (GTK_PANED(vpane), sw);
    +
    + selection = gtk_tree_view_get_selection (
    + GTK_TREE_VIEW (message_list_view));
    + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
    + g_signal_connect (G_OBJECT (selection), "changed",
    + G_CALLBACK (display_diff_msg), imhtml);
    +
    + /* Set up the window to display away message in */
    + msg_window = gtk_scrolled_window_new (NULL, NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(msg_window),
    + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(msg_window),
    + GTK_SHADOW_IN);
    + gtk_paned_add2 (GTK_PANED(vpane), msg_window);
    +
    + gtk_container_add (GTK_CONTAINER(msg_window), imhtml);
    + pidgin_setup_imhtml (imhtml);
    +
    + /* Bottom area */
    + hbox = gtk_hbox_new (FALSE, 6);
    + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    +
    + config_vbox = gtk_vbox_new (FALSE, 4);
    + gtk_box_pack_start (GTK_BOX(hbox), config_vbox, TRUE, TRUE, 0);
    +
    + pidgin_prefs_checkbox (
    + _("Queue new messages while away"),
    + "/plugins/gtk/autoprofile/queue_messages_when_away",
    + config_vbox);
    +
    + button = pidgin_prefs_checkbox (
    + _("Play sounds while away"),
    + "/core/sound/while_away",
    + config_vbox);
    + sound_pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (),
    + "/core/sound/while_away", sound_cb, button);
    +
    + gtk_box_pack_start (GTK_BOX(hbox), gtk_vseparator_new (), FALSE, FALSE, 0);
    +
    + config_vbox = gtk_vbox_new (FALSE, 4);
    + gtk_box_pack_start (GTK_BOX(hbox), config_vbox, TRUE, TRUE, 0);
    +
    + ap_gtk_prefs_add_summary_option (config_vbox);
    +
    + button = gtk_button_new_with_label (_("Hide summary now"));
    + gtk_box_pack_start (GTK_BOX(config_vbox), button, FALSE, FALSE, 0);
    + g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (hide_cb), NULL);
    +
    + /* Finish up */
    + g_signal_connect (G_OBJECT(dialog), "delete-event",
    + G_CALLBACK(gtk_widget_hide_on_delete), NULL);
    + gtk_paned_set_position (GTK_PANED(vpane), 250);
    + gtk_window_set_default_size (GTK_WINDOW(dialog), 430, 430);
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * PUBLIC FUNCTIONS *
    + *--------------------------------------------------------------------------*/
    +void ap_gtk_add_message (APUpdateType update_type, APMessageType type,
    + const gchar *text)
    +{
    + GtkTreeIter iter;
    + struct tm *cur_time;
    + char *time_string, *simple_text, *s;
    + time_t *general_time;
    + gchar *type_string;
    +
    + // Create the time string
    + general_time = (time_t *) malloc (sizeof(time_t));
    + time (general_time);
    + cur_time = ap_localtime (general_time);
    + free (general_time);
    +
    + time_string = (char *) malloc (sizeof(char[32]));
    + *time_string = '\0';
    +
    + strftime (time_string, 31, "<b>%I:%M %p</b>", cur_time);
    + free (cur_time);
    +
    + // Create the type string
    + type_string = strdup("<b>Status</b>");
    + switch (type) {
    + case AP_MESSAGE_TYPE_PROFILE:
    + type_string = strdup (_("<b>User profile</b>"));
    + break;
    + case AP_MESSAGE_TYPE_AWAY:
    + type_string = strdup (_("<b>Away message</b>"));
    + break;
    + case AP_MESSAGE_TYPE_AVAILABLE:
    + type_string = strdup (_("<b>Available message</b>"));
    + break;
    + case AP_MESSAGE_TYPE_STATUS:
    + type_string = strdup (_("<b>Status message</b>"));
    + break;
    + default:
    + type_string = strdup (_("<b>Other</b>"));
    + break;
    + }
    +
    + // Simplify the text
    + if (text != NULL) {
    + simple_text = strdup (text);
    +
    + // Only show the first line
    + s = (gchar *) purple_strcasestr (simple_text, "<br>");
    + if (s != NULL) {
    + *s++ = '.';
    + *s++ = '.';
    + *s++ = '.';
    + *s = '\0';
    + }
    +
    + // Strip HTML
    + s = simple_text;
    + simple_text = purple_markup_strip_html (simple_text);
    + free (s);
    +
    + } else {
    + simple_text = NULL;
    + }
    +
    + // Add it
    + gtk_list_store_prepend (message_list, &iter);
    + gtk_list_store_set (message_list, &iter,
    + 0, time_string,
    + 1, type_string,
    + 2, simple_text,
    + 3, text,
    + -1);
    + free (type_string);
    + free (time_string);
    + if (simple_text) free (simple_text);
    +
    + // Delete if too many
    + if (gtk_tree_model_iter_nth_child
    + (GTK_TREE_MODEL(message_list), &iter, NULL,
    + AP_GTK_MAX_MESSAGES)) {
    + gtk_list_store_remove (message_list, &iter);
    + }
    +
    + // Move the timeout bar
    + ap_gtk_timeout_start (update_type);
    +
    + // Check if it needs to be visible or not
    + if (type != AP_MESSAGE_TYPE_PROFILE &&
    + ap_is_currently_away () != ap_previously_away) {
    + update_summary_visibility ();
    + }
    +}
    +
    +void ap_gtk_make_visible ()
    +{
    + gtk_widget_show_all (dialog);
    + gtk_window_present (GTK_WINDOW(dialog));
    +}
    +
    +void ap_gtk_start () {
    + progress_bars = g_hash_table_new (NULL, NULL);
    +
    + // Message queueing
    + queue_pref_cb = purple_prefs_connect_callback (
    + ap_get_plugin_handle (),
    + "/plugins/gtk/autoprofile/queue_messages_when_away", queue_cb, NULL);
    +
    + // Create window
    + create_dialog ();
    + update_summary_visibility ();
    +}
    +
    +static void ap_gtk_finish_progress_bar (APUpdateType type)
    +{
    + APProgressBar *progress_bar;
    +
    + progress_bar = g_hash_table_lookup (progress_bars, GINT_TO_POINTER(type));
    + if (progress_bar) {
    + if (progress_bar->timeout) {
    + purple_timeout_remove (progress_bar->timeout);
    + }
    + free (progress_bar);
    + g_hash_table_insert (progress_bars, GINT_TO_POINTER(type), NULL);
    + }
    +}
    +
    +void ap_gtk_finish () {
    + // Kill the window and associated variables
    + gtk_widget_destroy (dialog);
    + dialog = NULL;
    + message_list = NULL;
    +
    + ap_gtk_finish_progress_bar (AP_UPDATE_PROFILE);
    + ap_gtk_finish_progress_bar (AP_UPDATE_STATUS);
    +
    + // Disconnect queue message
    + purple_prefs_disconnect_callback (queue_pref_cb);
    + queue_pref_cb = 0;
    + purple_prefs_disconnect_callback (sound_pref_cb);
    + sound_pref_cb = 0;
    +
    + g_hash_table_destroy (progress_bars);
    + progress_bars = NULL;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/gtk_widget.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,778 @@
    +/*--------------------------------------------------------------------------*
    + * 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, 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;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/preferences.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,750 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +
    +#include "gtkimhtml.h"
    +#include "gtksavedstatuses.h"
    +#include "gtkprefs.h"
    +
    +/*--------------------------------------------------------------------------*
    + * Info Tab *
    + *--------------------------------------------------------------------------*/
    +static GtkWidget *get_info_page () {
    + GtkWidget *page;
    + GtkWidget *label;
    + GtkWidget *aboutwin;
    + GtkWidget *text;
    +
    + gchar *labeltext, *str;
    +
    + /* Make the box */
    + page = gtk_vbox_new (FALSE, 5);
    + gtk_container_set_border_width (GTK_CONTAINER (page), 5);
    +
    + /* AutoProfile title */
    + labeltext = g_strdup_printf (
    + _("<span weight=\"bold\" size=\"larger\">AutoProfile %s</span>"),
    + PP_VERSION);
    + label = gtk_label_new (NULL);
    + gtk_label_set_markup (GTK_LABEL(label), labeltext);
    + gtk_label_set_line_wrap (GTK_LABEL(label), TRUE);
    + gtk_misc_set_alignment (GTK_MISC(label), 0.5, 0);
    + gtk_box_pack_start (GTK_BOX(page), label, FALSE, FALSE, 0);
    + g_free(labeltext);
    +
    + /* Window with info */
    + aboutwin = gtk_scrolled_window_new (NULL, NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(aboutwin),
    + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    + 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),
    + _("Use the <b>Autoprofile</b> portion of the <b>Tools</b> "
    + "menu in the <b>"
    + "buddy list</b> to configure the actual content that will go in your "
    + "status messages and profiles and set options.<br><br>"),
    + GTK_IMHTML_NO_SCROLL);
    +
    + gtk_imhtml_append_text (GTK_IMHTML(text),
    + _("<u>DOCUMENTATION / HELP</u><br>"), GTK_IMHTML_NO_SCROLL);
    + gtk_imhtml_append_text (GTK_IMHTML(text),
    + _("Complete documentation can be found at:<br> <a href="
    + "\"http://hkn.eecs.berkeley.edu/~casey/autoprofile/documentation.php\">"
    + "hkn.eecs.berkeley.edu/~casey/autoprofile/documentation.php</a><br>"),
    + GTK_IMHTML_NO_SCROLL);
    +
    + gtk_imhtml_append_text (GTK_IMHTML(text),
    + _("<br><u>ABOUT</u><br>"), GTK_IMHTML_NO_SCROLL);
    +
    + str = g_strconcat(
    + "<font size=\"3\"><b>", _("Developers"), ":</b></font><br>"
    + " Casey Ho (Lead Developer)<br>"
    + " Mitchell Harwell<br>", NULL);
    + gtk_imhtml_append_text(GTK_IMHTML(text), str, GTK_IMHTML_NO_SCROLL);
    + g_free(str);
    +
    + str = g_strconcat(
    + "<font size=\"3\"><b>", _("Contributors/Patchers"), ":</b></font><br>"
    + " Onime Clement<br>"
    + " Michael Milligan<br>"
    + " Mark Painter<br>", NULL);
    + gtk_imhtml_append_text(GTK_IMHTML(text), str, GTK_IMHTML_NO_SCROLL);
    + g_free(str);
    +
    + str = g_strconcat(
    + "<font size=\"3\"><b>", _("Website"), ":</b></font><br>"
    + " <a href=\"http://autoprofile.sourceforge.net\">"
    + "autoprofile.sourceforge.net<br>", NULL);
    + gtk_imhtml_append_text(GTK_IMHTML(text), str, GTK_IMHTML_NO_SCROLL);
    + g_free(str);
    +
    + return page;
    +}
    +
    +/*----------------------------------------------------------------------------
    + * Accounts Tab
    + *--------------------------------------------------------------------------*/
    +/* PRIMARILY RIPPED FROM GAIM GTKACCOUNT.C */
    +enum
    +{
    + COLUMN_ICON,
    + COLUMN_SCREENNAME,
    + COLUMN_ENABLED,
    + COLUMN_PROTOCOL,
    + COLUMN_DATA,
    + COLUMN_PULSE_DATA,
    + NUM_COLUMNS
    +};
    +
    +typedef struct
    +{
    + PurpleAccount *account;
    + char *username;
    + char *alias;
    +} PidginAccountAddUserData;
    +
    +typedef struct
    +{
    + GtkWidget *treeview;
    +
    + GtkListStore *model;
    + GtkTreeIter drag_iter;
    +
    + GtkTreeViewColumn *screenname_col;
    +} AccountsWindow;
    +
    +static void add_account_to_liststore(PurpleAccount *, gpointer);
    +static void set_account(GtkListStore *, GtkTreeIter *, PurpleAccount *);
    +
    +static gboolean is_profile_settable (PurpleAccount *a) {
    + const gchar *id = purple_account_get_protocol_id (a);
    + if (!strcmp (id, "prpl-yahoo") ||
    + !strcmp (id, "prpl-msn") ||
    + !strcmp (id, "prpl-jabber")) {
    + return FALSE;
    + }
    +
    + return TRUE;
    +}
    +
    +static void
    +drag_data_get_cb(GtkWidget *widget, GdkDragContext *ctx,
    + GtkSelectionData *data, guint info, guint time,
    + AccountsWindow *dialog)
    +{
    + if (data->target == gdk_atom_intern("PURPLE_ACCOUNT", FALSE)) {
    + GtkTreeRowReference *ref;
    + GtkTreePath *source_row;
    + GtkTreeIter iter;
    + PurpleAccount *account = NULL;
    + 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(dialog->model), &iter, source_row);
    + gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
    + COLUMN_DATA, &val);
    +
    + dialog->drag_iter = iter;
    +
    + account = g_value_get_pointer(&val);
    +
    + gtk_selection_data_set(data, gdk_atom_intern("PURPLE_ACCOUNT", FALSE),
    + 8, (void *)&account, sizeof(account));
    +
    + gtk_tree_path_free(source_row);
    + }
    +}
    +
    +static void
    +move_account_after(GtkListStore *store, GtkTreeIter *iter,
    + GtkTreeIter *position)
    +{
    + GtkTreeIter new_iter;
    + PurpleAccount *account;
    +
    + gtk_tree_model_get(GTK_TREE_MODEL(store), iter, COLUMN_DATA, &account, -1);
    + gtk_list_store_insert_after(store, &new_iter, position);
    +
    + set_account(store, &new_iter, account);
    +
    + gtk_list_store_remove(store, iter);
    +}
    +
    +static void
    +move_account_before(GtkListStore *store, GtkTreeIter *iter,
    + GtkTreeIter *position)
    +{
    + GtkTreeIter new_iter;
    + PurpleAccount *account;
    +
    + gtk_tree_model_get(GTK_TREE_MODEL(store), iter, COLUMN_DATA, &account, -1);
    + gtk_list_store_insert_before(store, &new_iter, position);
    +
    + set_account(store, &new_iter, account);
    +
    + gtk_list_store_remove(store, iter);
    +}
    +
    +static void
    +drag_data_received_cb(GtkWidget *widget, GdkDragContext *ctx,
    + guint x, guint y, GtkSelectionData *sd,
    + guint info, guint t, AccountsWindow *dialog)
    +{
    + if (sd->target == gdk_atom_intern("PURPLE_ACCOUNT", FALSE) && sd->data) {
    + gint dest_index;
    + PurpleAccount *a = NULL;
    + GtkTreePath *path = NULL;
    + GtkTreeViewDropPosition position;
    +
    + memcpy(&a, sd->data, sizeof(a));
    +
    + if (gtk_tree_view_get_dest_row_at_pos(GTK_TREE_VIEW(widget), x, y,
    + &path, &position)) {
    +
    + GtkTreeIter iter;
    + PurpleAccount *account;
    + GValue val = {0};
    +
    + gtk_tree_model_get_iter(GTK_TREE_MODEL(dialog->model), &iter, path);
    + gtk_tree_model_get_value(GTK_TREE_MODEL(dialog->model), &iter,
    + COLUMN_DATA, &val);
    +
    + account = g_value_get_pointer(&val);
    +
    + switch (position) {
    + case GTK_TREE_VIEW_DROP_AFTER:
    + case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
    + move_account_after(dialog->model, &dialog->drag_iter, &iter);
    + dest_index = g_list_index(purple_accounts_get_all(), account) + 1;
    + break;
    +
    + case GTK_TREE_VIEW_DROP_BEFORE:
    + case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
    + dest_index = g_list_index(purple_accounts_get_all(), account);
    + move_account_before(dialog->model, &dialog->drag_iter, &iter);
    + break;
    +
    + default:
    + return;
    + }
    +
    + purple_accounts_reorder(a, dest_index);
    + }
    + }
    +}
    +
    +static void
    +enabled_cb(GtkCellRendererToggle *renderer, gchar *path_str, gpointer data)
    +{
    + AccountsWindow *dialog = (AccountsWindow *)data;
    + PurpleAccount *account;
    + GtkTreeModel *model = GTK_TREE_MODEL(dialog->model);
    + GtkTreeIter iter;
    + gboolean enabled;
    +
    + gtk_tree_model_get_iter_from_string(model, &iter, path_str);
    + gtk_tree_model_get(model, &iter,
    + COLUMN_DATA, &account,
    + COLUMN_ENABLED, &enabled,
    + -1);
    +
    + /* Change profile settings */
    + ap_account_enable_profile (account, !enabled);
    + set_account (dialog->model, &iter, account);
    +}
    +
    +static void
    +add_columns(GtkWidget *treeview, AccountsWindow *dialog)
    +{
    + GtkCellRenderer *renderer;
    + GtkTreeViewColumn *column;
    +
    + /* Screen Name column */
    + column = gtk_tree_view_column_new();
    + gtk_tree_view_column_set_title(column, _("Screen Name"));
    + gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
    + gtk_tree_view_column_set_resizable(column, TRUE);
    +
    + /* Icon */
    + renderer = gtk_cell_renderer_pixbuf_new();
    + gtk_tree_view_column_pack_start(column, renderer, FALSE);
    + gtk_tree_view_column_add_attribute(column, renderer, "pixbuf", COLUMN_ICON);
    +
    + /* Screen Name */
    + renderer = gtk_cell_renderer_text_new();
    + gtk_tree_view_column_pack_start(column, renderer, TRUE);
    + gtk_tree_view_column_add_attribute(column, renderer,
    + "text", COLUMN_SCREENNAME);
    + dialog->screenname_col = column;
    +
    + /* Enabled */
    + renderer = gtk_cell_renderer_toggle_new();
    +
    + g_signal_connect(G_OBJECT(renderer), "toggled",
    + G_CALLBACK(enabled_cb), dialog);
    +
    + column =
    + gtk_tree_view_column_new_with_attributes(_("AutoProfile sets user info"),
    + renderer, "active", COLUMN_ENABLED, NULL);
    +
    + gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
    + gtk_tree_view_column_set_resizable(column, TRUE);
    +
    + /* Protocol name */
    + column = gtk_tree_view_column_new();
    + gtk_tree_view_column_set_title(column, _("Protocol"));
    + gtk_tree_view_insert_column(GTK_TREE_VIEW(treeview), column, -1);
    + gtk_tree_view_column_set_resizable(column, TRUE);
    +
    + renderer = gtk_cell_renderer_text_new();
    + gtk_tree_view_column_pack_start(column, renderer, TRUE);
    + gtk_tree_view_column_add_attribute(column, renderer,
    + "text", COLUMN_PROTOCOL);
    +}
    +
    +static void
    +set_account(GtkListStore *store, GtkTreeIter *iter, PurpleAccount *account)
    +{
    + GdkPixbuf *pixbuf;
    + GdkPixbuf *scale;
    +
    + scale = NULL;
    +
    + pixbuf = pidgin_create_prpl_icon(account, 0.5);
    +
    + if (pixbuf != NULL)
    + {
    + scale = gdk_pixbuf_scale_simple(pixbuf, 16, 16, GDK_INTERP_BILINEAR);
    +
    + if (purple_account_is_disconnected(account))
    + gdk_pixbuf_saturate_and_pixelate(scale, scale, 0.0, FALSE);
    + }
    +
    + gtk_list_store_set(store, iter,
    + COLUMN_ICON, scale,
    + COLUMN_SCREENNAME, purple_account_get_username(account),
    + COLUMN_ENABLED, ap_account_has_profile_enabled(account),
    + COLUMN_PROTOCOL, purple_account_get_protocol_name(account),
    + COLUMN_DATA, account,
    + -1);
    +
    + if (pixbuf != NULL) g_object_unref(G_OBJECT(pixbuf));
    + if (scale != NULL) g_object_unref(G_OBJECT(scale));
    +}
    +
    +static void
    +add_account_to_liststore(PurpleAccount *account, gpointer user_data)
    +{
    + GtkTreeIter iter;
    + AccountsWindow *dialog = (AccountsWindow *) user_data;
    +
    + if (dialog == NULL) return;
    +
    + if (!is_profile_settable (account)) return;
    +
    + gtk_list_store_append(dialog->model, &iter);
    + set_account(dialog->model, &iter, account);
    +}
    +
    +static void
    +populate_accounts_list(AccountsWindow *dialog)
    +{
    + GList *l;
    +
    + gtk_list_store_clear(dialog->model);
    +
    + for (l = purple_accounts_get_all(); l != NULL; l = l->next)
    + add_account_to_liststore((PurpleAccount *)l->data, dialog);
    + }
    +
    +#if !GTK_CHECK_VERSION(2,2,0)
    +static void
    +get_selected_helper(GtkTreeModel *model, GtkTreePath *path,
    + GtkTreeIter *iter, gpointer user_data)
    +{
    + *((gboolean *)user_data) = TRUE;
    +}
    +#endif
    +
    +static void
    +account_selected_cb(GtkTreeSelection *sel, AccountsWindow *dialog)
    +{
    + gboolean selected = FALSE;
    +
    +#if GTK_CHECK_VERSION(2,2,0)
    + selected = (gtk_tree_selection_count_selected_rows(sel) > 0);
    +#else
    + gtk_tree_selection_selected_foreach(sel, get_selected_helper, &selected);
    +#endif
    +}
    +
    +static GtkWidget *
    +create_accounts_list(AccountsWindow *dialog)
    +{
    + GtkWidget *sw;
    + GtkWidget *treeview;
    + GtkTreeSelection *sel;
    + GtkTargetEntry gte[] = {{"PURPLE_ACCOUNT", GTK_TARGET_SAME_APP, 0}};
    +
    + /* Create the scrolled window. */
    + sw = gtk_scrolled_window_new(0, 0);
    + 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_widget_show(sw);
    +
    + /* Create the list model. */
    + dialog->model = gtk_list_store_new(NUM_COLUMNS,
    + GDK_TYPE_PIXBUF, G_TYPE_STRING,
    + G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_POINTER,
    + G_TYPE_POINTER);
    +
    + /* And now the actual treeview */
    + treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(dialog->model));
    + dialog->treeview = treeview;
    + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(treeview), TRUE);
    +
    + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
    + gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
    + g_signal_connect(G_OBJECT(sel), "changed",
    + G_CALLBACK(account_selected_cb), dialog);
    +
    + gtk_container_add(GTK_CONTAINER(sw), treeview);
    + gtk_widget_show(treeview);
    +
    + add_columns(treeview, dialog);
    +
    + populate_accounts_list(dialog);
    +
    + /* Setup DND. I wanna be an orc! */
    + gtk_tree_view_enable_model_drag_source(
    + GTK_TREE_VIEW(treeview), GDK_BUTTON1_MASK, gte,
    + 1, GDK_ACTION_COPY);
    + gtk_tree_view_enable_model_drag_dest(
    + GTK_TREE_VIEW(treeview), gte, 1,
    + GDK_ACTION_COPY | GDK_ACTION_MOVE);
    +
    + g_signal_connect(G_OBJECT(treeview), "drag-data-received",
    + G_CALLBACK(drag_data_received_cb), dialog);
    + g_signal_connect(G_OBJECT(treeview), "drag-data-get",
    + G_CALLBACK(drag_data_get_cb), dialog);
    +
    + return sw;
    +}
    +
    +static void account_page_delete_cb (GtkObject *object, gpointer data)
    +{
    + g_free (data);
    +}
    +
    +GtkWidget *get_account_page () {
    + GtkWidget *page;
    + GtkWidget *sw;
    + GtkWidget *label;
    + AccountsWindow *accounts_window;
    +
    + /* Make the box */
    + page = gtk_vbox_new (FALSE, 8);
    + gtk_container_set_border_width (GTK_CONTAINER (page), 12);
    +
    + accounts_window = g_new0(AccountsWindow, 1);
    +
    + /* Setup the scrolled window that will contain the list of accounts. */
    + sw = create_accounts_list(accounts_window);
    + gtk_box_pack_start(GTK_BOX(page), sw, TRUE, TRUE, 0);
    +
    + label = gtk_label_new (
    + _("Accounts that do not support user-specified profiles are not shown"));
    + gtk_box_pack_start(GTK_BOX(page), label, FALSE, FALSE, 0);
    +
    + g_signal_connect (G_OBJECT (page), "destroy",
    + G_CALLBACK (account_page_delete_cb), accounts_window);
    +
    + return page;
    +}
    +
    +/*----------------------------------------------------------------------------
    + * Behavior Tab
    + *--------------------------------------------------------------------------*/
    +void ap_gtk_prefs_add_summary_option (GtkWidget *widget) {
    + pidgin_prefs_dropdown (widget,
    + "Show AutoProfile summary window",
    + PURPLE_PREF_STRING,
    + "/plugins/gtk/autoprofile/show_summary",
    + "Always", "always", "When away", "away", "Never", "never", NULL);
    +}
    +
    +static void
    +set_idle_away(PurpleSavedStatus *status)
    +{
    + purple_prefs_set_int("/core/savedstatus/idleaway",
    + purple_savedstatus_get_creation_time(status));
    +}
    +
    +static GtkWidget *get_behavior_page () {
    + GtkWidget *page;
    + GtkWidget *label;
    + GtkWidget *frame, *vbox, *hbox;
    + GtkWidget *button, *select, *menu;
    + GtkSizeGroup *sg;
    + gchar *markup;
    +
    + /* Make the box */
    + page = gtk_vbox_new (FALSE, 8);
    + gtk_container_set_border_width (GTK_CONTAINER (page), 12);
    +
    + /*---------- Update frequency ----------*/
    + frame = pidgin_make_frame (page, _("Update frequency"));
    + vbox = gtk_vbox_new (FALSE, 0);
    + gtk_container_add (GTK_CONTAINER (frame), vbox);
    +
    + pidgin_prefs_labeled_spin_button (vbox,
    + _("Minimum number of seconds between updates"),
    + "/plugins/gtk/autoprofile/delay_update",
    + 15, 1000, NULL);
    +
    + label = gtk_label_new ("");
    + markup = g_markup_printf_escaped ("<span style=\"italic\">%s</span>",
    + _("WARNING: Using values below 60 seconds may increase the frequency\n"
    + "of rate limiting errors"));
    + gtk_label_set_markup (GTK_LABEL (label), markup);
    + g_free (markup);
    + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0);
    +
    + /*----------- Auto-away stuff ------------*/
    + frame = pidgin_make_frame(page, _("Auto-away"));
    +
    + button = pidgin_prefs_checkbox(_("Change status when idle"),
    + "/plugins/gtk/autoprofile/away_when_idle", frame);
    +
    + sg = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
    + select = pidgin_prefs_labeled_spin_button(frame,
    + _("Minutes before changing status:"), "/core/away/mins_before_away",
    + 1, 24 * 60, sg);
    + g_signal_connect(G_OBJECT(button), "clicked",
    + G_CALLBACK(pidgin_toggle_sensitive), select);
    +
    + hbox = gtk_hbox_new(FALSE, 0);
    + gtk_container_add(GTK_CONTAINER(frame), hbox);
    +
    + label = gtk_label_new_with_mnemonic(_("Change status to:"));
    + gtk_size_group_add_widget(sg, label);
    + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    + g_signal_connect(G_OBJECT(button), "clicked",
    + G_CALLBACK(pidgin_toggle_sensitive), label);
    + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    + /* TODO: Show something useful if we don't have any saved statuses. */
    + menu = pidgin_status_menu(purple_savedstatus_get_idleaway(),
    + G_CALLBACK(set_idle_away));
    + gtk_box_pack_start(GTK_BOX(frame), menu, FALSE, FALSE, 0);
    + g_signal_connect(G_OBJECT(button), "clicked",
    + G_CALLBACK(pidgin_toggle_sensitive), menu);
    + gtk_label_set_mnemonic_widget(GTK_LABEL(label), menu);
    +
    + if (!purple_prefs_get_bool("/plugins/gtk/autoprofile/away_when_idle")) {
    + gtk_widget_set_sensitive(GTK_WIDGET(menu), FALSE);
    + gtk_widget_set_sensitive(GTK_WIDGET(select), FALSE);
    + gtk_widget_set_sensitive(GTK_WIDGET(label), FALSE);
    + }
    +
    + return page;
    +}
    +
    +/*----------------------------------------------------------------------------
    + * Auto-reply Tab
    + *--------------------------------------------------------------------------*/
    +/* Update string arguments */
    +static gboolean update_behavior_string (GtkWidget *widget, GdkEventFocus *evt,
    + gpointer data)
    +{
    + ap_debug ("preferences", "behavior string preference modified");
    +
    + if (!strcmp (data, "text_trigger")) {
    + purple_prefs_set_string ("/plugins/gtk/autoprofile/autorespond/trigger",
    + gtk_entry_get_text (GTK_ENTRY (widget)));
    + } else if (!strcmp (data, "text_respond")) {
    + purple_prefs_set_string ("/plugins/gtk/autoprofile/autorespond/text",
    + gtk_entry_get_text (GTK_ENTRY (widget)));
    + } else {
    + ap_debug_error ("preferences", "invalid data argument to string update");
    + }
    +
    + return FALSE;
    +}
    +
    +/* Update value returned from spinner for auto-respond delay */
    +static gboolean update_delay_respond (GtkWidget *widget, GdkEventFocus *evt,
    + gpointer data)
    +{
    + purple_prefs_set_int ("/plugins/gtk/autoprofile/delay_respond",
    + gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (widget)));
    + return FALSE;
    +}
    +
    +static GtkWidget *get_autoreply_page () {
    + GtkWidget *page;
    + GtkWidget *label, *checkbox, *spinner, *entry;
    + GtkWidget *frame, *vbox, *large_vbox, *hbox;
    + GtkWidget *dd;
    + GtkSizeGroup *sg;
    +
    + /* Make the box */
    + page = gtk_vbox_new (FALSE, 8);
    + gtk_container_set_border_width (GTK_CONTAINER (page), 12);
    +
    + frame = pidgin_make_frame(page, _("General"));
    +
    + dd = pidgin_prefs_dropdown(frame, _("Auto-reply:"),
    + PURPLE_PREF_STRING, "/plugins/gtk/autoprofile/autorespond/auto_reply",
    + _("Never"), "never",
    + _("When away"), "away",
    + _("When both away and idle"), "awayidle",
    + NULL);
    + sg = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
    + gtk_size_group_add_widget(sg, dd);
    + gtk_misc_set_alignment(GTK_MISC(dd), 0, 0.5);
    +
    + /*---------- Auto-responses ----------*/
    + frame = pidgin_make_frame (page, _("Dynamic auto-responses"));
    + vbox = gtk_vbox_new (FALSE, 5);
    + gtk_container_add (GTK_CONTAINER (frame), vbox);
    +
    + /* Auto-response activated */
    + checkbox = pidgin_prefs_checkbox (
    + _("Allow users to request more auto-responses"),
    + "/plugins/gtk/autoprofile/autorespond/enable", vbox);
    + large_vbox = gtk_vbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (vbox), large_vbox, FALSE, FALSE, 0);
    +
    + /* Auto-response delay */
    + hbox = gtk_hbox_new (FALSE, 5);
    + gtk_box_pack_start (GTK_BOX (large_vbox), hbox, FALSE, FALSE, 0);
    + label = gtk_label_new (_("Delay"));
    + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
    + spinner = gtk_spin_button_new_with_range (1, G_MAXINT, 1);
    + gtk_box_pack_start (GTK_BOX (hbox), spinner, TRUE, TRUE, 0);
    + label = gtk_label_new (_("seconds between auto-responses"));
    + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
    + gtk_spin_button_set_value (GTK_SPIN_BUTTON (spinner), purple_prefs_get_int (
    + "/plugins/gtk/autoprofile/autorespond/delay"));
    + g_signal_connect (G_OBJECT (spinner), "value-changed",
    + G_CALLBACK (update_delay_respond), NULL);
    +
    + /* Auto-response message string */
    + label = gtk_label_new (_("Message sent with first autoresponse:"));
    + gtk_box_pack_start (GTK_BOX (large_vbox), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + entry = gtk_entry_new ();
    + gtk_box_pack_start (GTK_BOX (large_vbox), entry, FALSE, FALSE, 0);
    + gtk_entry_set_max_length (GTK_ENTRY (entry), 100);
    + gtk_entry_set_text (GTK_ENTRY (entry), purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/autorespond/text"));
    + g_signal_connect (G_OBJECT (entry), "focus-out-event",
    + G_CALLBACK (update_behavior_string), "text_respond");
    +
    + label = gtk_label_new (_("Request trigger message:"));
    + gtk_box_pack_start (GTK_BOX (large_vbox), label, FALSE, FALSE, 0);
    + gtk_misc_set_alignment (GTK_MISC (label), 0, 0);
    + entry = gtk_entry_new ();
    + gtk_box_pack_start (GTK_BOX (large_vbox), entry, FALSE, FALSE, 0);
    + gtk_entry_set_max_length (GTK_ENTRY (entry), 50);
    + gtk_entry_set_text (GTK_ENTRY (entry), purple_prefs_get_string (
    + "/plugins/gtk/autoprofile/autorespond/trigger"));
    + g_signal_connect (G_OBJECT (entry), "focus-out-event",
    + G_CALLBACK (update_behavior_string), "text_trigger");
    +
    + /* Sensitivity signals */
    + g_signal_connect(G_OBJECT(checkbox), "clicked",
    + G_CALLBACK(pidgin_toggle_sensitive), large_vbox);
    + if (!purple_prefs_get_bool ("/plugins/gtk/autoprofile/autorespond/enable")) {
    + gtk_widget_set_sensitive (large_vbox, FALSE);
    + } else {
    + gtk_widget_set_sensitive (large_vbox, TRUE);
    + }
    +
    + return page;
    +}
    +
    +/*----------------------------------------------------------------------------
    + * Menu as a whole
    + *--------------------------------------------------------------------------*/
    +static GtkWidget *get_config_frame (PurplePlugin *plugin)
    +{
    + GtkWidget *info = get_info_page ();
    + gtk_widget_set_size_request (info, 350, 400);
    + return info;
    +}
    +
    +static void dialog_cb (GtkDialog *dialog, gint arg1, gpointer user_data)
    +{
    + gtk_widget_destroy ((GtkWidget *)dialog);
    +}
    +
    +void ap_preferences_display ()
    +{
    + GtkWidget *dialog, *notebook;
    +
    + notebook = gtk_notebook_new ();
    +
    + gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
    + get_behavior_page (), gtk_label_new (_("General")));
    + gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
    + get_account_page (), gtk_label_new (_("User info/profiles")));
    + gtk_notebook_append_page (GTK_NOTEBOOK (notebook),
    + get_autoreply_page (), gtk_label_new (_("Auto-reply")));
    +
    + g_object_set (notebook, "homogeneous", TRUE, NULL);
    +
    + dialog = gtk_dialog_new_with_buttons(PIDGIN_ALERT_TITLE, NULL,
    + GTK_DIALOG_NO_SEPARATOR,
    + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
    + NULL);
    +
    + gtk_container_add (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), notebook);
    + gtk_window_set_default_size (GTK_WINDOW(dialog), 400, 400);
    + gtk_widget_show_all (dialog);
    +
    + g_signal_connect (G_OBJECT(dialog), "response",
    + G_CALLBACK(dialog_cb), dialog);
    +}
    +
    +/*--------------- Generate the preference widget once ----------------*/
    +PidginPluginUiInfo ui_info =
    +{
    + get_config_frame
    +};
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/sizes.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,49 @@
    +/*--------------------------------------------------------------------------*
    + * 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 *
    + *--------------------------------------------------------------------------*/
    +
    +/* Size definitions (0 indicates not compatible) */
    +#define AP_SIZE_MAXIMUM 2048
    +
    +/* Profile sizes */
    +#define AP_SIZE_PROFILE_MAX 2048
    +#define AP_SIZE_PROFILE_AIM 2048
    +#define AP_SIZE_PROFILE_ICQ 0
    +#define AP_SIZE_PROFILE_JABBER 0
    +#define AP_SIZE_PROFILE_MSN 0
    +#define AP_SIZE_PROFILE_YAHOO 0
    +
    +/* Away message sizes */
    +#define AP_SIZE_AWAY_MAX 2048
    +#define AP_SIZE_AWAY_AIM 2048
    +
    +
    +/* Available message sizes */
    +#define AP_SIZE_AVAILABLE_MAX 512
    +#define AP_SIZE_AVAILABLE_AIM 60
    +#define AP_SIZE_AVAILABLE_ICQ 0
    +// jabber
    +#define AP_SIZE_AVAILABLE_MSN 0
    +#define AP_SIZE_AVAILABLE_YAHOO 512
    +
    +/* End size definitions */
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/utility.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,221 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "autoprofile.h"
    +
    +#include "debug.h"
    +
    +/* TODO: get rid of this and port to glib time */
    +static GStaticMutex time_mutex = G_STATIC_MUTEX_INIT;
    +
    +static struct tm *ap_tm_copy (const struct tm *t) {
    + struct tm *r;
    + r = (struct tm *) malloc (sizeof (struct tm));
    + r->tm_sec = t->tm_sec;
    + r->tm_min = t->tm_min;
    + r->tm_hour = t->tm_hour;
    + r->tm_mday = t->tm_mday;
    + r->tm_mon = t->tm_mon;
    + r->tm_year = t->tm_year;
    + r->tm_wday = t->tm_wday;
    + r->tm_yday = t->tm_yday;
    + r->tm_isdst = t->tm_isdst;
    + return r;
    +}
    +
    +struct tm *ap_localtime (const time_t *tp) {
    + struct tm *result;
    + g_static_mutex_lock (&time_mutex);
    + result = ap_tm_copy (localtime (tp));
    + g_static_mutex_unlock (&time_mutex);
    + return result;
    +}
    +
    +struct tm *ap_gmtime (const time_t *tp) {
    + struct tm *result;
    + g_static_mutex_lock (&time_mutex);
    + result = ap_tm_copy (gmtime (tp));
    + g_static_mutex_unlock (&time_mutex);
    + return result;
    +}
    +
    +/* Reads from fortune-style file and returns GList of each quote */
    +static void fortune_helper (GString *s, gchar *data, gboolean escape_html) {
    + if (*data == '\n') {
    + g_string_append_printf (s, "<br>");
    + return;
    + }
    +
    + if (escape_html) {
    + switch (*data) {
    + case '"': g_string_append_printf (s, "&quot;"); return;
    + case '&': g_string_append_printf (s, "&amp;"); return;
    + case '<': g_string_append_printf (s, "&lt;"); return;
    + case '>': g_string_append_printf (s, "&gt;"); return;
    + }
    + }
    +
    + g_string_append_unichar (s, g_utf8_get_char (data));
    +}
    +
    +GList *read_fortune_file (const char *filename, gboolean escape_html)
    +{
    + int state;
    + gchar *raw_data, *raw_data_start;
    + gchar *converted, *text;
    + GList *quotes = NULL;
    + GString *cur_quote;
    +
    + if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
    + return NULL;
    + }
    +
    + if (!g_file_get_contents (filename, &text, NULL, NULL)) {
    + return NULL;
    + }
    +
    + converted = purple_utf8_try_convert (text);
    + if (converted != NULL) {
    + g_free (text);
    + text = converted;
    + }
    +
    + raw_data_start = raw_data = purple_utf8_salvage (text);
    + g_free (text);
    +
    + purple_str_strip_char (raw_data, '\r');
    +
    + /* Modeling the parser as a finite state machine */
    + state = 0;
    + cur_quote = g_string_new ("");
    + while (*raw_data) {
    + switch (state) {
    + /* State after newline (potential quote) */
    + case 1:
    + if (*raw_data == '%') { // Found it
    + quotes = g_list_append (quotes, strdup (cur_quote->str));
    + g_string_truncate (cur_quote, 0);
    + state = 2;
    + } else {
    + state = 0;
    + g_string_append_printf (cur_quote, "<br>");
    + fortune_helper (cur_quote, raw_data, escape_html);
    + }
    + break;
    +
    + /* State after end of a quote */
    + case 2:
    + if (*raw_data != '\n' && *raw_data != '%') {
    + state = 0;
    + fortune_helper (cur_quote, raw_data, escape_html);
    + }
    + break;
    + /* General state */
    + default:
    + if (*raw_data == '\n') {
    + state = 1;
    + } else {
    + fortune_helper (cur_quote, raw_data, escape_html);
    + }
    + break;
    + }
    +
    + raw_data = g_utf8_next_char (raw_data);
    + }
    +
    + if (strlen (cur_quote->str) > 0) {
    + quotes = g_list_append (quotes, strdup (cur_quote->str));
    + }
    +
    + g_string_free (cur_quote, TRUE);
    + free (raw_data_start);
    + return quotes;
    +}
    +
    +/* Returns 1 if a pattern is found at the start of a string */
    +int match_start (const char *text, const char *pattern)
    +{
    + while (*pattern) {
    + if (!*text || *pattern++ != *text++)
    + return 0;
    + }
    + return 1;
    +}
    +
    +/* Free's a GList as well as the internal contents */
    +void free_string_list (GList *list)
    +{
    + GList *node = list;
    +
    + while (node) {
    + free (node->data);
    + node = node->next;
    + }
    +
    + g_list_free (list);
    +}
    +
    +/* Check if string is in GList */
    +gboolean string_list_find (GList *lst, const char *data)
    +{
    + while (lst) {
    + if (!strcmp (data, (char *) lst->data)) {
    + return TRUE;
    + }
    + lst = lst->next;
    + }
    +
    + return FALSE;
    +}
    +
    +/* Prints out debug messages with repetitive formatting completed */
    +static void auto_debug_helper (
    + PurpleDebugLevel level, const char *category, const char *message)
    +{
    + GString *s;
    +
    + if (message == NULL)
    + message = "NULL";
    +
    + s = g_string_new ("");
    + g_string_printf (s, "%s: %s\n", category, message);
    + purple_debug (level, "autoprofile", s->str);
    + g_string_free (s, TRUE);
    +}
    +
    +void ap_debug (const char *category, const char *message) {
    + auto_debug_helper (PURPLE_DEBUG_INFO, category, message);
    +}
    +
    +void ap_debug_misc (const char *category, const char *message) {
    + auto_debug_helper (PURPLE_DEBUG_MISC, category, message);
    +}
    +
    +void ap_debug_warn (const char *category, const char *message) {
    + auto_debug_helper (PURPLE_DEBUG_WARNING, category, message);
    +}
    +
    +void ap_debug_error (const char *category, const char *message) {
    + auto_debug_helper (PURPLE_DEBUG_ERROR, category, message);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/utility.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,41 @@
    +/*--------------------------------------------------------------------------*
    + * 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 *
    + *--------------------------------------------------------------------------*/
    +
    +struct tm *ap_localtime (const time_t *);
    +struct tm *ap_gmtime (const time_t *);
    +
    +GList *read_fortune_file (const char *, gboolean);
    +int match_start (const char *, const char *);
    +void free_string_list (GList *);
    +gboolean string_list_find (GList *, const char *);
    +
    +void ap_debug (const char *, const char *);
    +void ap_debug_misc (const char *, const char *);
    +void ap_debug_warn (const char *, const char *);
    +void ap_debug_error (const char *, const char *);
    +
    +/* RFC 822 Date/Time */
    +#include <time.h>
    +time_t rfc_parse_date_time (const char *data);
    +int rfc_parse_was_gmt ();
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/utility_rfc822.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,187 @@
    +/*--------------------------------------------------------------------------*
    + * 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 <time.h>
    +#include <string.h>
    +#include <ctype.h>
    +#include <stdio.h>
    +
    +static struct tm parsed_datetime;
    +static int parsed_gmttime = 0;
    +
    +/* Strip leading whitespace */
    +static const char* rfc_parse_whitespace (const char *data) {
    + while (*data && isspace (*data))
    + data++;
    + return data;
    +}
    +
    +/* Strip leading digits */
    +static const char* rfc_parse_num (const char *data) {
    + while (*data && isdigit (*data))
    + data++;
    + return data;
    +}
    +
    +/* Strip leading whitespace and digits */
    +static const char* rfc_parse_whitespace_num (const char *data) {
    + while (*data && (isspace (*data) || isdigit (*data)))
    + data++;
    + return data;
    +}
    +
    +
    +static const char* rfc_parse_date (const char *data) {
    + char month[4];
    + int day = 0;
    + int year = 0;
    + int monthnum = 0;
    +
    + sscanf (data, "%d", &day);
    + data = rfc_parse_whitespace_num (data);
    + sscanf (data, "%s", month);
    +
    + if (!strcmp (month, "Jan")) { monthnum = 0; } else
    + if (!strcmp (month, "Feb")) { monthnum = 1; } else
    + if (!strcmp (month, "Mar")) { monthnum = 2; } else
    + if (!strcmp (month, "Apr")) { monthnum = 3; } else
    + if (!strcmp (month, "May")) { monthnum = 4; } else
    + if (!strcmp (month, "Jun")) { monthnum = 5; } else
    + if (!strcmp (month, "Jul")) { monthnum = 6; } else
    + if (!strcmp (month, "Aug")) { monthnum = 7; } else
    + if (!strcmp (month, "Sep")) { monthnum = 8; } else
    + if (!strcmp (month, "Oct")) { monthnum = 9; } else
    + if (!strcmp (month, "Nov")) { monthnum = 10; } else
    + if (!strcmp (month, "Dec")) { monthnum = 11; }
    +
    + data += 3;
    + sscanf (data, "%d", &year);
    + data = rfc_parse_whitespace (data);
    + data = rfc_parse_num (data);
    +
    + if (year < 50) {
    + year += 100;
    + } else if (year > 100) {
    + year -= 1900;
    + }
    +
    + /* Set the values */
    + parsed_datetime.tm_mday = day;
    + parsed_datetime.tm_mon = monthnum;
    + parsed_datetime.tm_year = year;
    +
    + return data;
    +}
    +
    +static const char* rfc_parse_hour (const char *data) {
    + int hour = 0;
    + int minutes = 0;
    + int seconds = 0;
    +
    + sscanf (data, "%d", &hour);
    + data = strchr (data, ':');
    + sscanf (++data, "%d", &minutes);
    +
    + if (strchr (data, ':')) {
    + data = strchr (data, ':') + 1;
    + sscanf (data, "%d", &seconds);
    + data = rfc_parse_whitespace_num (data);
    + }
    +
    + parsed_datetime.tm_hour = hour;
    + parsed_datetime.tm_min = minutes;
    + parsed_datetime.tm_sec = seconds;
    +
    + return data;
    +}
    +
    +static const char *rfc_parse_zone (const char *data) {
    + if (strstr (data, "GMT"))
    + parsed_gmttime = 1;
    + else
    + parsed_gmttime = 0;
    +
    + return data;
    +}
    +
    +static const char* rfc_parse_time (const char *data) {
    + data = rfc_parse_hour (data);
    + data = rfc_parse_zone (data);
    + return data;
    +}
    +
    +static const char* rfc_parse_day (const char *data) {
    + return strchr (data, ',') + 1;
    +}
    +
    +int rfc_parse_was_gmt () {
    + return parsed_gmttime;
    +}
    +
    +time_t rfc_parse_date_time (const char *data) {
    + time_t result;
    + /* Initialize values */
    + parsed_datetime.tm_sec = 0;
    + parsed_datetime.tm_min = 0;
    + parsed_datetime.tm_hour = 0;
    + parsed_datetime.tm_mday = 0;
    + parsed_datetime.tm_mon = 0;
    + parsed_datetime.tm_year = 0;
    + parsed_datetime.tm_isdst = -1;
    +
    + data = rfc_parse_whitespace (data);
    + if (isalpha (*data)) {
    + data = rfc_parse_day (data);
    + }
    +
    + data = rfc_parse_date (data);
    + data = rfc_parse_time (data);
    +
    + result = mktime(&parsed_datetime);
    +
    + if (rfc_parse_was_gmt ())
    + result -= timezone;
    +
    + return result;
    +}
    +
    +/* DEBUGGING
    +
    +int main () {
    +
    + struct tm *x = rfc_parse_date_time ("Mon, 06 Jun 2005 20:24:18 GMT");
    +
    + printf ("Sec: %d\n", x->tm_sec);
    + printf ("Min: %d\n", x->tm_min);
    + printf ("Hour: %d\n", x->tm_hour);
    + printf ("Day: %d\n", x->tm_mday);
    + printf ("Month: %d\n", x->tm_mon);
    + printf ("Year: %d\n", x->tm_year);
    +
    + printf ("GMT: %d\n", parsed_gmttime);
    +
    + return 0;
    +}
    +
    +*/
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/widget.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,607 @@
    +/*--------------------------------------------------------------------------*
    + * 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 "../common/pp_internal.h"
    +
    +#include "widget.h"
    +#include "utility.h"
    +
    +#include <ctype.h>
    +#include <string.h>
    +
    +static GStaticMutex widget_mutex = G_STATIC_MUTEX_INIT;
    +static GList *widgets = NULL;
    +static GHashTable *identifiers = NULL;
    +static GRand *r = NULL;
    +
    +static char *widget_pref = "/plugins/gtk/autoprofile/widgets/widget_ids";
    +
    +static void ap_widget_init_default_statuses ()
    +{
    + // Make sure we don't keep on readding the default statuses if a user
    + // deleted them
    + if (!purple_prefs_exists (widget_pref)) {
    + purple_prefs_add_none ("/plugins/gtk/autoprofile/widgets/42");
    + purple_prefs_add_string (
    + "/plugins/gtk/autoprofile/widgets/42/component", "Timestamp");
    + purple_prefs_add_string (
    + "/plugins/gtk/autoprofile/widgets/42/alias", "Timestamp");
    + purple_prefs_add_string (
    + "/plugins/gtk/autoprofile/widgets/42/timestamp_format",
    + "Automatically created at %I:%M %p");
    + }
    +}
    +
    +void ap_widget_init () {
    + GList *node;
    +
    + ap_widget_init_default_statuses ();
    +
    + node = g_list_append (NULL, g_strdup ("42"));
    + purple_prefs_add_string_list (widget_pref, node);
    + free_string_list (node);
    +}
    +
    +/* Basic functions */
    +static gchar *strip_whitespace (const gchar *text) {
    + gchar *result, *end, *search;
    +
    + while (isspace (*text)) {
    + text++;
    + }
    +
    + end = NULL;
    + search = result = g_strdup (text);
    +
    + while (*search) {
    + if (end == NULL && isspace (*search)) {
    + end = search;
    + }
    + if (!isspace (*search)) {
    + end = NULL;
    + }
    + search++;
    + }
    +
    + if (end != NULL) *end = '\0';
    + return result;
    +}
    +
    +static void update_widget_ids () {
    + GList *cur_node, *ids;
    + struct widget *cur_widget;
    +
    + ids = NULL;
    + for (cur_node = widgets; cur_node != NULL; cur_node = cur_node->next) {
    + cur_widget = (struct widget *) cur_node->data;
    + ids = g_list_append (ids, cur_widget->wid);
    + }
    +
    + purple_prefs_set_string_list (widget_pref, ids);
    + g_list_free (ids);
    +}
    +
    +// Mutex is ALREADY HELD when this function is called
    +static struct widget *ap_widget_find_internal (const gchar *search_text) {
    + GList *cur_node;
    + struct widget *cur_widget;
    + gchar *alias;
    +
    + alias = strip_whitespace (search_text);
    +
    + cur_node = widgets;
    +
    + while (cur_node) {
    + cur_widget = (struct widget *) cur_node->data;
    + if (!purple_utf8_strcasecmp (alias, cur_widget->alias)) {
    + free (alias);
    + return cur_widget;
    + }
    + cur_node = cur_node->next;
    + }
    +
    + free (alias);
    + return NULL;
    +}
    +
    +struct widget *ap_widget_find (const gchar *search_text) {
    + struct widget *w;
    +
    + g_static_mutex_lock (&widget_mutex);
    + w = ap_widget_find_internal (search_text);
    + g_static_mutex_unlock (&widget_mutex);
    + return w;
    +}
    +
    +struct widget *ap_widget_find_by_identifier (const gchar *search_text) {
    + struct widget *w;
    +
    + g_static_mutex_lock (&widget_mutex);
    + w = (struct widget *) g_hash_table_lookup (identifiers, search_text);
    + g_static_mutex_unlock (&widget_mutex);
    + return w;
    +}
    +
    +void ap_widget_start () {
    + GList *widget_identifiers, *widget_identifiers_start;
    + GString *pref_name;
    + const gchar *identifier, *component_identifier;
    + struct component *comp;
    + struct widget *w;
    +
    + g_static_mutex_lock (&widget_mutex);
    +
    + r = g_rand_new ();
    +
    + widgets = NULL;
    + identifiers = g_hash_table_new (g_str_hash, g_str_equal);
    +
    + pref_name = g_string_new ("");
    + widget_identifiers_start = purple_prefs_get_string_list (widget_pref);
    +
    + for (widget_identifiers = widget_identifiers_start;
    + widget_identifiers != NULL;
    + widget_identifiers = widget_identifiers->next) {
    + g_string_printf (pref_name,
    + "/plugins/gtk/autoprofile/widgets/%s/component",
    + (gchar *) widget_identifiers->data);
    +
    + component_identifier = purple_prefs_get_string (pref_name->str);
    + if (component_identifier == NULL) {
    + ap_debug_error ("widget", "widget does not have component information");
    + continue;
    + }
    +
    + comp = ap_component_get_component (component_identifier);
    +
    + if (comp == NULL) {
    + ap_debug_error ("widget", "no component matches widget identifier");
    + continue;
    + }
    +
    + g_string_printf (pref_name,
    + "/plugins/gtk/autoprofile/widgets/%s/alias",
    + (gchar *) widget_identifiers->data);
    +
    + identifier = purple_prefs_get_string (pref_name->str);
    + if (identifier == NULL) {
    + ap_debug_error ("widget", "widget does not have alias information");
    + continue;
    + }
    +
    + w = ap_widget_find_internal (identifier);
    + if (w != NULL) {
    + ap_debug_error ("widget", "widget alias already in use");
    + continue;
    + }
    +
    + w = (struct widget *) malloc (sizeof (struct widget));
    + w->alias = g_strdup (identifier);
    + w->wid = g_strdup ((gchar *) widget_identifiers->data);
    + w->component = comp;
    + w->data = g_hash_table_new (NULL, NULL);
    +
    + widgets = g_list_append (widgets, w);
    + g_hash_table_insert (identifiers, w->wid, w);
    +
    + if (w->component->load) {
    + w->component->load (w);
    + }
    +
    + g_string_printf (pref_name,
    + "loaded saved widget with alias %s and identifier %s",
    + w->alias,
    + w->wid);
    + ap_debug_misc ("widget", pref_name->str);
    + }
    +
    + free_string_list (widget_identifiers_start);
    + g_string_free (pref_name, TRUE);
    +
    + g_static_mutex_unlock (&widget_mutex);
    +
    + ap_widget_gtk_start ();
    +}
    +
    +void ap_widget_finish () {
    + GList *tmp;
    + struct widget *w;
    +
    + g_static_mutex_lock (&widget_mutex);
    +
    + ap_widget_gtk_finish ();
    +
    + g_hash_table_destroy (identifiers);
    + identifiers = NULL;
    +
    + while (widgets) {
    + w = (struct widget *) widgets->data;
    +
    + if (w->component->unload) {
    + w->component->unload (w);
    + }
    +
    + g_hash_table_destroy (w->data);
    + free (w->alias);
    + free (w->wid);
    + free (w);
    +
    + tmp = widgets->next;
    + g_list_free_1 (widgets);
    + widgets = tmp;
    + }
    +
    + g_rand_free (r);
    + r = NULL;
    +
    + g_static_mutex_unlock (&widget_mutex);
    +}
    +
    +gboolean ap_widget_has_content_changed () {
    + GList *node;
    + struct widget *w;
    + gboolean changed = FALSE;
    +
    + g_static_mutex_lock (&widget_mutex);
    + for (node = widgets; node != NULL; node = node->next) {
    + w = (struct widget *) node->data;
    + if (w->component->has_content_changed == NULL ||
    + w->component->has_content_changed (w)) {
    + changed = TRUE;
    + break;
    + }
    + }
    +
    + g_static_mutex_unlock (&widget_mutex);
    + return changed;
    +}
    +
    +GList *ap_widget_get_widgets () {
    + GList *result;
    + g_static_mutex_lock (&widget_mutex);
    + result = g_list_copy (widgets);
    + g_static_mutex_unlock (&widget_mutex);
    + return result;
    +}
    +
    +struct widget *ap_widget_create (struct component *comp)
    +{
    + struct widget *w;
    + GString *s;
    + gchar *identifier, *alias;
    + int i;
    + GList *node;
    +
    + g_static_mutex_lock (&widget_mutex);
    +
    + // Sanity check to make sure we dont "delete" old widgets by
    + // overriding old pref
    + if (identifiers == NULL) {
    + ap_debug_warn ("widget",
    + "tried to create widget when variables unitialized");
    + g_static_mutex_unlock (&widget_mutex);
    + return NULL;
    + }
    +
    + ap_debug ("widget", "instantiating new widget from component");
    +
    + s = g_string_new ("");
    +
    + // Get alias
    + w = ap_widget_find_internal (comp->identifier);
    + alias = NULL; // Stupid compiler
    +
    + if (w == NULL) {
    + alias = g_strdup (comp->identifier);
    + } else {
    + for (i = 1; i < 10000; i++) {
    + g_string_printf (s, "%s%d", comp->identifier, i);
    + w = ap_widget_find_internal (s->str);
    + if (w == NULL) {
    + alias = g_strdup (s->str);
    + break;
    + }
    + }
    +
    + if (i == 10000) {
    + // This would happen....very very rarely...
    + ap_debug_error ("widget", "ran out of aliases for component");
    + g_string_free (s, TRUE);
    + g_static_mutex_unlock (&widget_mutex);
    + return NULL;
    + }
    + }
    +
    + // Get identifier
    + while (TRUE) {
    + i = g_rand_int (r);
    + g_string_printf (s, "%d", i);
    +
    + node = widgets;
    +
    + while (node) {
    + w = (struct widget *) node->data;
    + if (!strcmp (s->str, w->wid)) {
    + break;
    + }
    + node = node->next;
    + }
    +
    + if (node == NULL) {
    + identifier = g_strdup (s->str);
    + break;
    + }
    + }
    +
    + w = (struct widget *) malloc (sizeof (struct widget));
    + w->alias = alias;
    + w->wid = identifier;
    + w->component = comp;
    + w->data = g_hash_table_new (NULL, NULL);
    +
    + widgets = g_list_append (widgets, w);
    + g_hash_table_insert (identifiers, w->wid, w);
    +
    + // Modify Purple prefs
    + update_widget_ids ();
    +
    + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s", w->wid);
    + purple_prefs_add_none (s->str);
    +
    + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s/component",
    + w->wid);
    + purple_prefs_add_string (s->str, w->component->identifier);
    +
    + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s/alias", w->wid);
    + purple_prefs_add_string (s->str, w->alias);
    +
    + // Initialize widget
    + if (w->component->init_pref) {
    + w->component->init_pref (w);
    + }
    + if (w->component->load) {
    + w->component->load (w);
    + }
    +
    + // Cleanup
    + g_string_printf (s, "Created widget with alias %s and identifier %s",
    + alias, identifier);
    + ap_debug ("widget", s->str);
    +
    + g_string_free (s, TRUE);
    +
    +
    + g_static_mutex_unlock (&widget_mutex);
    +
    + return w;
    +}
    +
    +void ap_widget_delete (struct widget *w) {
    + GString *s;
    +
    + if (w == NULL) {
    + ap_debug_error ("widget", "attempt to delete NULL widget");
    + return;
    + }
    +
    + g_static_mutex_lock (&widget_mutex);
    +
    + // Sanity check to make sure we dont "delete" old widgets by
    + // overriding old pref
    + if (identifiers == NULL) {
    + ap_debug_warn ("widget",
    + "tried to delete widget when variables unitialized");
    + g_static_mutex_unlock (&widget_mutex);
    + return;
    + }
    +
    + s = g_string_new ("");
    +
    + g_string_printf (s, "Deleting widget with alias %s and identifier %s",
    + w->alias, w->wid);
    + ap_debug ("widget", s->str);
    +
    + widgets = g_list_remove (widgets, w);
    + g_hash_table_remove (identifiers, w->wid);
    +
    + update_widget_ids ();
    +
    + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s", w->wid);
    + purple_prefs_remove (s->str);
    +
    + g_string_free (s, TRUE);
    +
    + if (w->component->unload) {
    + w->component->unload (w);
    + }
    +
    + g_hash_table_destroy (w->data);
    + free (w->wid);
    + free (w->alias);
    + free (w);
    +
    + g_static_mutex_unlock (&widget_mutex);
    +}
    +
    +// TRUE if rename succeeds, FALSE otherwise
    +gboolean ap_widget_rename (struct widget *orig, const gchar *new_alias) {
    + struct widget *w;
    + GString *s;
    + gchar *orig_alias;
    +
    + g_static_mutex_lock (&widget_mutex);
    +
    + w = ap_widget_find_internal (new_alias);
    + if (w != NULL && w != orig) {
    + g_static_mutex_unlock (&widget_mutex);
    + return FALSE;
    + }
    +
    + orig_alias = orig->alias;
    + orig->alias = g_strdup (new_alias);
    +
    + s = g_string_new ("");
    +
    + g_string_printf (s, "/plugins/gtk/autoprofile/widgets/%s/alias", orig->wid);
    + purple_prefs_set_string (s->str, new_alias);
    +
    + g_string_printf (s, "Changed alias of widget from %s to %s",
    + orig_alias, new_alias);
    + ap_debug ("widget", s->str);
    +
    + free (orig_alias);
    + g_string_free (s, TRUE);
    +
    + g_static_mutex_unlock (&widget_mutex);
    + return TRUE;
    +}
    +
    +/* Widget data galore! */
    +void ap_widget_set_data (struct widget *w, int id, gpointer data) {
    + g_static_mutex_lock (&widget_mutex);
    + g_hash_table_insert (w->data, GINT_TO_POINTER(id), data);
    + g_static_mutex_unlock (&widget_mutex);
    +}
    +
    +gpointer ap_widget_get_data (struct widget *w, int id) {
    + gpointer result;
    +
    + g_static_mutex_lock (&widget_mutex);
    + result = g_hash_table_lookup (w->data, GINT_TO_POINTER(id));
    + g_static_mutex_unlock (&widget_mutex);
    +
    + return result;
    +}
    +
    +/* Widget preferences galore! */
    +gchar *ap_prefs_get_pref_name (struct widget *w, const char *name) {
    + GString *s;
    + gchar *result;
    +
    + s = g_string_new ("");
    + g_string_append (s, "/plugins/gtk/autoprofile/widgets/");
    + g_string_append_printf (s, "%s/%s", w->wid, name);
    +
    + result = s->str;
    + g_string_free (s, FALSE);
    + return result;
    +}
    +
    +void ap_prefs_add_bool (struct widget *w, const char *name, gboolean value) {
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_add_bool (pref, value);
    + free (pref);
    +}
    +
    +void ap_prefs_add_int (struct widget *w, const char *name, int value) {
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_add_int (pref, value);
    + free (pref);
    +}
    +
    +void ap_prefs_add_none (struct widget *w, const char *name) {
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_add_none (pref);
    + free (pref);
    +}
    +
    +void ap_prefs_add_string (struct widget *w, const char *name,
    + const char *value)
    +{
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_add_string (pref, value);
    + free (pref);
    +}
    +
    +void ap_prefs_add_string_list (struct widget *w, const char *name,
    + GList *value)
    +{
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_add_string_list (pref, value);
    + free (pref);
    +}
    +
    +gboolean ap_prefs_get_bool (struct widget *w, const char *name) {
    + gboolean result;
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + result = purple_prefs_get_bool (pref);
    + free (pref);
    + return result;
    +}
    +
    +int ap_prefs_get_int (struct widget *w, const char *name) {
    + int result;
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + result = purple_prefs_get_int (pref);
    + free (pref);
    + return result;
    +}
    +
    +const char *ap_prefs_get_string (struct widget *w, const char *name) {
    + const char *result;
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + result = purple_prefs_get_string (pref);
    + free (pref);
    + return result;
    +}
    +
    +GList *ap_prefs_get_string_list (struct widget *w, const char *name) {
    + GList *result;
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + result = purple_prefs_get_string_list (pref);
    + free (pref);
    + return result;
    +}
    +
    +void ap_prefs_set_bool (struct widget *w, const char *name, gboolean value) {
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_set_bool (pref, value);
    + free (pref);
    + ap_widget_prefs_updated (w);
    +}
    +
    +void ap_prefs_set_int (struct widget *w, const char *name, int value) {
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_set_int (pref, value);
    + free (pref);
    + ap_widget_prefs_updated (w);
    +}
    +
    +void ap_prefs_set_string (struct widget *w, const char *name,
    + const char *value)
    +{
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_set_string (pref, value);
    + free (pref);
    + ap_widget_prefs_updated (w);
    +}
    +
    +void ap_prefs_set_string_list (struct widget *w, const char *name,
    + GList *value)
    +{
    + gchar *pref = ap_prefs_get_pref_name (w, name);
    + purple_prefs_set_string_list (pref, value);
    + free (pref);
    + ap_widget_prefs_updated (w);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/widget.h Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,96 @@
    +/*--------------------------------------------------------------------------*
    + * 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 *
    + *--------------------------------------------------------------------------*/
    +
    +#ifndef _AP_WIDGET_H_
    +#define _AP_WIDGET_H_
    +
    +#include "component.h"
    +
    +#include "pidgin.h"
    +#include "gtkutils.h"
    +
    +/* The heart of everything */
    +struct widget {
    + char *wid;
    + char *alias;
    + struct component *component;
    + GHashTable *data;
    +};
    +
    +void ap_widget_init ();
    +void ap_widget_start ();
    +void ap_widget_finish ();
    +void ap_widget_gtk_start ();
    +void ap_widget_gtk_finish ();
    +
    +/* Basic functions */
    +gboolean ap_widget_has_content_changed ();
    +GList *ap_widget_get_widgets ();
    +
    +struct widget *ap_widget_find (const gchar *);
    +struct widget *ap_widget_find_by_identifier (const gchar *);
    +
    +struct widget *ap_widget_create (struct component *);
    +void ap_widget_delete (struct widget *);
    +
    +// TRUE if rename succeeds, FALSE otherwise
    +gboolean ap_widget_rename (struct widget *, const gchar *);
    +
    +/* GUI functions */
    +GtkWidget *ap_widget_get_config_page ();
    +void ap_widget_prefs_updated (struct widget *);
    +GtkWidget *get_widget_list (GtkWidget *, GtkTreeSelection **);
    +void done_with_widget_list ();
    +
    +/* Widget data galore! */
    +void ap_widget_set_data (struct widget *, int, gpointer);
    +gpointer ap_widget_get_data (struct widget *, int);
    +
    +/* Widget preferences galore! */
    +gchar *ap_prefs_get_pref_name (struct widget *, const char *);
    +GtkWidget *ap_prefs_checkbox (struct widget *, const char *, const char *,
    + GtkWidget *);
    +GtkWidget *ap_prefs_dropdown_from_list (struct widget *, GtkWidget *,
    + const gchar *, PurplePrefType, const char *, GList *);
    +GtkWidget *ap_prefs_labeled_entry (struct widget *, GtkWidget *page,
    + const gchar *, const char *, GtkSizeGroup *);
    +GtkWidget *ap_prefs_labeled_spin_button (struct widget *, GtkWidget *,
    + const gchar *, const char *, int, int, GtkSizeGroup *);
    +
    +void ap_prefs_add_bool (struct widget *, const char *name, gboolean value);
    +void ap_prefs_add_int (struct widget *, const char *name, int value);
    +void ap_prefs_add_none (struct widget *, const char *name);
    +void ap_prefs_add_string (struct widget *, const char *, const char *);
    +void ap_prefs_add_string_list (struct widget *, const char *, GList *);
    +
    +gboolean ap_prefs_get_bool (struct widget *, const char *name);
    +int ap_prefs_get_int (struct widget *, const char *name);
    +const char * ap_prefs_get_string (struct widget *, const char *name);
    +GList * ap_prefs_get_string_list (struct widget *, const char *name);
    +
    +void ap_prefs_set_bool (struct widget *, const char *name, gboolean value);
    +void ap_prefs_set_int (struct widget *, const char *name, int value);
    +void ap_prefs_set_string (struct widget *, const char *name, const char *);
    +void ap_prefs_set_string_list (struct widget *, const char *, GList *);
    +
    +#endif /* _AP_WIDGET_H_ */
    --- a/autoreply/autoreply.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/autoreply/autoreply.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Autoreply - Autoreply feature for all the protocols
    - * Copyright (C) 2005
    + * Copyright (C) 2005-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/awaynotify/awaynotify.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/awaynotify/awaynotify.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * awaynotify - show notices when status changes
    - * Copyright (C) 2005-2006 Matt Perry <guy@somewhere.fscked.org>
    + * Copyright (C) 2005-2008 Matt Perry <guy@somewhere.fscked.org>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/bash/bash.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/bash/bash.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * bash: provides a /command to display the URL for a random or specified
    --- a/bit/bit.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/bit/bit.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,5 +1,5 @@
    /*
    - * Copyright (C) 2005 Peter Lawler <bleeter from users.sf.net>
    + * Copyright (C) 2005-2008 Peter Lawler <bleeter from users.sf.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/blistops/blistops.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/blistops/blistops.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,7 @@
    /*
    * Hides the blist on signon (or when it's created)
    - * Copyright (C) 2004 Gary Kramlich.
    + * Copyright (C) 2004-2008 Gary Kramlich
    + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/chronic/chronic.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/chronic/chronic.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Chronic - Remote sound play triggering
    - * Copyright (C) 2006 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/common/Makefile.am Sun Dec 23 07:18:21 2007 -0500
    +++ b/common/Makefile.am Mon Mar 24 15:08:23 2008 -0400
    @@ -1,5 +1,5 @@
    EXTRA_DIST = \
    - core_template.c \
    glib_compat.h \
    gtk_template.c \
    + purple_template.c \
    pp_internal.h
    --- a/common/core_template.c Sun Dec 23 07:18:21 2007 -0500
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,84 +0,0 @@
    -/*
    - * Plugin Name - Summary
    - * Copyright (C) 2004
    - *
    - * 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
    - * 02111-1301, USA.
    - */
    -
    -#include "../common/pp_internal.h"
    -
    -#define PLUGIN_ID "unnamed plugin"
    -#define PLUGIN_STATIC_NAME "unnamed"
    -#define PLUGIN_AUTHOR "someone <someone@somewhere.tld>"
    -
    -/* Purple headers */
    -#include <plugin.h>
    -
    -static gboolean
    -plugin_load(PurplePlugin *plugin) {
    - return TRUE;
    -}
    -
    -static gboolean
    -plugin_unload(PurplePlugin *plugin) {
    - return TRUE;
    -}
    -
    -static PurplePluginInfo info = {
    - PURPLE_PLUGIN_MAGIC, /* Magic */
    - PURPLE_MAJOR_VERSION, /* Purple Major Version */
    - PURPLE_MINOR_VERSION, /* Purple Minor Version */
    - PURPLE_PLUGIN_STANDARD, /* plugin type */
    - NULL, /* ui requirement */
    - 0, /* flags */
    - NULL, /* dependencies */
    - PURPLE_PRIORITY_DEFAULT, /* priority */
    -
    - PLUGIN_ID, /* plugin id */
    - NULL, /* name */
    - PP_VERSION, /* version */
    - NULL, /* summary */
    - NULL, /* description */
    - PLUGIN_AUTHOR, /* author */
    - PP_WEBSITE, /* website */
    -
    - plugin_load, /* load */
    - plugin_unload, /* unload */
    - NULL, /* destroy */
    -
    - NULL, /* ui_info */
    - NULL, /* extra_info */
    - NULL, /* prefs_info */
    - NULL, /* actions */
    - NULL, /* reserved 1 */
    - NULL, /* reserved 2 */
    - NULL, /* reserved 3 */
    - NULL /* reserved 4 */
    -};
    -
    -static void
    -init_plugin(PurplePlugin *plugin) {
    -#ifdef ENABLE_NLS
    - bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    - bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    -#endif /* ENABLE_NLS */
    -
    - info.name = _("unnamed");
    - info.summary = _("summary");
    - info.description = _("description");
    -}
    -
    -PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- a/common/glib_compat.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/common/glib_compat.h Mon Mar 24 15:08:23 2008 -0400
    @@ -24,8 +24,7 @@
    #if !GLIB_CHECK_VERSION(2,2,0)
    gboolean
    -g_str_has_suffix (const gchar *str,
    - const gchar *suffix)
    +g_str_has_suffix (const gchar *str, const gchar *suffix)
    {
    int str_len;
    int suffix_len;
    @@ -43,8 +42,7 @@
    }
    gboolean
    -g_str_has_prefix (const gchar *str,
    - const gchar *prefix)
    +g_str_has_prefix (const gchar *str, const gchar *prefix)
    {
    int str_len;
    int prefix_len;
    @@ -77,9 +75,7 @@
    # define N_(String) (String)
    # endif
    static gchar **
    -g_strsplit_set (const gchar *string,
    - const gchar *delimiters,
    - gint max_tokens)
    +g_strsplit_set (const gchar *string, const gchar *delimiters, gint max_tokens)
    {
    gboolean delim_table[256];
    GSList *tokens, *list;
    --- a/common/gtk_template.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/common/gtk_template.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Plugin Name - Summary
    - * Copyright (C) 2004
    + * Copyright (C) 2004-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/common/pp_internal.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/common/pp_internal.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See AUTHORS for a list of all authors
    *
    * This program is free software; you can redistribute it and/or
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/common/purple_template.c Mon Mar 24 15:08:23 2008 -0400
    @@ -0,0 +1,84 @@
    +/*
    + * Plugin Name - Summary
    + * Copyright (C) 2004-2008
    + *
    + * 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
    + * 02111-1301, USA.
    + */
    +
    +#include "../common/pp_internal.h"
    +
    +#define PLUGIN_ID "unnamed plugin"
    +#define PLUGIN_STATIC_NAME "unnamed"
    +#define PLUGIN_AUTHOR "someone <someone@somewhere.tld>"
    +
    +/* Purple headers */
    +#include <plugin.h>
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info = {
    + PURPLE_PLUGIN_MAGIC, /* Magic */
    + PURPLE_MAJOR_VERSION, /* Purple Major Version */
    + PURPLE_MINOR_VERSION, /* Purple Minor Version */
    + PURPLE_PLUGIN_STANDARD, /* plugin type */
    + NULL, /* ui requirement */
    + 0, /* flags */
    + NULL, /* dependencies */
    + PURPLE_PRIORITY_DEFAULT, /* priority */
    +
    + PLUGIN_ID, /* plugin id */
    + NULL, /* name */
    + PP_VERSION, /* version */
    + NULL, /* summary */
    + NULL, /* description */
    + PLUGIN_AUTHOR, /* author */
    + PP_WEBSITE, /* website */
    +
    + plugin_load, /* load */
    + plugin_unload, /* unload */
    + NULL, /* destroy */
    +
    + NULL, /* ui_info */
    + NULL, /* extra_info */
    + NULL, /* prefs_info */
    + NULL, /* actions */
    + NULL, /* reserved 1 */
    + NULL, /* reserved 2 */
    + NULL, /* reserved 3 */
    + NULL /* reserved 4 */
    +};
    +
    +static void
    +init_plugin(PurplePlugin *plugin) {
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("unnamed");
    + info.summary = _("summary");
    + info.description = _("description");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- a/configure.ac Sun Dec 23 07:18:21 2007 -0500
    +++ b/configure.ac Mon Mar 24 15:08:23 2008 -0400
    @@ -1,4 +1,4 @@
    -AC_INIT([purple-plugin_pack], [2.3.0mtn], [plugins-devel@lists.guifications.org])
    +AC_INIT([purple-plugin_pack], [2.4.0mtn], [plugins-devel@lists.guifications.org])
    AC_CANONICAL_SYSTEM
    AM_CONFIG_HEADER(pre_config.h)
    @@ -209,6 +209,13 @@
    AC_SUBST(GTK_CFLAGS)
    AC_SUBST(GTK_LIBS)
    +HAVE_GNT="no"
    +GNT_CFLAGS=""
    +GNT_LIBS=""
    +PKG_CHECK_MODULES(GNT, [gnt], HAVE_GNT="yes", HAVE_GNT="no")
    +AC_SUBST(GNT_CFLAGS)
    +AC_SUBST(GNT_CFLAGS)
    +
    dnl #######################################################################
    dnl # Plugin dependency checking
    dnl #######################################################################
    @@ -310,6 +317,7 @@
    VERSION
    plugin_pack.spec
    album/Makefile
    + autoprofile/Makefile
    autoreply/Makefile
    awaynotify/Makefile
    bash/Makefile
    --- a/convbadger/convbadger.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/convbadger/convbadger.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * ConvBadger - Adds the protocol icon to the menu tray of a conversation
    - * Copyright (C) 2007 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2007-2008 Gary Kramlich <grim@reaperworld.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    @@ -139,7 +139,8 @@
    purple_signal_connect(conv_handle, "deleting-conversation", plugin,
    PURPLE_CALLBACK(convbadger_conv_destroyed_cb), NULL);
    - purple_signal_connect(conv_handle, "conversation-switched", plugin,
    + purple_signal_connect(pidgin_conversations_get_handle(),
    + "conversation-switched", plugin,
    PURPLE_CALLBACK(convbadger_conv_switched_cb), NULL);
    return TRUE;
    @@ -181,6 +182,11 @@
    NULL,
    NULL,
    NULL,
    + NULL,
    +
    + NULL,
    + NULL,
    + NULL,
    NULL
    };
    --- a/dewysiwygification/dewysiwygification.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/dewysiwygification/dewysiwygification.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * DeWYSIWYGification - Lets you type in HTML without it being escaped to entities.
    - * Copyright (C) 2004 Tim Ringenbach <omarvo@hotmail.com>
    + * Copyright (C) 2004-2008 Tim Ringenbach <omarvo@hotmail.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    @@ -107,6 +107,11 @@
    NULL,
    NULL,
    NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    NULL
    };
    --- a/dice/dice.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/dice/dice.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,8 +1,8 @@
    /*
    * Adds a command to roll an arbitrary number of dice with an arbitrary
    * number of sides
    - * Copyright (C) 2005 Gary Kramlich <grim@reaperworld.com>
    - * Modified and commented 2007-11 by Lucas <reilithion@gmail.com>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2007 Lucas <reilithion@gmail.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/difftopic/difftopic.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/difftopic/difftopic.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * DiffTopic - Show the old topic when the topic in a chat room changes.
    - * Copyright (C) 2006
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/eight_ball/eight_ball.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/eight_ball/eight_ball.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * eight_ball: Provides Magic 8-ball-like functionality
    --- a/enhancedhist/enhancedhist.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/enhancedhist/enhancedhist.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,5 +1,8 @@
    /*
    * enhanced_hist.c - Enhanced History Plugin for libpurple
    + * Copyright (C) 2004-2008 Andrew Pangborn <gaim@andrewpangborn.com>
    + * Copyright (C) 2007-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2007 Ankit Singla
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -49,6 +52,8 @@
    #define ENHANCED_HISTORY_ID "gtk-plugin_pack-enhanced_history"
    +#define PREF_ROOT_GPPATH "/plugins/gtk"
    +#define PREF_ROOT_PPATH "/plugins/gtk/plugin_pack"
    #define PREF_ROOT_PATH "/plugins/gtk/plugin_pack/enhanced_history"
    #define PREF_NUMBER_PATH "/plugins/gtk/plugin_pack/enhanced_history/number"
    #define PREF_MINS_PATH "/plugins/gtk/plugin_pack/enhanced_history/minutes"
    @@ -340,15 +345,11 @@
    {
    gboolean dates = FALSE, ims = FALSE, chats = FALSE;
    + purple_prefs_add_none(PREF_ROOT_GPPATH);
    + purple_prefs_add_none(PREF_ROOT_PPATH);
    purple_prefs_add_none(PREF_ROOT_PATH);
    if(purple_prefs_exists("/plugins/core/enhanced_history/int")) {
    - /* Rename these prefs to fit within the Plugin Pack scheme */
    - purple_prefs_rename("/plugins/core/enhanced_history/int", PREF_NUMBER_PATH);
    - purple_prefs_rename("/plugins/core/enhanced_history/mins", PREF_MINS_PATH);
    - purple_prefs_rename("/plugins/core/enhanced_history/hours", PREF_HOURS_PATH);
    - purple_prefs_rename("/plugins/core/enhanced_history/days", PREF_DAYS_PATH);
    -
    if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_date"), "no"))
    dates = TRUE;
    if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_im"), "no"))
    @@ -356,14 +357,22 @@
    if(strcmp(purple_prefs_get_string("/plugins/core/enhanced_history/string_chat"), "no"))
    chats = TRUE;
    + purple_prefs_add_int(PREF_NUMBER_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/int"));
    + purple_prefs_add_int(PREF_MINS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/mins"));
    + purple_prefs_add_int(PREF_HOURS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/hours"));
    + purple_prefs_add_int(PREF_DAYS_PATH, purple_prefs_get_int("/plugins/core/enhanced_history/days"));
    + purple_prefs_add_bool(PREF_DATES_PATH, dates);
    + purple_prefs_add_bool(PREF_IM_PATH, ims);
    + purple_prefs_add_bool(PREF_CHAT_PATH, chats);
    +
    + purple_prefs_remove("/plugins/core/enhanced_history/int");
    + purple_prefs_remove("/plugins/core/enhanced_history/mins");
    + purple_prefs_remove("/plugins/core/enhanced_history/hours");
    + purple_prefs_remove("/plugins/core/enhanced_history/days");
    purple_prefs_remove("/plugins/core/enhanced_history/string_date");
    purple_prefs_remove("/plugins/core/enhanced_history/string_im");
    purple_prefs_remove("/plugins/core/enhanced_history/string_chat");
    purple_prefs_remove("/plugins/core/enhanced_history");
    -
    - purple_prefs_add_bool(PREF_DATES_PATH, dates);
    - purple_prefs_add_bool(PREF_IM_PATH, ims);
    - purple_prefs_add_bool(PREF_CHAT_PATH, chats);
    } else {
    /* Create these prefs with sensible defaults */
    purple_prefs_add_int(PREF_NUMBER_PATH, 1);
    --- a/findip/findip.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/findip/findip.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Find IP - Find the IP of a person in the buddylist
    - * Copyright (C) 2007
    + * Copyright (C) 2007-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/gRIM/gRIM.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/gRIM/gRIM.c Mon Mar 24 15:08:23 2008 -0400
    @@ -2,7 +2,11 @@
    * A completely stupid plugin, inspired by a dumb conversation in #gaim
    * and needing some light relief from 'real' work.
    * Also as a tribute to our fearless project leader.
    - * Copyright (C) 2005 Peter Lawler <bleeter from users.sf.net>
    + *
    + * Copyright (C) 2005-2008 Peter Lawler <bleeter from users.sf.net>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2007 Ankit Singla
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/groupmsg/groupmsg.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/groupmsg/groupmsg.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * GroupMsg - Send an IM to a group of buddies
    - * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
    + * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/hideconv/hideconv.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/hideconv/hideconv.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Hide Conversations - You can hide conversations without having to close them.
    - * Copyright (C) 2007
    + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/highlight/highlight.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/highlight/highlight.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,7 @@
    /**
    * highlight.c Highlight on customized words.
    *
    - * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * 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
    --- a/ignore/ignore.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/ignore/ignore.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,7 @@
    /**
    * @file ignore.c Ignore people.
    *
    - * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * 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
    @@ -117,7 +117,7 @@
    char *split = strrchr(pref, '/');
    *split++ = '\0';
    - if (*rule != 'n') {
    + if (rule && *rule != 'n') {
    if (last == NULL || g_strcasecmp(last, pref)) {
    g_free(last);
    last = g_strdup(pref);
    --- a/infopane/infopane.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/infopane/infopane.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Infopane - Use different views for the details information in conversation windows.
    - * Copyright (C) 2007
    + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irc-more/irc-more.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irc-more/irc-more.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /**
    * @file irc-more.c A couple of additional IRC features.
    *
    - * Copyright (C) 2007 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    + * Copyright (C) 2007-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    + * Copyright (C) 2007-2008 John Bailey <rekkanoryo@rekkanoryo.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
    @@ -122,6 +123,7 @@
    return;
    }
    +#if !PURPLE_VERSION_CHECK(2,4,0)
    static PurpleCmdRet
    notice_cmd_cb(PurpleConversation *conv, const gchar *cmd, gchar **args,
    gchar **error, void *data)
    @@ -171,12 +173,13 @@
    return PURPLE_CMD_RET_OK;
    }
    +#endif
    static void
    irc_sending_text(PurpleConnection *gc, char **msg, gpointer null)
    {
    PurpleAccount *account = purple_connection_get_account(gc);
    - char **old = msg;
    + char *old = *msg;
    if (MATCHES("QUIT ")) {
    char *message = strchr(*msg, ':');
    @@ -197,8 +200,8 @@
    *version = '\0';
    *msg = g_strdup_printf("%s:\001VERSION %s\001\r\n", *msg, CTCP_REPLY);
    }
    - if (msg != old)
    - g_free(*old);
    + if (*msg != old)
    + g_free(old);
    }
    static gboolean
    @@ -218,9 +221,11 @@
    /* specify our help string and register our command */
    notice_help = _("notice target message: Send a notice to the specified target.");
    +#if !PURPLE_VERSION_CHECK(2,4,0)
    notice_cmd_id = purple_cmd_register("notice", "ws", PURPLE_CMD_P_PLUGIN,
    PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_PRPL_ONLY,
    "prpl-irc", notice_cmd_cb, notice_help, NULL);
    +#endif
    /* we need this handle for the signed-on signal */
    gc_handle = purple_connections_get_handle();
    --- a/irchelper/irchelper.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irchelper/irchelper.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,7 @@
    /*
    * IRC Helper Plugin for libpurple
    *
    - * Copyright (C) 2005-2007, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2005-2008, Richard Laager <rlaager@pidgin.im>
    * Copyright (C) 2004-2005, Mathias Hasselmann <mathias@taschenorakel.de>
    * Copyright (C) 2005, Daniel Beardsmore <uilleann@users.sf.net>
    * Copyright (C) 2005, Björn Nilsson <BNI on irc.freenode.net>
    @@ -199,7 +199,12 @@
    conv = g_new0(PurpleConversation, 1);
    conv->type = PURPLE_CONV_TYPE_IM;
    - purple_conversation_set_account(conv, account);
    + /* If we use this then the conversation updated signal is fired and
    + * other plugins might start doing things to our conversation, such as
    + * setting data on it which we would then need to free etc. It's easier
    + * just to be more hacky by setting account directly. */
    + /* purple_conversation_set_account(conv, account); */
    + conv->account = account;
    return conv;
    }
    --- a/irssi/datechange.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/datechange.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/datechange.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/datechange.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/irssi.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/irssi.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2006 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2006 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/lastlog.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/lastlog.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/lastlog.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/lastlog.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/layout.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/layout.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/layout.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/layout.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/textfmt.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/textfmt.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -103,8 +104,10 @@
    /* now let's replce the matches.
    * If we don't have any, drop out right away.
    */
    - if(regexec(&regex, iter, GROUP_TOTAL, matches, 0) != 0)
    + if(regexec(&regex, iter, GROUP_TOTAL, matches, 0) != 0) {
    + regfree(&regex);
    return message;
    + }
    /* create our GString. Heh heh */
    str = g_string_new("");
    @@ -144,6 +147,8 @@
    iter += matches[GROUP_ALL].rm_eo;
    } while(regexec(&regex, iter, GROUP_TOTAL, matches, REG_NOTBOL) == 0);
    + regfree(&regex);
    +
    /* at this point, iter is either the remains of the text, of a single null
    * terminator. So throw it onto the GString.
    */
    --- a/irssi/textfmt.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/textfmt.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/window.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/window.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/irssi/window.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/irssi/window.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,8 @@
    /*
    * irssi - Implements several irssi features for Purple
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    - * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 John Bailey <rekkanoryo@rekkanoryo.org>
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/lastseen/lastseen.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/lastseen/lastseen.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Last Seen - Record when a buddy was last seen
    - * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
    + * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/listhandler/aim_blt_files.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/aim_blt_files.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/aim_blt_files.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/aim_blt_files.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/alias_xml_files.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/alias_xml_files.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/alias_xml_files.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/alias_xml_files.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/gen_xml_files.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/gen_xml_files.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/gen_xml_files.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/gen_xml_files.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/lh_util.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/lh_util.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/lh_util.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/lh_util.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/listhandler.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/listhandler.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/listhandler.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/listhandler.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/migrate.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/migrate.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/migrate.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/migrate.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/purple_blist_xml.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/purple_blist_xml.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/listhandler/purple_blist_xml.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/listhandler/purple_blist_xml.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple Plugin Pack
    - * Copyright (C) 2003-2005
    + * Copyright (C) 2003-2008
    * See ../AUTHORS for a list of all authors
    *
    * listhandler: Provides importing, exporting, and copying functions
    --- a/m4/pluginpack.m4 Sun Dec 23 07:18:21 2007 -0500
    +++ b/m4/pluginpack.m4 Mon Mar 24 15:08:23 2008 -0400
    @@ -37,7 +37,7 @@
    continue
    fi
    - if test -f "$d/Makefile.am" -a ! "$d" = "$srcdir/common" -a ! "$d" = "$srcdir/doc" -a ! "$d" = "$srcdir/m4" -a ! -f "$d/.abusive" -a ! -f "$d/.build" -a ! -f "$d/.incomplete" ; then
    + if test -f "$d/Makefile.am" -a ! "$d" = "$srcdir/common" -a ! "$d" = "$srcdir/doc" -a ! "$d" = "$srcdir/m4" -a ! -f "$d/configure" -a ! -f "$d/.abusive" -a ! -f "$d/.build" -a ! -f "$d/.incomplete" ; then
    AC_ERROR(
    [
    *** Plugin Directory $d is misconfigured
    @@ -64,7 +64,9 @@
    fi
    PP_PURPLE="$PP_PURPLE $base"
    - elif test -f "$d/.pidgin-plugin" ; then
    + fi
    +
    + if test -f "$d/.pidgin-plugin" ; then
    if test -f "$d/.abusive" ; then
    PP_PIDGIN_ABUSIVE="$PP_PIDGIN_ABUSIVE $base"
    elif test -f "$d/.build" ; then
    @@ -76,7 +78,9 @@
    fi
    PP_PIDGIN="$PP_PIDGIN $base"
    - elif test -f "$d/.finch-plugin" ; then
    + fi
    +
    + if test -f "$d/.finch-plugin" ; then
    if test -f "$d/.abusive" ; then
    PP_FINCH_ABUSIVE="$PP_FINCH_ABUSIVE $base"
    elif test -f "$d/.build" ; then
    @@ -96,29 +100,64 @@
    dnl #######################################################################
    AC_ARG_WITH(plugins,
    AC_HELP_STRING([--with-plugins], [what plugins to build]),
    - ,with_plugins=all)
    + ,with_plugins=default)
    dnl #######################################################################
    dnl # Now determine which ones have been selected
    dnl #######################################################################
    - if test "x$with_plugins" = "xdefault" ; then
    - tmp_SUB="$PP_AVAILABLE"
    - else
    - exp_plugins=`echo "$with_plugins" | sed 's/,/ /g'`
    - for p in "$PP_AVAILABLE $PP_ABUSIVE"; do
    - for r in $exp_plugins; do
    - if test x"$r" = x"$p" ; then
    - tmp_SUB="$tmp_SUB $p"
    - fi
    + case "$with_plugins" in
    + all)
    + PP_FINCH_BUILD="$PP_FINCH_ABUSIVE $PP_FINCH_BUILD"
    + PP_PIDGIN_BUILD="$PP_PIDGIN_ABUSIVE $PP_PIDGIN_BUILD"
    + PP_PURPLE_BUILD="$PP_PURPLE_ABUSIVE $PP_PURPLE_BUILD"
    + ;;
    + default)
    + dnl # we don't do anything if the defaults are selected, they're
    + dnl # already set up :)
    + ;;
    + *)
    + dnl # clear out the build variables
    + PP_FINCH_BUILD=""
    + PP_PIDGIN_BUILD=""
    + PP_PURPLE_BUILD=""
    +
    + dnl # turn the with plugins variable into a space delimited list
    + exp_plugins=`echo "$with_plugins" | sed 's/,/ /g'`
    +
    + dnl # loop through the with plugins list and update the build variables
    + dnl # as we find the plugins in each type.
    + for w in $exp_plugins
    + do
    + for p in $PP_FINCH
    + do
    + if test x"$w" = x"$p"
    + then
    + PP_FINCH_BUILD="$PP_FINCH_BUILD $p"
    + fi
    + done
    +
    + for p in $PP_PIDGIN
    + do
    + if test x"$w" = x"$p"
    + then
    + PP_PIDGIN_BUILD="$PP_PIDGIN_BUILD $p"
    + fi
    + done
    +
    + for p in $PP_PURPLE
    + do
    + if test x"$w" = x"$p"
    + then
    + PP_PURPLE_BUILD="$PP_PURPLE_BUILD $p"
    + fi
    + done
    done
    - done
    - fi
    + esac
    - dnl # remove duplicates
    - PP_BUILD=`echo $tmp_SUB | awk '{for (i = 1; i <= NF; i++) { print $i } }' | sort | uniq | xargs echo `
    -
    - dnl # add the abusive plugins to the dist
    - PP_DIST="$PP_AVAILABLE $PP_ABUSIVE"
    + dnl # sort everything
    + PP_FINCH_BUILD=`echo $PP_FINCH_BUILD | awk '{for (i = 1; i <=NF; i++) { print $i } }' | sort | uniq | xargs echo`
    + PP_PIDGIN_BUILD=`echo $PP_PIDGIN_BUILD | awk '{for (i = 1; i <=NF; i++) { print $i } } ' | sort | uniq | xargs echo`
    + PP_PURPLE_BUILD=`echo $PP_PURPLE_BUILD | awk '{for (i = 1; i <=NF; i++) { print $i } } ' | sort | uniq | xargs echo`
    dnl #######################################################################
    dnl # substitue our sub dirs
    --- a/mystatusbox/mystatusbox.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/mystatusbox/mystatusbox.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * mystatusbox - Show/Hide the peraccount statusboxes
    - * Copyright (C) 2005
    + * Copyright (C) 2005-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    Binary file napster/16/napster.png has changed
    Binary file napster/22/napster.png has changed
    Binary file napster/48/napster.png has changed
    --- a/napster/Makefile.am Sun Dec 23 07:18:21 2007 -0500
    +++ b/napster/Makefile.am Mon Mar 24 15:08:23 2008 -0400
    @@ -1,10 +1,10 @@
    EXTRA_DIST=\
    + .build \
    .purple-plugin \
    - .incomplete \
    Makefile.mingw \
    - napster16x16.png \
    - napster22x22.png \
    - napster48x48.png
    + 16/napster.png \
    + 22/napster.png \
    + 48/napster.png
    napsterdir = $(PURPLE_LIBDIR)
    @@ -22,19 +22,14 @@
    $(PURPLE_LIBS)
    if HAVE_PIDGIN
    -napsterpix16x16dir=$(PIDGIN_PIXMAPSDIR)/protocols/16
    -napsterpix16x16_DATA=napster16x16.png
    -
    -napsterpix22x22dir=$(PIDGIN_PIXMAPSDIR)/protocols/22
    -napsterpix22x22_DATA=napster22x22.png
    +napsterpix16dir=$(PIDGIN_PIXMAPSDIR)/protocols/16
    +napsterpix16_DATA=16/napster.png
    -napsterpix48x48dir=$(PIDGIN_PIXMAPSDIR)/protocols/48
    -napsterpix48x48_DATA=napster48x48.png
    +napsterpix22dir=$(PIDGIN_PIXMAPSDIR)/protocols/22
    +napsterpix22_DATA=22/napster.png
    -install-data-hook:
    - mv $(PIDGIN_PIXMAPSDIR)/protocols/16/napster16x16.png $(PIDGIN_PIXMAPSDIR)//protocols/16/napster.png
    - mv $(PIDGIN_PIXMAPSDIR)/protocols/22/napster22x22.png $(PIDGIN_PIXMAPSDIR)//protocols/22/napster.png
    - mv $(PIDGIN_PIXMAPSDIR)/protocols/48/napster48x48.png $(PIDGIN_PIXMAPSDIR)//protocols/48/napster.png
    +napsterpix48dir=$(PIDGIN_PIXMAPSDIR)/protocols/48
    +napsterpix48_DATA=48/napster.png
    endif
    @@ -46,3 +41,5 @@
    $(DEBUG_CFLAGS) \
    $(PURPLE_CFLAGS)
    +bullshit:
    + echo $(DESTDIR)
    --- a/napster/napster.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/napster/napster.c Mon Mar 24 15:08:23 2008 -0400
    @@ -4,7 +4,8 @@
    * Copyright (C) 2000-2001, Rob Flynn <rob@marko.net>
    *
    * Assimilated for inclusion in the Plugin Pack:
    - * Copyright (C) 2006, Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2006-2008 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2007 John Bailey <rekkanoryo@rekkanoryo.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
    @@ -692,9 +693,11 @@
    NULL, /* new_xfer */
    NULL, /* offline_message */
    NULL, /* whiteboard_prpl_ops */
    - NULL, /* reserved 1 */
    - NULL, /* reserved 2 */
    - NULL, /* reserved 3 */
    + NULL, /* send_raw */
    + NULL, /* roomlist_room_serialize */
    + NULL, /* unregister_user */
    + NULL, /* send_attention */
    + NULL, /* get_attention_types */
    NULL /* reserved 4 */
    };
    Binary file napster/napster16x16.png has changed
    Binary file napster/napster22x22.png has changed
    Binary file napster/napster48x48.png has changed
    --- a/nicksaid/nicksaid.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/nicksaid/nicksaid.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Nicksaid - Record when someone said your nick in a chat.
    - * Copyright (C) 2006
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/oldlogger/oldlogger.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/oldlogger/oldlogger.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Old Logger - Re-implements the legacy, deficient, logging
    - * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
    + * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/plonkers/plonkers.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/plonkers/plonkers.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    - Purple-Plonkers - Manager the plonkers out in cyberland
    - Copyright (C) 2005 Peter Lawler
    + Purple-Plonkers - Manage the plonkers out in cyberland
    + Copyright (C) 2005-2008 Peter Lawler
    Very loosely based on gxr, Copyright (C) 2004 Gary Kramlich
    --- a/plugin_pack.spec.in Sun Dec 23 07:18:21 2007 -0500
    +++ b/plugin_pack.spec.in Mon Mar 24 15:08:23 2008 -0400
    @@ -37,6 +37,7 @@
    Summary: Plugin Pack for Pidgin
    Group: Applications/Internet
    Requires: pidgin >= %{pidgin_major_ver}.%{pidgin_build_minor_ver}, pidgin < %{pidgin_next_major_ver}
    +Requires: purple-plugin_pack = %{version}
    %description
    All the other plugins for all libpurple derived clients
    @@ -75,6 +76,9 @@
    %changelog
    +* Sat Mar 01 2008 Stu Tomlinson <stu@nosnilmot.com>
    +- make the pidgin plugin pack depend on the purple plugin pack
    +
    * Sat Oct 27 2007 Stu Tomlinson <stu@nosnilmot.com>
    - Add --without xmms option to build without xmms plugin
    --- a/po/POTFILES.in Sun Dec 23 07:18:21 2007 -0500
    +++ b/po/POTFILES.in Mon Mar 24 15:08:23 2008 -0400
    @@ -8,9 +8,7 @@
    buddytime/buddytime.c
    buddytime/gtkbuddytime.c
    chronic/chronic.c
    -common/core_template.c
    -common/core_template.c
    -common/gtk_template.c
    +common/purple_template.c
    common/gtk_template.c
    convbadger/convbadger.c
    dewysiwygification/dewysiwygification.c
    --- a/schedule/pidgin-schedule.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/schedule/pidgin-schedule.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple-Schedule - Schedule reminders at specified times.
    - * Copyright (C) 2006
    + * Copyright (C) 2006-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/schedule/schedule.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/schedule/schedule.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple-Schedule - Schedule reminders/pounces at specified times.
    - * Copyright (C) 2006
    + * Copyright (C) 2006-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/schedule/schedule.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/schedule/schedule.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple-Schedule - Schedule reminders/pounces at specified times.
    - * Copyright (C) 2006
    + * Copyright (C) 2006-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/sepandtab/sepandtab.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/sepandtab/sepandtab.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Extra conversation placement options for Purple
    - * Copyright (C) 2004 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2004-2008 Gary Kramlich <grim@reaperworld.com>
    *
    * Purple is the legal property of its developers, whose names are too numerous
    * to list here. Please refer to the COPYRIGHT file distributed with this
    --- a/showoffline/showoffline.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/showoffline/showoffline.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Show Offline - Show specific buddies while offline
    - * Copyright (C) 2004 Stu Tomlinson <stu@nosnilmot.com>
    + * Copyright (C) 2004-2008 Stu Tomlinson <stu@nosnilmot.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/simfix/simfix.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/simfix/simfix.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,7 @@
    /*
    * simfix - fix received messages from SIM clients in Purple
    *
    - * (C) Copyright 2005 Stu Tomlinson <stu@nosnilmot.com>
    + * (C) Copyright 2005-2008 Stu Tomlinson <stu@nosnilmot.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/slashexec/slashexec.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/slashexec/slashexec.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,10 +1,10 @@
    /*
    * slashexec - A CLI for libpurple clients
    - * Copyright (C) 2004-2007 Gary Kramlich
    - * Copyright (C) 2005-2007 Peter Lawler
    - * Copyright (C) 2005-2007 Daniel Atallah
    - * Copyright (C) 2005-2007 John Bailey
    - * Copyright (C) 2006-2007 Sadrul Habib Chowdhury
    + * Copyright (C) 2004-2008 Gary Kramlich
    + * Copyright (C) 2005-2008 Peter Lawler
    + * Copyright (C) 2005-2008 Daniel Atallah
    + * Copyright (C) 2005-2008 John Bailey
    + * Copyright (C) 2006-2008 Sadrul Habib Chowdhury
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/smartear/Makefile.am Sun Dec 23 07:18:21 2007 -0500
    +++ b/smartear/Makefile.am Mon Mar 24 15:08:23 2008 -0400
    @@ -1,36 +1,62 @@
    EXTRA_DIST= \
    .build \
    + .pidgin-plugin \
    .purple-plugin \
    - .pidgin-plugin \
    .finch-plugin \
    Makefile.mingw
    -smarteardir = $(PIDGIN_LIBDIR)
    -
    -smartear_la_LDFLAGS = -module -avoid-version
    if HAVE_PURPLE
    +smarteardir = $(PURPLE_LIBDIR)
    +smartear_la_LDFLAGS = -module -avoid-version
    +smartear_la_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(GLIB_CFLAGS) \
    + $(PURPLE_CFLAGS)
    +
    smartear_LTLIBRARIES = smartear.la
    smartear_la_SOURCES = smartear.c
    smartear_la_LIBADD = $(PURPLE_LIBS)
    endif
    if HAVE_PIDGIN
    +gtksmarteardir = $(PIDGIN_LIBDIR)
    +gtksmartear_la_LDFLAGS = -module -avoid-version
    +gtksmartear_la_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    +
    gtksmartear_LTLIBRARIES = gtksmartear.la
    gtksmartear_la_SOURCES = gtksmartear.c
    -gtksmartear_la_LIBADD = $(PIDGIN_LIBS)
    +gtksmartear_la_LIBADD = \
    + $(GTK_LIBS) \
    + $(PIDGIN_LIBS) \
    + $(PURPLE_LIBS)
    +
    endif
    if HAVE_FINCH
    +gntsmarteardir = $(FINCH_LIBDIR)
    +gntsmartear_la_LDFLAGS = -module -avoid-version
    +gntsmartear_la_CPPFLAGS = \
    + -DLIBDIR=\"$(FINCH_LIBDIR)\" \
    + -DDATADIR=\"$(FINCH_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(FINCH_PIIXMAPSDIR)\" \
    + $(FINCH_CFLAGS) \
    + $(GNT_CFLAGS)
    +
    gntsmartear_LTLIBRARIES = gntsmartear.la
    gntsmartear_la_SOURCES = gntsmartear.c
    -gntsmartear_la_LIBADD = $(FINCH_LIBS)
    +gntsmartear_la_LIBADD = \
    + $(FINCH_LIBS) \
    + $(GNT_LIBS) \
    + $(PURPLE_LIBS)
    +
    endif
    -AM_CPPFLAGS = \
    - -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    - -DDATADIR=\"$(PURPLE_DATADIR)\" \
    - -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    - $(DEBUG_CFLAGS) \
    - $(PURPLE_CFLAGS)
    -
    --- a/smartear/gtksmartear.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/smartear/gtksmartear.c Mon Mar 24 15:08:23 2008 -0400
    @@ -25,8 +25,6 @@
    /* Pack/Local headers */
    #include "../common/pp_internal.h"
    -#define PLUGIN_AUTHOR "John Bailey <rekkanoryo@rekkanoryo.org>"
    -
    /* System headers */
    #include <gdk/gdk.h>
    #include <glib.h>
    @@ -86,7 +84,7 @@
    PP_VERSION, /* version */
    NULL, /* summary */
    NULL, /* description */
    - PLUGIN_AUTHOR, /* author */
    + "John Bailey <rekkanoryo@rekkanoryo.org>",
    PP_WEBSITE, /* website */
    plugin_load, /* load */
    --- a/snpp/snpp.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/snpp/snpp.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,7 +1,7 @@
    /*
    * gaim-snpp Protocol Plugin
    *
    - * Copyright (C) 2004, Don Seiler <don@seiler.us>
    + * Copyright (C) 2004-2008 Don Seiler <don@seiler.us>
    *
    * 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
    @@ -534,7 +534,16 @@
    NULL, /* roomlist_cancel */
    NULL, /* roomlist_expand_catagory */
    NULL, /* can_receive_file */
    - NULL /* send_file */
    + NULL, /* send_file */
    + NULL, /* new_xfer */
    + NULL, /* offline_message */
    + NULL, /* whiteboard_prpl_ops */
    + NULL, /* send_raw */
    + NULL, /* roomlist_room_serialize */
    + NULL, /* unregister_user */
    + NULL, /* send_attention */
    + NULL, /* get_attention_types */
    + NULL /* reserved 4 */
    };
    --- a/sslinfo/sslinfo.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/sslinfo/sslinfo.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * sslinfo - Gets info about your currently loaded ssl plugin
    - * Copyright (C) 2004 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2004-2008 Gary Kramlich <grim@reaperworld.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License
    --- a/stocker/stocker.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/stocker/stocker.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Stocker - Adds a stock ticker to the buddy list
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/stocker/stocker_prefs.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/stocker/stocker_prefs.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Stocker - Adds a stock ticker to the buddy list
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/stocker/stocker_prefs.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/stocker/stocker_prefs.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Stocker - Adds a stock ticker to the buddy list
    - * Copyright (C) 2005-2007 Gary Kramlich <grim@reaperworld.com>
    + * Copyright (C) 2005-2008 Gary Kramlich <grim@reaperworld.com>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/switchspell/switchspell.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/switchspell/switchspell.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Switchspell - Switch spelling language during run time.
    - * Copyright (C) 2007
    + * Copyright (C) 2007-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -127,6 +127,7 @@
    delete_aspell_config(config);
    dels = aspell_dict_info_list_elements(dlist);
    + aspell_dict_info_list_empty(dlist);
    while ((entry = aspell_dict_info_enumeration_next(dels)) != 0) {
    GtkWidget *menuitem = gtk_radio_menu_item_new_with_label(group, entry->name);
    group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menuitem));
    --- a/timelog/log-widget.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/timelog/log-widget.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,12 +1,11 @@
    /*
    * TimeLog plugin.
    *
    - * Copyright (c) 2006 Jon Oberheide.
    + * Copyright (C) 2006 Jon Oberheide.
    + * Copyright (C) 2007-2008 Stu Tomlinson
    *
    * Based on code from Gaim's gtklog.c
    *
    - * $Id: log-widget.c,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
    - *
    * 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
    --- a/timelog/log-widget.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/timelog/log-widget.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,9 +1,8 @@
    /*
    - * Gaim-TimeLog plugin.
    + * TimeLog plugin.
    *
    - * Copyright (c) 2006 Jon Oberheide.
    - *
    - * $Id: log-widget.h,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
    + * Copyright (C) 2006 Jon Oberheide.
    + * Copyright (C) 2007-2008 Stu Tomlinson
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/timelog/range-widget.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/timelog/range-widget.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,9 +1,8 @@
    /*
    * TimeLog plugin.
    *
    - * Copyright (c) 2006 Jon Oberheide.
    - *
    - * $Id: range-widget.c,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
    + * Copyright (C) 2006 Jon Oberheide
    + * Copyright (C) 2007-2008 Stu Tomlinson
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/timelog/range-widget.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/timelog/range-widget.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,9 +1,8 @@
    /*
    - * Gaim-TimeLog plugin.
    + * TimeLog plugin.
    *
    - * Copyright (c) 2006 Jon Oberheide.
    - *
    - * $Id: range-widget.h,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
    + * Copyright (C) 2006 Jon Oberheide.
    + * Copyright (C) 2007-2008 Stu Tomlinson
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/timelog/timelog.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/timelog/timelog.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,9 +1,8 @@
    /*
    * TimeLog plugin.
    *
    - * Copyright (c) 2006 Jon Oberheide.
    - *
    - * $Id: timelog.c,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
    + * Copyright (C) 2006 Jon Oberheide
    + * Copyright (C) 2007-2008 Stu Tomlinson
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    --- a/timelog/timelog.h Sun Dec 23 07:18:21 2007 -0500
    +++ b/timelog/timelog.h Mon Mar 24 15:08:23 2008 -0400
    @@ -1,9 +1,8 @@
    /*
    * TimeLog plugin.
    *
    - * Copyright (c) 2006 Jon Oberheide.
    - *
    - * $Id: gaim-timelog.h,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
    + * Copyright (C) 2006 Jon Oberheide
    + * Copyright (C) 2007-2008 Stu Tomlinson
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -22,7 +21,7 @@
    */
    #ifndef _TIMELOG_H_
    -#define _TIMELOG_H
    +#define _TIMELOG_H_
    #define TIMELOG_PLUGIN_ID "gtk-binaryjono-timelog"
    #define TIMELOG_TITLE _("TimeLog")
    --- a/xchat-chats/xchat-chats.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/xchat-chats/xchat-chats.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    * Purple-XChat - Use XChat-like chats
    - * Copyright (C) 2005
    + * Copyright (C) 2005-2008
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -147,7 +147,14 @@
    }
    #endif
    -GtkWidget *get_xtext(PurpleConversation *conv)
    +static gboolean
    +is_2_4_0_or_above(void)
    +{
    + return (purple_version_check(2, 4, 0) == NULL);
    +}
    +
    +
    +static GtkWidget *get_xtext(PurpleConversation *conv)
    {
    PurpleXChat *gx;
    @@ -258,35 +265,44 @@
    g_free(msg);
    }
    +#define DEBUG_INFO(x) \
    + name = G_OBJECT_TYPE_NAME(x); \
    + printf("%s\n", name)
    +
    static GtkWidget*
    hack_and_get_widget(PidginConversation *gtkconv)
    {
    - GtkWidget *tab_cont, *pane, *vbox, *hpaned, *frame;
    + GtkWidget *tab_cont, *vbox, *hpaned, *frame;
    GList *list;
    const char *name;
    /* If you think this is ugly, you are right. */
    -#define DEBUG_INFO(x) \
    - name = G_OBJECT_TYPE_NAME(x); \
    - printf("%s\n", name)
    -
    tab_cont = gtkconv->tab_cont;
    DEBUG_INFO(tab_cont);
    list = gtk_container_get_children(GTK_CONTAINER(tab_cont));
    - pane = list->data;
    + if (!is_2_4_0_or_above()) {
    + GtkWidget *pane = list->data;
    + DEBUG_INFO(pane);
    +
    + vbox = GTK_PANED(pane)->child1;
    + } else {
    + vbox = list->data;
    + }
    g_list_free(list);
    - DEBUG_INFO(pane);
    - vbox = GTK_PANED(pane)->child1;
    DEBUG_INFO(vbox);
    list = GTK_BOX(vbox)->children;
    - while (list->next)
    + while (list) {
    + if (GTK_IS_PANED(((GtkBoxChild*)list->data)->widget))
    + break;
    list = list->next;
    + }
    hpaned = ((GtkBoxChild*)list->data)->widget;
    DEBUG_INFO(hpaned);
    frame = GTK_PANED(hpaned)->child1;
    +
    return frame;
    }
    @@ -310,6 +326,7 @@
    box = gtk_hbox_new(FALSE, 0);
    xtext = get_xtext(conv);
    +
    GTK_PANED(parent)->child1 = NULL;
    gtk_paned_pack1(GTK_PANED(parent), box, TRUE, TRUE);
    @@ -345,6 +362,17 @@
    }
    }
    +static void
    +workaround_for_hidden_convs(PidginConversation *gtkconv)
    +{
    + PurpleConversation *conv = gtkconv->active_conv;
    +
    + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT ||
    + g_hash_table_lookup(xchats, conv))
    + return;
    + purple_conversation_use_xtext(conv);
    +}
    +
    static gboolean
    plugin_load(PurplePlugin *plugin)
    {
    @@ -370,10 +398,17 @@
    list = purple_get_chats();
    while (list)
    {
    + /* TODO: We can get the message history of the conversation and populate
    + * the next xtext with that data.
    + * Note: purple_conversation_get_message_history
    + */
    purple_conversation_use_xtext(list->data);
    list = list->next;
    }
    -
    +
    + purple_signal_connect(pidgin_conversations_get_handle(), "conversation-displayed",
    + plugin, G_CALLBACK(workaround_for_hidden_convs), NULL);
    +
    return TRUE;
    }
    @@ -401,7 +436,7 @@
    uiops->write_conv = default_write_conv;
    uiops->create_conversation = default_create_conversation;
    uiops->destroy_conversation = default_destroy_conversation;
    -
    +
    /* Clear up everything */
    g_hash_table_foreach(xchats, (GHFunc)remove_xtext, NULL);
    g_hash_table_destroy(xchats);
    --- a/xmmsremote/xmmsremote.c Sun Dec 23 07:18:21 2007 -0500
    +++ b/xmmsremote/xmmsremote.c Mon Mar 24 15:08:23 2008 -0400
    @@ -1,6 +1,6 @@
    /*
    xmms-remote - Control xmms from Pidgin conversations
    - Copyright (C) 2004 Gary Kramlich
    + Copyright (C) 2004-2008 Gary Kramlich
    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License