pidgin/purple-plugin-pack

7547e4f94232
propagate from branch 'org.guifications.plugins' (head f09d18bf539ac5277d82c5b0c94692107074a0bb)
to branch 'org.guifications.plugins.smartear' (head 22d2b0f5359de38829e576dcd53b00bc1e4fb3f8)
  • +5 -1
    .mtn-ignore
  • +6 -1
    AUTHORS
  • +46 -9
    ChangeLog
  • +3 -0
    Makefile.am
  • +9 -0
    README
  • +1 -1
    VERSION
  • +11 -2
    album/album-ui.c
  • +4 -2
    album/album-ui.h
  • +3 -3
    album/album.c
  • +3 -2
    album/album.h
  • +125 -39
    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
  • +38 -15
    autoreply/autoreply.c
  • +2 -1
    awaynotify/awaynotify.c
  • +2 -1
    bash/bash.c
  • +2 -1
    bit/bit.c
  • +3 -1
    blistops/blistops.c
  • +0 -28
    broadcast/Makefile.am
  • +0 -11
    broadcast/Makefile.mingw
  • +0 -187
    broadcast/broadcast.c
  • +30 -8
    buddytime/Makefile.am
  • +429 -0
    buddytime/buddyedit.c
  • +416 -186
    buddytime/buddytime.c
  • +260 -0
    buddytime/buddytime.c.old
  • +52 -0
    buddytime/buddytime.h
  • +150 -0
    buddytime/gtkbuddytime.c
  • +237 -0
    buddytime/gtktimezone.c
  • +343 -0
    buddytime/gtktimezonetest.c
  • +1250 -0
    buddytime/localtime.c
  • +16 -0
    buddytime/localtime.h
  • +316 -0
    buddytime/private.h
  • +141 -0
    buddytime/recurse.c
  • +9 -0
    buddytime/recurse.h
  • +25 -0
    buddytime/recursetest.c
  • +28 -0
    buddytime/timetest.c
  • +175 -0
    buddytime/tzfile.h
  • +2 -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
  • +18 -5
    configure.ac
  • +14 -5
    convbadger/convbadger.c
  • +26 -0
    dewysiwygification/Makefile.am
  • +12 -0
    dewysiwygification/Makefile.mingw
  • +131 -0
    dewysiwygification/dewysiwygification.c
  • +303 -62
    dice/dice.c
  • +2 -1
    difftopic/difftopic.c
  • +211 -5
    eight_ball/eight_ball.c
  • +29 -0
    enhancedhist/Makefile.am
  • +12 -0
    enhancedhist/Makefile.mingw
  • +394 -0
    enhancedhist/enhancedhist.c
  • +16 -21
    findip/findip.c
  • +9 -3
    gRIM/gRIM.c
  • +2 -1
    groupmsg/groupmsg.c
  • +1 -1
    hideconv/Makefile.am
  • +3 -2
    hideconv/hideconv.c
  • +1 -1
    highlight/highlight.c
  • +4 -3
    ignore/ignore.c
  • +10 -3
    infopane/infopane.c
  • +27 -13
    irc-more/irc-more.c
  • +8 -2
    irchelper/irchelper.c
  • +3 -2
    irssi/datechange.c
  • +3 -2
    irssi/datechange.h
  • +4 -2
    irssi/irssi.c
  • +4 -2
    irssi/lastlog.c
  • +3 -2
    irssi/lastlog.h
  • +4 -2
    irssi/layout.c
  • +3 -2
    irssi/layout.h
  • +9 -3
    irssi/textfmt.c
  • +3 -2
    irssi/textfmt.h
  • +4 -2
    irssi/window.c
  • +3 -2
    irssi/window.h
  • +2 -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
  • +2 -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
  • +53 -18
    m4/pluginpack.m4
  • +8 -4
    menuconfig
  • +68 -21
    mkinstalldirs
  • +2 -1
    mystatusbox/mystatusbox.c
  • +0 -0
    napster/16/napster.png
  • +0 -0
    napster/22/napster.png
  • +0 -0
    napster/48/napster.png
  • +13 -15
    napster/Makefile.am
  • +11 -0
    napster/Makefile.mingw
  • +39 -33
    napster/napster.c
  • +0 -0
    napster/napster16x16.png
  • +0 -0
    napster/napster22x22.png
  • +0 -0
    napster/napster48x48.png
  • +3 -2
    nicksaid/nicksaid.c
  • +15 -1
    oldlogger/oldlogger.c
  • +3 -2
    plonkers/plonkers.c
  • +9 -1
    plugin_pack.spec.in
  • +9 -4
    po/POTFILES.in
  • +307 -123
    po/en_AU.po
  • +291 -105
    po/es_ES.po
  • +304 -109
    po/fr.po
  • +2 -1
    schedule/pidgin-schedule.c
  • +1 -1
    schedule/schedule.c
  • +2 -1
    schedule/schedule.h
  • +2 -1
    sepandtab/sepandtab.c
  • +2 -1
    showoffline/showoffline.c
  • +2 -1
    simfix/simfix.c
  • +6 -5
    slashexec/slashexec.c
  • +15 -6
    snpp/snpp.c
  • +2 -1
    sslinfo/sslinfo.c
  • +272 -21
    stocker/stocker.c
  • +6 -9
    stocker/stocker_prefs.c
  • +5 -5
    stocker/stocker_prefs.h
  • +2 -2
    switchspell/Makefile.am
  • +1 -5
    switchspell/Makefile.mingw
  • +3 -1
    switchspell/switchspell.c
  • +6 -0
    talkfilters/talkfilters.c
  • +31 -0
    timelog/Makefile.am
  • +18 -0
    timelog/Makefile.mingw
  • +381 -0
    timelog/log-widget.c
  • +28 -0
    timelog/log-widget.h
  • +364 -0
    timelog/range-widget.c
  • +30 -0
    timelog/range-widget.h
  • +199 -0
    timelog/timelog.c
  • +34 -0
    timelog/timelog.h
  • +49 -13
    xchat-chats/xchat-chats.c
  • +88 -75
    xmmsremote/xmmsremote.c
  • --- a/.mtn-ignore Tue Oct 09 06:21:19 2007 -0400
    +++ b/.mtn-ignore Mon Mar 24 05:08:00 2008 -0400
    @@ -4,7 +4,9 @@
    .+\.spec
    .*\.dll
    aclocal.m4
    -config.(guess|log|status|sub)
    +buddytime/gtktimezonetest
    +buddytime/recursetest
    +config.(cache|guess|log|status|sub)
    configure
    depcomp
    install-sh
    @@ -14,6 +16,8 @@
    ltmain.sh
    missing
    po/Makefile.in.in
    +po/missing
    +po/notexist
    po/POTFILES
    po/stamp-it
    po/.+\.pot
    --- a/AUTHORS Tue Oct 09 06:21:19 2007 -0400
    +++ b/AUTHORS Mon Mar 24 05:08:00 2008 -0400
    @@ -9,6 +9,10 @@
    Daniel Atallah <datallah@users.sourceforge.net>
    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
    ===========
    @@ -36,15 +40,16 @@
    Joshua Cowan
    Mathias Hasselmann
    Björn Nilsson
    +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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/ChangeLog Mon Mar 24 05:08:00 2008 -0400
    @@ -1,13 +1,41 @@
    -Version 2.2.0mtn:
    - * Merged autorejoin into irc-more. No prefs migration will take place.
    - You will need to reconfigure the delay yourself. Autorejoin no longer
    - exists.
    - * Slashexec's '/exec command' and '!command' are now optional (QuLogic)
    - * Listhandler now supports restoring a buddy list from a backed-up
    - blist.xml file created by backing up ~/.purple.
    - * Added support for initial user modes to irc-more
    +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.
    + * Added /bollocks command to Magic 8 ball plugin inspired by (and ported
    + from) /dev/bollocks kernel module
    + * Added Andrew Pangborn's Enhanced History plugin and ported it to the
    + Pidgin and libpurple APIs. Cleaned up some preferences as well.
    + * Dice plugin now supports dice notation
    + (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
    + build.
    + * Added support for initial setting/unsetting of user modes to irc-more
    * Added the abusive findip plugin
    * Added infopane plugin
    + * Added dewysiwygification plugin
    + * Added timelog plugin, from Jon Oberheide's gaim-timelog
    + * Fixed autoreply so it doesn't reply with an empty message (rageboy04)
    * Fixed a crash in ignore where a nickname that is not all lowercase
    causes unintended behavior resulting in a crash (rageboy04, qwert)
    * Fixed a possible double-free crash in /notice support in irc-more.
    @@ -19,7 +47,16 @@
    built against.
    * Fixed building with ancient glib. (Bodo Bellut)
    * Removed the .build file from hideconv to remove it from default
    - builds. Pidgin 2.2.1 will have persistent conversations.
    + builds. Pidgin will have persistent conversations soon.
    + * Partially merged buddytimezone from the buddytools package into the
    + existing (incomplete) buddytime plugin
    + * Autoreply now can be disabled per-account (rageboy04)
    + * Listhandler now supports restoring a buddy list from a backed-up
    + blist.xml file created by backing up ~/.purple.
    + * Merged autorejoin into irc-more. No prefs migration will take place.
    + You will need to reconfigure the delay yourself. Autorejoin no longer
    + exists.
    + * Slashexec's '/exec command' and '!command' are now optional (QuLogic)
    Version 2.1.1: 8/19/07
    * Fixed lack of .build, .pidgin-plugin, and Makefile.mingw for convbadger
    --- a/Makefile.am Tue Oct 09 06:21:19 2007 -0400
    +++ b/Makefile.am Mon Mar 24 05:08:00 2008 -0400
    @@ -44,6 +44,9 @@
    ACLOCAL_AMFLAGS = -I m4
    +signatures: dist
    + echo $(DIST_ARCHIVES) | xargs -n 1 gpg -a -b
    +
    info:
    @echo "---------------------------------------"
    @echo "Plugin Pack Info"
    --- a/README Tue Oct 09 06:21:19 2007 -0400
    +++ b/README Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/VERSION Mon Mar 24 05:08:00 2008 -0400
    @@ -1,1 +1,1 @@
    -2.2.0mtn
    +2.4.0mtn
    --- a/album/album-ui.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/album/album-ui.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/album/album-ui.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/album/album.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/album/album.h Mon Mar 24 05:08:00 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
    @@ -22,6 +22,7 @@
    #ifndef _ALBUM_H_
    #define _ALBUM_H_
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <account.h>
    --- a/autogen.sh Tue Oct 09 06:21:19 2007 -0400
    +++ b/autogen.sh Mon Mar 24 05:08:00 2008 -0400
    @@ -1,49 +1,135 @@
    #! /bin/sh
    -PACKAGE="purple-plugin_pack"
    -
    -(intltoolize --version) < /dev/null > /dev/null 2>&1 || {
    - echo;
    - echo "You must have intltool installed to compile $PACKAGE";
    - echo;
    - exit;
    -}
    +# Guifications - The end-all, be-all notification framework
    +# 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
    +# Software Foundation; either version 2 of the License, or (at your option)
    +# any later version.
    +#
    +# This program is distributed in the hope that it will be useful, but WITHOUT
    +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
    +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
    +# more details.
    +#
    +# You should have received a copy of the GNU General Public License along with
    +# this program; if not, write to the Free Software Foundation, Inc., 51
    +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    -(libtoolize --version) < /dev/null > /dev/null 2>&1 || {
    - echo;
    - echo "You must have libtool installed to compile $PACKAGE";
    - echo;
    - exit;
    -}
    +###############################################################################
    +# Usage
    +###############################################################################
    +# This script uses a config file that can be used to stash common arguments
    +# passed to configure or environment variables that need to be set before
    +# configure is called. The configuration file is a simple shell script that
    +# gets sourced.
    +#
    +# By default, the config file that is used is named 'autogen.args'. This can
    +# be configured below.
    +#
    +# Available options that are handled are as follow:
    +# ACLOCAL_FLAGS - command line arguments to pass to aclocal
    +# AUTOCONF_FLAGS - command line arguments to pass to autoconf
    +# AUTOHEADER_FLAGS - command line arguments to pass to autoheader
    +# AUTOMAKE_FLAGS - command line arguments to pass to automake flags
    +# CONFIGURE_FLAGS - command line arguments to pass to configure
    +# INTLTOOLIZE_FLAGS - command line arguments to pass to intltoolize
    +# LIBTOOLIZE_FLAGS - command line arguments to pass to libtoolize
    +#
    +# Other helpfull notes:
    +# If you're using a different c compiler, you can override the environment
    +# variable in 'autogen.args'. For example, say you're using distcc, just add
    +# the following to 'autogen.args':
    +#
    +# CC="distcc"
    +#
    +# This will work for any influential environment variable to configure.
    +###############################################################################
    +PACKAGE="Purple Plugin Pack"
    +ARGS_FILE="autogen.args"
    -(automake --version) < /dev/null > /dev/null 2>&1 || {
    - echo;
    - echo "You must have automake installed to compile $PACKAGE";
    - echo;
    - exit;
    +###############################################################################
    +# Some helper functions
    +###############################################################################
    +check () {
    + CMD=$1
    +
    + echo -n "checking for ${CMD}... "
    + BIN=`which ${CMD}`
    +
    + if [ x"${BIN}" = x"" ] ; then
    + echo "not found."
    + echo "${CMD} is required to build ${PACKAGE}!"
    + exit 1;
    + fi
    +
    + echo "${BIN}"
    }
    -(autoconf --version) < /dev/null > /dev/null 2>&1 || {
    - echo;
    - echo "You must have autoconf installed to compile $PACKAGE";
    - echo;
    - exit;
    +run_or_die () { # beotch
    + CMD=$1
    + shift
    +
    + echo -n "running ${CMD} ${@}... "
    + OUTPUT=`${CMD} ${@} 2>&1`
    + if [ $? != 0 ] ; then
    + echo "failed."
    + echo ${OUTPUT}
    + exit 1
    + else
    + echo "done."
    + if [ x"${OUTPUT}" != x"" ] ; then
    + echo ${OUTPUT}
    + fi
    + fi
    }
    -echo "Generating configuration files for $PACKAGE, please wait...."
    -echo;
    +###############################################################################
    +# We really start here, yes, very sneaky!
    +###############################################################################
    +FIGLET=`which figlet`
    +if [ x"${FIGLET}" != x"" ] ; then
    + ${FIGLET} -f small ${PACKAGE}
    + echo "build system is being generated"
    +else
    + echo "autogenerating build system for '${PACKAGE}'"
    +fi
    -echo "Running libtoolize, please ignore non-fatal messages...."
    -echo n | libtoolize --copy --force || exit;
    -echo;
    +###############################################################################
    +# Look for our args file
    +###############################################################################
    +echo -n "checking for ${ARGS_FILE}: "
    +if [ -f ${ARGS_FILE} ] ; then
    + echo "found."
    + echo -n "sourcing ${ARGS_FILE}: "
    + . autogen.args
    + echo "done."
    +else
    + echo "not found."
    +fi
    -libtoolize -c -f --automake
    -intltoolize --force --copy
    -aclocal -I m4 || exit;
    -autoheader || exit;
    -automake --add-missing --copy
    -autoconf || exit;
    -automake || exit;
    +###############################################################################
    +# Check for our required helpers
    +###############################################################################
    +check "libtoolize"; LIBTOOLIZE=${BIN};
    +check "intltoolize"; INTLTOOLIZE=${BIN};
    +check "aclocal"; ACLOCAL=${BIN};
    +check "autoheader"; AUTOHEADER=${BIN};
    +check "automake"; AUTOMAKE=${BIN};
    +check "autoconf"; AUTOCONF=${BIN};
    -echo "Running ./configure $@"
    -echo;
    -./configure $@
    +###############################################################################
    +# Run all of our helpers
    +###############################################################################
    +run_or_die ${LIBTOOLIZE} -c -f --automake ${LIBTOOLIZE_FLAGS}
    +run_or_die ${INTLTOOLIZE} -c -f --automake ${INTLTOOLIZE_FLAGS}
    +run_or_die ${ACLOCAL} -I m4 ${ACLOCAL_FLAGS}
    +run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS}
    +run_or_die ${AUTOMAKE} -a -c -f --gnu ${AUTOMAKE_FLAGS}
    +run_or_die ${AUTOCONF} -f ${AUTOCONF_FLAGS}
    +
    +###############################################################################
    +# Run configure
    +###############################################################################
    +echo "running ./configure ${CONFIGURE_ARGS} $@"
    +./configure ${CONFIGURE_ARGS} $@
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/Makefile.am Mon Mar 24 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/autoreply/autoreply.c Mon Mar 24 05:08:00 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
    @@ -18,6 +18,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #define PLUGIN_ID "core-plugin_pack-autoreply"
    @@ -44,6 +45,7 @@
    #define PREFS_MAXSEND PREFS_PREFIX "/maxsend"
    #define PREFS_USESTATUS PREFS_PREFIX "/usestatus"
    #define PREFS_PREFIX_MSG PREFS_PREFIX "/prefix"
    +#define PREFS_X_INVISIBLE PREFS_PREFIX "/invisible"
    typedef struct _PurpleAutoReply PurpleAutoReply;
    typedef struct _AutoReplyProtocolOptions AutoReplyProtocolOptions;
    @@ -56,6 +58,7 @@
    struct _AutoReplyProtocolOptions {
    PurpleAccountOption *message;
    + PurpleAccountOption *off;
    };
    typedef enum
    @@ -87,31 +90,31 @@
    reply = purple_savedstatus_get_message(purple_savedstatus_get_current());
    }
    - if (!reply && buddy)
    + if ((!reply || !*reply) && buddy)
    {
    /* Is there any special auto-reply for this buddy? */
    reply = purple_blist_node_get_string((PurpleBlistNode*)buddy, "autoreply");
    - if (!reply && PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy))
    + if ((!reply || !*reply) && PURPLE_BLIST_NODE_IS_BUDDY((PurpleBlistNode*)buddy))
    {
    /* Anything for the contact, then? */
    reply = purple_blist_node_get_string(((PurpleBlistNode*)buddy)->parent, "autoreply");
    }
    }
    - if (!reply)
    + if (!reply || !*reply)
    {
    /* Is there any specific auto-reply for this account? */
    reply = purple_account_get_string(account, "autoreply", NULL);
    }
    - if (!reply)
    + if (!reply || !*reply)
    {
    /* Get the global auto-reply message */
    reply = purple_prefs_get_string(PREFS_GLOBAL);
    }
    - if (*reply == ' ')
    + if (*reply == ' ' || *reply == '\0')
    reply = NULL;
    if (!reply && use_status == STATUS_FALLBACK)
    @@ -135,21 +138,28 @@
    if (!message || !*message)
    return;
    - /* Do not send an autoreply for an autoreply */
    - if (flags & PURPLE_MESSAGE_AUTO_RESP)
    + /* Do not send an autoreply for an autoreply or a 'delayed' (offline?) message */
    + if (flags & (PURPLE_MESSAGE_AUTO_RESP | PURPLE_MESSAGE_DELAYED))
    + return;
    +
    + if(purple_account_get_bool(account, "ar_off", FALSE))
    return;
    g_return_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_IM);
    presence = purple_account_get_presence(account);
    + if (purple_prefs_get_bool(PREFS_X_INVISIBLE) &&
    + purple_presence_is_status_primitive_active(presence, PURPLE_STATUS_INVISIBLE))
    + return;
    +
    if (purple_prefs_get_bool(PREFS_AWAY) && !purple_presence_is_available(presence))
    trigger = TRUE;
    if (purple_prefs_get_bool(PREFS_IDLE) && purple_presence_is_idle(presence))
    trigger = TRUE;
    if (!trigger)
    - return;
    + return;
    buddy = purple_find_buddy(account, who);
    reply = get_autoreply_message(buddy, account);
    @@ -226,7 +236,7 @@
    get_autoreply_message(buddy, account), TRUE, FALSE,
    (gc->flags & PURPLE_CONNECTION_HTML) ? "html" : NULL,
    _("_Save"), G_CALLBACK(set_auto_reply_cb),
    - _("_Cancel"), NULL,
    + _("_Cancel"), NULL,
    account, purple_buddy_get_name(buddy), NULL, node);
    g_free(message);
    }
    @@ -252,13 +262,16 @@
    {
    AutoReplyProtocolOptions *arpo;
    PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(plg);
    -
    +
    arpo = g_new(AutoReplyProtocolOptions, 1);
    arpo->message = purple_account_option_string_new(_("Autoreply message"),
    "autoreply", NULL);
    + arpo->off = purple_account_option_bool_new(_("Turn off autoreply"),
    + "ar_off", FALSE);
    info->protocol_options = g_list_append(info->protocol_options,
    arpo->message);
    + info->protocol_options = g_list_append(info->protocol_options, arpo->off);
    if (!g_hash_table_lookup(options, plg))
    g_hash_table_insert(options, plg, arpo);
    @@ -273,7 +286,7 @@
    if(!arpo)
    return;
    -
    +
    /*
    * 22:55 < sadrul> grim: the check when removing is required, iirc, when
    * pidgin quits, and a prpl is unloaded before the plugin
    @@ -282,9 +295,14 @@
    {
    info->protocol_options = g_list_remove_link(info->protocol_options, l);
    purple_account_option_destroy(arpo->message);
    - g_hash_table_remove(options, plg);
    + }
    + if ((l = g_list_find(info->protocol_options, arpo->off)))
    + {
    + info->protocol_options = g_list_remove_link(info->protocol_options, l);
    + purple_account_option_destroy(arpo->off);
    }
    + g_hash_table_remove(options, plg);
    g_free(arpo);
    }
    @@ -322,7 +340,7 @@
    add_options_for_protocol(list->data);
    list = list->next;
    }
    -
    +
    return TRUE;
    }
    @@ -376,13 +394,17 @@
    _("Autoreply Prefix\n(only when necessary)"));
    purple_plugin_pref_frame_add(frame, pref);
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_X_INVISIBLE,
    + _("Do not autoreply when invisible."));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    pref = purple_plugin_pref_new_with_label(_("Status message"));
    purple_plugin_pref_frame_add(frame, pref);
    pref = purple_plugin_pref_new_with_name_and_label(PREFS_USESTATUS,
    _("Autoreply with status message"));
    purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_CHOICE);
    - purple_plugin_pref_add_choice(pref, _("Never"),
    + purple_plugin_pref_add_choice(pref, _("Never"),
    GINT_TO_POINTER(STATUS_NEVER));
    purple_plugin_pref_add_choice(pref, _("Always when there is a status message"),
    GINT_TO_POINTER(STATUS_ALWAYS));
    @@ -479,6 +501,7 @@
    purple_prefs_add_int(PREFS_MAXSEND, 10);
    purple_prefs_add_int(PREFS_USESTATUS, STATUS_NEVER);
    purple_prefs_add_string(PREFS_PREFIX_MSG, _("This is an autoreply: "));
    + purple_prefs_add_bool(PREFS_X_INVISIBLE, TRUE);
    }
    PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- a/awaynotify/awaynotify.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/awaynotify/awaynotify.c Mon Mar 24 05:08:00 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
    @@ -17,6 +17,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <blist.h>
    --- a/bash/bash.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/bash/bash.c Mon Mar 24 05:08:00 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
    @@ -21,6 +21,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    /* libc */
    --- a/bit/bit.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/bit/bit.c Mon Mar 24 05:08:00 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
    @@ -16,6 +16,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <buddyicon.h>
    --- a/blistops/blistops.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/blistops/blistops.c Mon Mar 24 05:08:00 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
    @@ -18,6 +19,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <gdk/gdk.h>
    --- a/broadcast/Makefile.am Tue Oct 09 06:21:19 2007 -0400
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,28 +0,0 @@
    -EXTRA_DIST = \
    - .abusive \
    - .purple-plugin \
    - Makefile.mingw
    -
    -broadcastdir = $(PURPLE_LIBDIR)
    -
    -broadcast_la_LDFLAGS = -module -avoid-version
    -
    -if HAVE_PURPLE
    -
    -broadcast_LTLIBRARIES = broadcast.la
    -
    -broadcast_la_SOURCES = \
    - broadcast.c
    -
    -broadcast_la_LIBADD = \
    - $(GLIB_LIBS) \
    - $(PURPLE_LIBS)
    -
    -endif
    -
    -AM_CPPFLAGS = \
    - -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    - -DDATADIR=\"$(PURPLE_DATADIR)\" \
    - $(DEBUG_CFLAGS) \
    - $(PURPLE_CFLAGS)
    -
    --- a/broadcast/Makefile.mingw Tue Oct 09 06:21:19 2007 -0400
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,11 +0,0 @@
    -#
    -# Makefile.mingw
    -#
    -# Description: Makefile for broadcast plugin.
    -#
    -
    -PP_TOP := ..
    -
    -PP = broadcast
    -
    -include $(PP_TOP)/win_pp.mak
    --- a/broadcast/broadcast.c Tue Oct 09 06:21:19 2007 -0400
    +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
    @@ -1,187 +0,0 @@
    -/*
    - * Broadcast - Send an IM to your entire buddy list
    - * 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 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"
    -
    -#include <blist.h>
    -#include <debug.h>
    -#include <request.h>
    -#include <util.h>
    -
    -#define BROADCAST_CATEGORY "plugin pack: broadcast"
    -
    -/* These two functions directly ripped from groupmsg and renamed (shamelessly,
    - * I should add). */
    -static void
    -broadcast_request_cancel_cb(GList *list, const char *text)
    -{
    - g_list_free(list);
    -}
    -
    -static void
    -broadcast_request_send_cb(GList *list, const char *message)
    -{
    - char *stripped;
    - GList *l;
    - PurpleBuddy *b;
    - PurpleConversation *conv = NULL;
    -
    - purple_markup_html_to_xhtml(message, NULL, &stripped);
    -
    - for (l = list; l; l = l->next) {
    - b = l->data;
    - conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, b->account, b->name);
    - if(conv) {
    - if (b->account->gc->flags & PURPLE_CONNECTION_HTML)
    - purple_conv_im_send(PURPLE_CONV_IM(conv), message);
    - else
    - purple_conv_im_send(PURPLE_CONV_IM(conv), stripped);
    - }
    - }
    -
    - g_free(stripped);
    - g_list_free(list);
    -}
    -
    -static void
    -broadcast_action_cb(PurplePluginAction *action)
    -{
    - GList *buddies = NULL, *ltmp = NULL;
    - GString *stmp = NULL;
    - const gchar *bname = NULL, *balias = NULL;
    - PurpleBlistNode *root = NULL, *g = NULL, *c = NULL;
    - PurpleBuddy *b = NULL;
    - PurpleBuddyList *blist = NULL;
    -
    - blist = purple_get_blist();
    - root = blist->root;
    -
    - for(g = root; g && PURPLE_BLIST_NODE_IS_GROUP(g); g = g->next) {
    - for (c = g->child; c; c = c->next) {
    - if(!PURPLE_BLIST_NODE_IS_CHAT(c)) {
    - buddies = g_list_append(buddies,
    - purple_contact_get_priority_buddy((PurpleContact *)c));
    -
    - purple_debug_info(BROADCAST_CATEGORY, "added a buddy to queue\n");
    - }
    - }
    - }
    -
    - if(buddies == NULL) {
    - purple_debug_error(BROADCAST_CATEGORY, "no buddies in queue!\n");
    -
    - return;
    - }
    -
    - if(g_list_length(buddies) > 6) { /* arbitrary number that I like */
    - stmp = g_string_new(_("Your message will be sent to and possibly annoy"
    - " EVERYONE on your buddy list!"));
    -
    - purple_debug_info(BROADCAST_CATEGORY,
    - "too many buddies on list; showing generic message!\n");
    - } else {
    - stmp = g_string_new("Your message will be sent to these buddies:\n");
    -
    - for(ltmp = buddies; ltmp; ltmp = ltmp->next) {
    - b = (PurpleBuddy *)(ltmp->data);
    - bname = purple_buddy_get_alias(b);
    - balias = purple_buddy_get_alias(b);
    -
    - g_string_append_printf(stmp, " %s (%s)\n", balias, bname);
    -
    - purple_debug_info(BROADCAST_CATEGORY, "added %s (%s) to dialog string\n",
    - balias, bname);
    - }
    - }
    -
    - purple_debug_info(BROADCAST_CATEGORY, "requesting message input\n");
    -
    - purple_request_input(action, _("Broadcast Spim"),
    - _("Enter the message to send"), stmp->str, "", TRUE, FALSE, "html",
    - _("Send"), G_CALLBACK(broadcast_request_send_cb), _("Cancel"),
    - G_CALLBACK(broadcast_request_cancel_cb), NULL, NULL, NULL, buddies);
    -
    - g_string_free(stmp, TRUE);
    -
    - return;
    -}
    -
    -static GList *
    -broadcast_actions(PurplePlugin *plugin, gpointer context)
    -{
    - purple_debug_info(BROADCAST_CATEGORY, "creating the action list\n");
    -
    - return g_list_append(NULL, purple_plugin_action_new(_("Broadcast a Message"),
    - broadcast_action_cb));
    -}
    -
    -static gboolean
    -broadcast_load(PurplePlugin *plugin)
    -{
    - purple_debug_misc(BROADCAST_CATEGORY, "broadcast_load called\n");
    -
    - return TRUE;
    -}
    -
    -static PurplePluginInfo broadcast_info =
    -{
    - PURPLE_PLUGIN_MAGIC,
    - PURPLE_MAJOR_VERSION,
    - PURPLE_MINOR_VERSION,
    - PURPLE_PLUGIN_STANDARD,
    - NULL,
    - 0,
    - NULL,
    - PURPLE_PRIORITY_DEFAULT,
    - "core-plugin_pack-broadcast",
    - NULL,
    - PP_VERSION,
    - NULL,
    - NULL,
    - "John Bailey <rekkanoryo@rekkanoryo.org>",
    - PP_WEBSITE,
    - broadcast_load,
    - NULL,
    - NULL,
    - NULL,
    - NULL,
    - NULL,
    - broadcast_actions,
    - NULL,
    - NULL,
    - NULL,
    - NULL
    -};
    -
    -
    -static void
    -broadcast_init(PurplePlugin *plugin) {
    -#ifdef ENABLE_NLS
    - bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    - bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    -#endif /* ENABLE_NLS */
    -
    - broadcast_info.name = _("Broadcaster");
    - broadcast_info.summary = _("Send an IM to everyone on your buddy list.");
    - broadcast_info.description = _("Adds the ability to send an IM to every "
    - "Purple Contact on your Buddy List");
    -}
    -
    -PURPLE_INIT_PLUGIN(broadcast, broadcast_init, broadcast_info)
    -
    --- a/buddytime/Makefile.am Tue Oct 09 06:21:19 2007 -0400
    +++ b/buddytime/Makefile.am Mon Mar 24 05:08:00 2008 -0400
    @@ -1,23 +1,45 @@
    -EXTRA_DIST = .incomplete .pidgin-plugin
    +EXTRA_DIST = .incomplete .purple-plugin .pidgin-plugin
    +
    +noinst_PROGRAMS = recursetest
    -buddytimedir = $(PIDGIN_LIBDIR)
    -
    -buddytime_la_LDFLAGS = -module -avoid-version
    +recursetest_SOURCES = \
    + recurse.c \
    + recursetest.c
    if HAVE_PIDGIN
    -buddytime_LTLIBRARIES = buddytime.la
    +noinst_PROGRAMS += gtktimezonetest
    +
    +gtktimezonetest_SOURCES = \
    + gtktimezone.c \
    + gtktimezonetest.c \
    + recurse.c
    -buddytime_la_SOURCES = \
    - buddytime.c
    +gtktimezonetest_LDADD = \
    + $(GTK_LIBS)
    -buddytime_la_LIBADD = \
    +gtkbuddytimedir = $(PIDGIN_LIBDIR)
    +gtkbuddytime_la_LDFLAGS = -module -avoid-version
    +gtkbuddytime_LTLIBRARIES = gtkbuddytime.la
    +gtkbuddytime_la_SOURCES = \
    + gtkbuddytime.c
    +gtkbuddytime_la_LIBADD = \
    $(PIDGIN_LIBS) \
    $(GLIB_LIBS) \
    $(GTK_LIBS)
    endif
    +buddytimedir = $(PURPLE_LIBDIR)
    +buddytime_la_LDFLAGS = -module -avoid-version
    +buddytime_LTLIBRARIES = buddytime.la
    +buddytime_la_SOURCES = \
    + buddytime.c
    +buddytime_la_LIBADD = \
    + $(PURPLE_LIBS) \
    + $(GLIB_LIBS)
    +
    +
    AM_CPPFLAGS = \
    -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/buddyedit.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,429 @@
    +/*************************************************************************
    + * Buddy Edit Module
    + *
    + * A Gaim plugin that adds an edit to to buddies allowing you to change
    + * various details you can't normally change. It also provides a mechanism
    + * for subsequent plugins to add themselves to that dialog.
    + *
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Some code copyright (C) 2006, Richard Laager <rlaager@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 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.
    + *************************************************************************/
    +
    +#define GAIM_PLUGINS
    +#define PLUGIN "core-kleptog-buddyedit"
    +
    +#include <glib.h>
    +#include <string.h>
    +
    +#include "gaim-compat.h"
    +
    +#include "notify.h"
    +#include "plugin.h"
    +#include "util.h"
    +#include "version.h"
    +
    +#include "debug.h" /* Debug output functions */
    +#include "request.h" /* Requests stuff */
    +
    +static GaimPlugin *plugin_self;
    +
    +static void
    +buddyedit_editcomplete_cb(GaimBlistNode * data, GaimRequestFields * fields)
    +{
    + gboolean blist_destroy = FALSE;
    +
    + GaimBlistNode *olddata = data; /* Keep pointer in case we need to destroy it */
    +
    + /* Account detail stuff */
    + switch (data->type)
    + {
    + case GAIM_BLIST_BUDDY_NODE:
    + {
    + GaimBuddy *buddy = (GaimBuddy *) data;
    + GaimAccount *account = gaim_request_fields_get_account(fields, "account");
    + const char *name = gaim_request_fields_get_string(fields, "name");
    + const char *alias = gaim_request_fields_get_string(fields, "alias");
    +
    + /* If any details changes, create the buddy */
    + if((account != buddy->account) || strcmp(name, buddy->name))
    + {
    + GHashTable *tmp;
    + GaimBuddy *newbuddy = gaim_buddy_new(account, name, alias);
    + gaim_blist_add_buddy(newbuddy, NULL, NULL, data); /* Copy it to correct location */
    +
    + /* Now this is ugly, but we want to copy the settings and avoid issues with memory management */
    + tmp = ((GaimBlistNode *) buddy)->settings;
    + ((GaimBlistNode *) buddy)->settings = ((GaimBlistNode *) newbuddy)->settings;
    + ((GaimBlistNode *) newbuddy)->settings = tmp;
    +
    + blist_destroy = TRUE;
    + data = (GaimBlistNode *) newbuddy;
    + }
    + else
    + gaim_blist_alias_buddy(buddy, alias);
    + break;
    + }
    + case GAIM_BLIST_CONTACT_NODE:
    + {
    + GaimContact *contact = (GaimContact *) data;
    + const char *alias = gaim_request_fields_get_string(fields, "alias");
    + gaim_contact_set_alias(contact, alias);
    + break;
    + }
    + case GAIM_BLIST_GROUP_NODE:
    + {
    + GaimGroup *group = (GaimGroup *) data;
    + const char *alias = gaim_request_fields_get_string(fields, "alias");
    + gaim_blist_rename_group(group, alias);
    + break;
    + }
    + case GAIM_BLIST_CHAT_NODE:
    + {
    + GaimChat *chat = (GaimChat *) data;
    + gboolean new_chat = FALSE;
    +
    + GaimConnection *gc;
    + GList *list = NULL, *tmp;
    + gc = gaim_account_get_connection(chat->account);
    + if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
    + list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
    +
    + GaimAccount *newaccount = gaim_request_fields_get_account(fields, "account");
    +
    + /* In Gaim2 each prot_chat_entry has a field "required". We use
    + * this to determine if a field is important enough to recreate
    + * the chat if it changes. Non-required fields we jsut change
    + * in-situ. In Gaim1.5 this field doesn't exist so we always
    + * recreate */
    +#if GAIM_MAJOR_VERSION >= 2
    + if(newaccount != chat->account)
    + new_chat = TRUE;
    + else
    + {
    + const char *oldvalue, *newvalue;
    +
    + for (tmp = g_list_first(list); tmp && !new_chat; tmp = g_list_next(tmp))
    + {
    + struct proto_chat_entry *pce = tmp->data;
    + if(!pce->required) /* Only checking required fields at this point */
    + continue;
    + if(pce->is_int)
    + continue; /* Not yet */
    + oldvalue = g_hash_table_lookup(chat->components, pce->identifier);
    + newvalue = gaim_request_fields_get_string(fields, pce->identifier);
    +
    + if(oldvalue == NULL)
    + oldvalue = "";
    + if(newvalue == NULL)
    + newvalue = "";
    + if(strcmp(oldvalue, newvalue) != 0)
    + new_chat = TRUE;
    + }
    + }
    +#else
    + new_chat = TRUE;
    +#endif
    +
    + if(new_chat)
    + {
    + const char *oldvalue, *newvalue;
    + GHashTable *components =
    + g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
    +
    + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
    + {
    + struct proto_chat_entry *pce = tmp->data;
    +
    + if(pce->is_int)
    + {
    + oldvalue = g_hash_table_lookup(chat->components, pce->identifier);
    + g_hash_table_replace(components, g_strdup(pce->identifier),
    + g_strdup(oldvalue));
    + }
    + else
    + {
    + newvalue = gaim_request_fields_get_string(fields, pce->identifier);
    + g_hash_table_replace(components, g_strdup(pce->identifier),
    + g_strdup(newvalue));
    + }
    + }
    + GaimChat *newchat = gaim_chat_new(newaccount, NULL, components);
    + gaim_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */
    + data = (GaimBlistNode *) newchat;
    + blist_destroy = TRUE;
    + }
    + else /* Just updating values in old chat */
    + {
    + const char *newvalue;
    + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
    + {
    + struct proto_chat_entry *pce = tmp->data;
    +#if GAIM_MAJOR_VERSION >= 2
    + if(pce->required)
    + continue;
    +#endif
    + if(pce->is_int)
    + {
    + /* Do nothing, yet */
    + }
    + else
    + {
    + newvalue = gaim_request_fields_get_string(fields, pce->identifier);
    + g_hash_table_replace(chat->components, g_strdup(pce->identifier),
    + g_strdup(newvalue));
    + }
    + }
    + }
    +
    + const char *alias = gaim_request_fields_get_string(fields, "alias");
    + gaim_blist_alias_chat(chat, alias);
    + break;
    + }
    + case GAIM_BLIST_OTHER_NODE:
    + break;
    + }
    +
    + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-submit-fields", fields, data);
    +
    + if(blist_destroy)
    + {
    + if(olddata->type == GAIM_BLIST_BUDDY_NODE)
    + gaim_blist_remove_buddy((GaimBuddy *) olddata);
    + else if(olddata->type == GAIM_BLIST_CHAT_NODE)
    + gaim_blist_remove_chat((GaimChat *) olddata);
    + }
    +
    + /* Save any changes */
    + gaim_blist_schedule_save();
    +}
    +
    +static GaimAccount *buddyedit_account_filter_func_data;
    +
    +static gboolean
    +buddyedit_account_filter_func(GaimAccount * account)
    +{
    + GaimPluginProtocolInfo *gppi1 =
    + GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(account)->prpl);
    + GaimPluginProtocolInfo *gppi2 =
    + GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(buddyedit_account_filter_func_data)->
    + prpl);
    +
    + return gppi1 == gppi2;
    +}
    +
    +/* Node is either a contact or a buddy */
    +static void
    +buddy_edit_cb(GaimBlistNode * node, gpointer data)
    +{
    + gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddy_edit_cb(%p)\n", node);
    + GaimRequestFields *fields;
    + GaimRequestField *field;
    + GaimRequestFieldGroup *group;
    + char *request_title = NULL;
    +
    + fields = gaim_request_fields_new();
    +
    + switch (node->type)
    + {
    + case GAIM_BLIST_BUDDY_NODE:
    + {
    + GaimBuddy *buddy = (GaimBuddy *) node;
    + group = gaim_request_field_group_new("Buddy Details");
    + gaim_request_fields_add_group(fields, group);
    +
    + field = gaim_request_field_account_new("account", "Account", buddy->account);
    + gaim_request_field_account_set_show_all(field, TRUE);
    + gaim_request_field_group_add_field(group, field);
    +
    + field = gaim_request_field_string_new("name", "Name", buddy->name, FALSE);
    + gaim_request_field_group_add_field(group, field);
    +
    + field = gaim_request_field_string_new("alias", "Alias", buddy->alias, FALSE);
    + gaim_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Buddy";
    + break;
    + }
    + case GAIM_BLIST_CONTACT_NODE:
    + {
    + GaimContact *contact = (GaimContact *) node;
    + group = gaim_request_field_group_new("Contact Details");
    + gaim_request_fields_add_group(fields, group);
    +
    + field = gaim_request_field_string_new("alias", "Alias", contact->alias, FALSE);
    + gaim_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Contact";
    + break;
    + }
    + case GAIM_BLIST_GROUP_NODE:
    + {
    + GaimGroup *grp = (GaimGroup *) node;
    + group = gaim_request_field_group_new("Group Details");
    + gaim_request_fields_add_group(fields, group);
    +
    + field = gaim_request_field_string_new("alias", "Name", grp->name, FALSE);
    + gaim_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Group";
    + break;
    + }
    + case GAIM_BLIST_CHAT_NODE:
    + {
    + GaimChat *chat = (GaimChat *) node;
    + group = gaim_request_field_group_new("Chat Details");
    + gaim_request_fields_add_group(fields, group);
    +
    + field = gaim_request_field_account_new("account", "Account", chat->account);
    + gaim_request_field_account_set_filter(field, buddyedit_account_filter_func);
    + buddyedit_account_filter_func_data = chat->account;
    + gaim_request_field_group_add_field(group, field);
    + GaimConnection *gc;
    + GList *list = NULL, *tmp;
    + gc = gaim_account_get_connection(chat->account);
    + if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
    + list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
    +
    + for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
    + {
    + struct proto_chat_entry *pce = tmp->data;
    + const char *value;
    +#if GAIM_MAJOR_VERSION >= 2
    + gaim_debug(GAIM_DEBUG_INFO, PLUGIN,
    + "identifier=%s, label=%s, is_int=%d, required=%d\n", pce->identifier,
    + pce->label, pce->is_int, pce->required);
    +#endif
    + if(pce->is_int)
    + continue; /* Not yet */
    + value = g_hash_table_lookup(chat->components, pce->identifier);
    + field = gaim_request_field_string_new(pce->identifier, pce->label, value, FALSE);
    +#if GAIM_MAJOR_VERSION >= 2
    + gaim_request_field_set_required(field, pce->required);
    +#endif
    + gaim_request_field_group_add_field(group, field);
    + }
    + field = gaim_request_field_string_new("alias", "Alias", chat->alias, FALSE);
    + gaim_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Chat";
    + break;
    + }
    + default:
    + request_title = "Edit";
    + break;
    + }
    +
    + gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-create-fields", fields, node);
    +
    + gaim_request_fields(plugin_self,
    + request_title, NULL, NULL,
    + fields, "OK", G_CALLBACK(buddyedit_editcomplete_cb), "Cancel", NULL, node);
    +}
    +
    +static void
    +buddy_menu_cb(GaimBlistNode * node, GList ** menu, void *data)
    +{
    +#if GAIM_MAJOR_VERSION < 2
    + GaimBlistNodeAction *action;
    +#else
    + GaimMenuAction *action;
    +#endif
    +
    + switch (node->type)
    + {
    + /* These are the types we handle */
    + case GAIM_BLIST_BUDDY_NODE:
    + case GAIM_BLIST_CONTACT_NODE:
    + case GAIM_BLIST_GROUP_NODE:
    + case GAIM_BLIST_CHAT_NODE:
    + break;
    +
    + case GAIM_BLIST_OTHER_NODE:
    + default:
    + return;
    + }
    +
    +#if GAIM_MAJOR_VERSION < 2
    + action = gaim_blist_node_action_new("Edit...", buddy_edit_cb, NULL);
    +#else
    + action = gaim_menu_action_new("Edit...", GAIM_CALLBACK(buddy_edit_cb), NULL, NULL);
    +#endif
    + *menu = g_list_append(*menu, action);
    +}
    +
    +static gboolean
    +plugin_load(GaimPlugin * plugin)
    +{
    +
    + plugin_self = plugin;
    +
    + void *blist_handle = gaim_blist_get_handle();
    +
    + gaim_signal_register(blist_handle, PLUGIN "-create-fields", /* Called when about to create dialog */
    + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
    + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */
    + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST));
    +
    + gaim_signal_register(blist_handle, PLUGIN "-submit-fields", /* Called when dialog submitted */
    + gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
    + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */
    + gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST));
    +
    + gaim_signal_connect(blist_handle, "blist-node-extended-menu", plugin,
    + GAIM_CALLBACK(buddy_menu_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +
    +static GaimPluginInfo info = {
    + GAIM_PLUGIN_MAGIC,
    + GAIM_MAJOR_VERSION,
    + GAIM_MINOR_VERSION,
    + GAIM_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + GAIM_PRIORITY_DEFAULT,
    +
    + PLUGIN,
    + "Buddy Edit Module",
    + G_STRINGIFY(PLUGIN_VERSION),
    +
    + "Enable editing of buddy properties",
    + "A plugin that adds an edit to to buddies allowing you to change various details you can't normally change. "
    + "It also provides a mechanism for subsequent plugins to add themselves to that dialog. ",
    + "Martijn van Oosterhout <kleptog@svana.org>",
    + "http://svana.org/kleptog/gaim/",
    +
    + plugin_load,
    + NULL,
    + NULL,
    +
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void
    +init_plugin(GaimPlugin * plugin)
    +{
    +}
    +
    +PURPLE_INIT_PLUGIN(buddyedit, init_plugin, info);
    --- a/buddytime/buddytime.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/buddytime/buddytime.c Mon Mar 24 05:08:00 2008 -0400
    @@ -1,6 +1,13 @@
    /*
    - * Plugin Name - Summary
    - * Copyright (C) 2004
    + * Buddy Time - Displays a buddy's local time
    + *
    + * A libpurple plugin that allows you to configure a timezone on a per-contact
    + * basis so it can display the localtime of your contact when a conversation
    + * starts. Convenient if you deal with contacts from many parts of the
    + * world.
    + *
    + * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org>
    *
    * This program is free software; you can redistribute it and/or
    * modify it under the terms of the GNU General Public License as
    @@ -14,247 +21,470 @@
    *
    * 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.
    + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
    + * 02111-1307, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    -#define PLUGIN_ID "gtk-plugin_pack-buddytime"
    -#define PLUGIN_STATIC_NAME "buddytime"
    -#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>"
    +#include "buddytime.h"
    +#define PLUGIN_STATIC_NAME CORE_PLUGIN_STATIC_NAME
    +#define PLUGIN_ID CORE_PLUGIN_ID
    -#include <gdk/gdk.h>
    -#include <gtk/gtk.h>
    +#define SETTING_NAME "timezone"
    +#define CONTROL_NAME PLUGIN_ID "-" SETTING_NAME
    -#include <blist.h>
    -#include <gtkutils.h>
    -#include <gtkplugin.h>
    -#include <request.h>
    +#include <glib.h>
    +#include <errno.h>
    +#include <ctype.h>
    +#include <string.h>
    -/******************************************************************************
    - * Defines
    - *****************************************************************************/
    -#define BT_NODE_SETTING "bt-timezone"
    +#include "conversation.h"
    +#include "core.h"
    +#include "debug.h"
    +#include "notify.h"
    +#include "plugin.h"
    +#include "request.h"
    +#include "util.h"
    +#include "value.h"
    +#include "version.h"
    -/******************************************************************************
    - * Structures
    - *****************************************************************************/
    -typedef struct {
    - PurpleBlistNode *node;
    - PurpleRequestField *timezone;
    - gpointer handle;
    -} BTDialog;
    +#include "gtkblist.h"
    +
    +#include "recurse.h"
    +
    +#define TIMEZONE_FLAG ((void*)1)
    +#define DISABLED_FLAG ((void*)2)
    +
    +BuddyTimeUiOps *ui_ops = NULL;
    +
    +static PurplePlugin *plugin_self;
    -typedef struct {
    - GtkWidget *ebox;
    - GtkWidget *label;
    - PurpleConversation *conv;
    - guint timeout;
    -} BTWidget;
    -
    -/******************************************************************************
    - * Globals
    - *****************************************************************************/
    -static GList *dialogs = NULL;
    -static GList *widgets = NULL;
    +/* Resolve specifies what the return value should mean:
    + *
    + * If TRUE, it's for display, we want to know the *effect* thus hiding the
    + * "none" value and going to going level to find the default
    + *
    + * If false, we only want what the user enter, thus the string "none" if
    + * that's what it is
    + *
    + * data is here so we can use this as a callback for IPC
    + */
    +static const char *
    +buddy_get_timezone(PurpleBlistNode * node, gboolean resolve, void *data)
    +{
    + PurpleBlistNode *datanode = NULL;
    + const char *timezone;
    -/******************************************************************************
    - * Main Stuff
    - *****************************************************************************/
    -static BTWidget *
    -bt_widget_new(PurpleConversation *conv) {
    - BTWidget *ret;
    + switch (node->type)
    + {
    + case PURPLE_BLIST_BUDDY_NODE:
    + datanode = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) node);
    + break;
    + case PURPLE_BLIST_CONTACT_NODE:
    + datanode = node;
    + break;
    + case PURPLE_BLIST_GROUP_NODE:
    + datanode = node;
    + break;
    + default:
    + return NULL;
    + }
    +
    + timezone = purple_blist_node_get_string(datanode, SETTING_NAME);
    - g_return_val_if_fail(conv, NULL);
    + if (!resolve)
    + return timezone;
    - ret = g_new0(BTWidget, 1);
    - ret->conv = conv;
    + /* The effect of "none" is to stop recursion */
    + if (timezone && strcmp(timezone, "none") == 0)
    + return NULL;
    +
    + if (timezone)
    + return timezone;
    - ret->ebox = gtk_event_box_new();
    + if (datanode->type == PURPLE_BLIST_CONTACT_NODE)
    + {
    + /* There is no purple_blist_contact_get_group(), though there probably should be */
    + datanode = datanode->parent;
    + timezone = purple_blist_node_get_string(datanode, SETTING_NAME);
    + }
    - ret->label = gtk_label_new("label");
    - gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label);
    + if (timezone && strcmp(timezone, "none") == 0)
    + return NULL;
    +
    + return timezone;
    }
    -/******************************************************************************
    - * Blist Stuff
    - *****************************************************************************/
    -static void
    -bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) {
    - BTDialog *dialog = (BTDialog *)data;
    +/* Calcuates the difference between two struct tm's. */
    +static double
    +timezone_calc_difference(struct tm *remote_tm, struct tm *tmp_tm)
    +{
    + int hours_diff = 0;
    + int minutes_diff = 0;
    - dialogs = g_list_remove(dialogs, dialog);
    - g_free(dialog);
    + /* Note this only works because the times are
    + * known to be within 24 hours of each other! */
    + if (remote_tm->tm_mday != tmp_tm->tm_mday)
    + hours_diff = 24;
    +
    + hours_diff += (remote_tm->tm_hour - tmp_tm->tm_hour);
    + minutes_diff = (remote_tm->tm_min - tmp_tm->tm_min);
    +
    + return (double)minutes_diff / 60.0 + hours_diff;
    }
    -static void
    -bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) {
    - BTDialog *dialog = (BTDialog *)data;
    +/* data is here so we can use this as a callback for IPC */
    +static int
    +timezone_get_time(const char *timezone, struct tm *tm, double *diff, void *data)
    +{
    + time_t now;
    + struct tm *tm_tmp;
    +#ifdef PRIVATE_TZLIB
    + struct state *tzinfo = timezone_load(timezone);
    +
    + if(!tzinfo)
    + return -1;
    +
    + time(&now);
    + localsub(&now, 0, tm, tzinfo);
    + free(tzinfo);
    +#else
    + const gchar *old_tz;
    +
    + /* Store the current TZ value. */
    + old_tz = g_getenv("TZ");
    - dialogs = g_list_remove(dialogs, dialog);
    - g_free(dialog);
    + g_setenv("TZ", timezone, TRUE);
    +
    + time(&now);
    + tm_tmp = localtime(&now);
    + *tm = *tm_tmp; /* Must copy, localtime uses local buffer */
    +
    + /* Reset the old TZ value. */
    + if (old_tz == NULL)
    + g_unsetenv("TZ");
    + else
    + g_setenv("TZ", old_tz, TRUE);
    +#endif
    +
    + /* Calculate user's localtime, and compare. If same, no output */
    + tm_tmp = localtime(&now);
    +
    + if (tm_tmp->tm_hour == tm->tm_hour && tm_tmp->tm_min == tm->tm_min)
    + return 1;
    +
    + *diff = timezone_calc_difference(tm, tm_tmp);
    + return 0;
    }
    static void
    -bt_show_dialog(PurpleBlistNode *node) {
    - BTDialog *dialog;
    - PurpleRequestFields *fields;
    - PurpleRequestFieldGroup *group;
    - PurpleAccount *account = NULL;
    - gint current = 0;
    +timezone_createconv_cb(PurpleConversation * conv, void *data)
    +{
    + const char *name;
    + PurpleBuddy *buddy;
    + struct tm tm;
    + const char *timezone;
    + double diff;
    + int ret;
    +
    + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM)
    + return;
    - dialog = g_new0(BTDialog, 1);
    + name = purple_conversation_get_name(conv);
    + buddy = purple_find_buddy(purple_conversation_get_account(conv), name);
    + if (!buddy)
    + return;
    - if(!dialog)
    - return;
    + timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE, NULL);
    - dialog->node = node;
    + if (!timezone)
    + return;
    +
    + ret = timezone_get_time(timezone, &tm, &diff, NULL);
    - current = purple_blist_node_get_int(node, BT_NODE_SETTING);
    -
    - /* TODO: set account from node */
    + if (ret == 0)
    + {
    + const char *text = purple_time_format(&tm);
    - /* build the request fields */
    - fields = purple_request_fields_new();
    - group = purple_request_field_group_new(NULL);
    - purple_request_fields_add_group(fields, group);
    + char *str;
    + if (diff < 0)
    + {
    + diff = 0 - diff;
    + str = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
    + "Remote Local Time: %s (%.4g hour behind)",
    + "Remote Local Time: %s (%.4g hours behind)", diff),
    + text, diff);
    + }
    + else
    + {
    + str = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
    + "Remote Local Time: %s (%.4g hour ahead)",
    + "Remote Local Time: %s (%.4g hours ahead)", diff),
    + text, diff);
    + }
    - dialog->timezone = purple_request_field_choice_new("timezone",
    - _("_Timezone"), 1);
    - purple_request_field_group_add_field(group, dialog->timezone);
    + purple_conversation_write(conv, PLUGIN_STATIC_NAME, str, PURPLE_MESSAGE_SYSTEM, time(NULL));
    - purple_request_field_choice_add(dialog->timezone, _("Clear setting"));
    + g_free(str);
    + }
    +}
    - purple_request_field_choice_add(dialog->timezone, _("GMT-12"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-11"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-10"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-9"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-8"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-7"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-6"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-5"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-4"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-3"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-2"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT-1"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+1"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+2"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+3"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+4"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+5"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+6"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+7"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+8"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+9"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+10"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+11"));
    - purple_request_field_choice_add(dialog->timezone, _("GMT+12"));
    +#if 0
    +static void
    +buddytimezone_submitfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data)
    +{
    + PurpleBlistNode *node;
    + PurpleRequestField *list;
    +
    + /* timezone stuff */
    + purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_submitfields_cb(%p,%p)\n", fields, data);
    +
    + switch (data->type)
    + {
    + case PURPLE_BLIST_BUDDY_NODE:
    + node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) data);
    + break;
    + case PURPLE_BLIST_CONTACT_NODE:
    + case PURPLE_BLIST_GROUP_NODE:
    + /* code handles either case */
    + node = data;
    + break;
    + case PURPLE_BLIST_CHAT_NODE:
    + case PURPLE_BLIST_OTHER_NODE:
    + default:
    + /* Not applicable */
    + return;
    + }
    -// purple_request_field_choice_set_default_value(dialog->timezone, current);
    -// purple_request_field_coice_set_value(dialog->timezone, current);
    + list = purple_request_fields_get_field(fields, CONTROL_NAME);
    + if (ui_ops != NULL && ui_ops->get_timezone_menu_selection != NULL)
    + {
    + const char *seldata = ui_ops->get_timezone_menu_selection(list->ui_data);
    + if (seldata == NULL)
    + purple_blist_node_remove_setting(node, SETTING_NAME);
    + else
    + purple_blist_node_set_string(node, SETTING_NAME, seldata);
    + }
    + else
    + {
    + const GList *sellist;
    + void *seldata = NULL;
    + sellist = purple_request_field_list_get_selected(list);
    + if (sellist)
    + seldata = purple_request_field_list_get_data(list, sellist->data);
    - /* TODO: set who from blist node */
    - dialog->handle =
    - purple_request_fields(NULL, _("Select timezone"),
    - NULL, "foo", fields,
    - _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb),
    - _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb),
    - account, NULL /* who */, NULL, dialog);
    -
    - dialogs = g_list_append(dialogs, dialog);
    + /* Otherwise, it's fixed value and this means deletion */
    + if (seldata == TIMEZONE_FLAG)
    + purple_blist_node_set_string(node, SETTING_NAME, sellist->data);
    + else if (seldata == DISABLED_FLAG)
    + purple_blist_node_set_string(node, SETTING_NAME, "none");
    + else
    + purple_blist_node_remove_setting(node, SETTING_NAME);
    + }
    +}
    +
    +static int
    +buddy_add_timezone_cb(char *filename, void *data)
    +{
    + PurpleRequestField *field = (PurpleRequestField *) data;
    + if (isupper(filename[0]))
    + purple_request_field_list_add(field, filename, TIMEZONE_FLAG);
    + return 0;
    }
    static void
    -bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) {
    - bt_show_dialog(node);
    +buddytimezone_createfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data)
    +{
    + purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_createfields_cb(%p,%p)\n", fields, data);
    + PurpleRequestField *field;
    + PurpleRequestFieldGroup *group;
    + const char *timezone;
    + gboolean is_default;
    +
    + switch (data->type)
    + {
    + case PURPLE_BLIST_BUDDY_NODE:
    + case PURPLE_BLIST_CONTACT_NODE:
    + is_default = FALSE;
    + break;
    + case PURPLE_BLIST_GROUP_NODE:
    + is_default = TRUE;
    + break;
    + case PURPLE_BLIST_CHAT_NODE:
    + case PURPLE_BLIST_OTHER_NODE:
    + default:
    + /* Not applicable */
    + return;
    + }
    +
    + group = purple_request_field_group_new(NULL);
    + purple_request_fields_add_group(fields, group);
    +
    + timezone = buddy_get_timezone(data, FALSE, NULL);
    +
    + if (ui_ops != NULL && ui_ops->create_menu)
    + {
    + field =
    + purple_request_field_new(CONTROL_NAME,
    + is_default ? "Default timezone for group" : "Timezone of contact",
    + PURPLE_REQUEST_FIELD_LIST);
    + field->ui_data = ui_ops->create_menu(timezone);
    + }
    + else
    + {
    + field =
    + purple_request_field_list_new(CONTROL_NAME,
    + is_default ? "Default timezone for group" :
    + "Timezone of contact (type to select)");
    + purple_request_field_list_set_multi_select(field, FALSE);
    + purple_request_field_list_add(field, "<Default>", "");
    + purple_request_field_list_add(field, "<Disabled>", DISABLED_FLAG);
    +
    + recurse_directory("/usr/share/zoneinfo/", buddy_add_timezone_cb, field);
    +
    + if (timezone)
    + {
    + if (strcmp(timezone, "none") == 0)
    + purple_request_field_list_add_selected(field, "<Disabled>");
    + else
    + purple_request_field_list_add_selected(field, timezone);
    + }
    + else
    + purple_request_field_list_add_selected(field, "<Default>");
    + }
    +
    + purple_request_field_group_add_field(group, field);
    +}
    +#endif
    +
    +static void
    +marshal_POINTER__POINTER_BOOL(PurpleCallback cb, va_list args, void *data,
    + void **return_val)
    +{
    + gpointer ret_val;
    + void *arg1 = va_arg(args, void *);
    + gboolean arg2 = va_arg(args, gboolean);
    +
    + ret_val = ((gpointer (*)(void *, gboolean, void *))cb)(arg1, arg2, data);
    +
    + if (return_val != NULL)
    + *return_val = ret_val;
    }
    static void
    -bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) {
    - PurpleMenuAction *action;
    -
    - if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
    - return;
    +marshal_POINTER__POINTER_POINTER_POINTER(PurpleCallback cb, va_list args, void *data,
    + void **return_val)
    +{
    + gpointer ret_val;
    + void *arg1 = va_arg(args, void *);
    + void *arg2 = va_arg(args, void *);
    + void *arg3 = va_arg(args, void *);
    - /* ignore chats and groups */
    - if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node))
    - return;
    -
    - action = purple_menu_action_new(_("Timezone"),
    - PURPLE_CALLBACK(bt_edit_timezone_cb),
    - NULL,
    - NULL);
    - (*menu) = g_list_append(*menu, action);
    + ret_val = ((gpointer (*)(void *, void *, void *, void *))cb)(arg1, arg2, arg3, data);
    +
    + if (return_val != NULL)
    + *return_val = ret_val;
    }
    -/******************************************************************************
    - * Conversation stuff
    - *****************************************************************************/
    +static gboolean
    +load_ui_plugin(gpointer data)
    +{
    + char *ui_plugin_id;
    + PurplePlugin *ui_plugin;
    +
    + ui_plugin_id = g_strconcat(purple_core_get_ui(), "-", PLUGIN_STATIC_NAME, NULL);
    + ui_plugin = purple_plugins_find_with_id(ui_plugin_id);
    + if (ui_plugin != NULL)
    + {
    + if (!purple_plugin_load(ui_plugin))
    + {
    + purple_notify_error(ui_plugin, NULL, _("Failed to load the Buddy Timezone UI."),
    + ui_plugin->error ? ui_plugin->error : "");
    + }
    + }
    +
    + g_free(ui_plugin_id);
    +
    + return FALSE;
    +}
    -/******************************************************************************
    - * Plugin Stuff
    - *****************************************************************************/
    static gboolean
    -plugin_load(PurplePlugin *plugin) {
    - purple_signal_connect(purple_blist_get_handle(),
    - "blist-node-extended-menu",
    - plugin,
    - PURPLE_CALLBACK(bt_blist_drawing_menu_cb),
    - NULL);
    +plugin_load(PurplePlugin * plugin)
    +{
    + plugin_self = plugin;
    +
    + purple_signal_connect(purple_conversations_get_handle(), "conversation-created", plugin,
    + PURPLE_CALLBACK(timezone_createconv_cb), NULL);
    +
    + purple_plugin_ipc_register(plugin, BUDDYTIME_BUDDY_GET_TIMEZONE,
    + PURPLE_CALLBACK(buddy_get_timezone),
    + marshal_POINTER__POINTER_BOOL,
    + purple_value_new(PURPLE_TYPE_STRING),
    + 2,
    + purple_value_new(PURPLE_TYPE_SUBTYPE,
    + PURPLE_SUBTYPE_BLIST_NODE),
    + purple_value_new(PURPLE_TYPE_BOOLEAN));
    +
    + purple_plugin_ipc_register(plugin, BUDDYTIME_TIMEZONE_GET_TIME,
    + PURPLE_CALLBACK(timezone_get_time),
    + marshal_POINTER__POINTER_POINTER_POINTER,
    + purple_value_new(PURPLE_TYPE_INT),
    + 2,
    + purple_value_new(PURPLE_TYPE_POINTER),
    + purple_value_new(PURPLE_TYPE_POINTER));
    +
    + /* This is done as an idle callback to avoid an infinite loop
    + * when we try to load the UI plugin which depends on this plugin
    + * which isn't officially loaded yet. */
    + purple_timeout_add(0, load_ui_plugin, NULL);
    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 */
    - PIDGIN_PLUGIN_TYPE, /* 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 PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + 0,
    + PURPLE_PLUGIN_STANDARD, /**< type */
    + NULL, /**< ui_requirement */
    + 0, /**< flags */
    + NULL, /**< dependencies */
    + PURPLE_PRIORITY_DEFAULT, /**< priority */
    + PLUGIN_ID, /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /**< summary */
    + NULL, /**< description */
    + PLUGIN_AUTHOR, /**< author */
    + PP_WEBSITE, /**< homepage */
    + plugin_load, /**< load */
    + NULL, /**< 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) {
    +init_plugin(PurplePlugin * plugin)
    +{
    #ifdef ENABLE_NLS
    bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    #endif /* ENABLE_NLS */
    info.name = _("Buddy Time");
    - info.summary = _("summary");
    - info.description = _("description");
    + info.summary = _("Quickly see the local time of a buddy");
    + info.description = _("Quickly see the local time of a buddy");
    }
    -PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info);
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/buddytime.c.old Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,260 @@
    +/*
    + * 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 "gtk-plugin_pack-buddytime"
    +#define PLUGIN_STATIC_NAME "buddytime"
    +#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>"
    +
    +#include <gdk/gdk.h>
    +#include <gtk/gtk.h>
    +
    +#include <blist.h>
    +#include <gtkutils.h>
    +#include <gtkplugin.h>
    +#include <request.h>
    +
    +/******************************************************************************
    + * Defines
    + *****************************************************************************/
    +#define BT_NODE_SETTING "bt-timezone"
    +
    +/******************************************************************************
    + * Structures
    + *****************************************************************************/
    +typedef struct {
    + PurpleBlistNode *node;
    + PurpleRequestField *timezone;
    + gpointer handle;
    +} BTDialog;
    +
    +typedef struct {
    + GtkWidget *ebox;
    + GtkWidget *label;
    + PurpleConversation *conv;
    + guint timeout;
    +} BTWidget;
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static GList *dialogs = NULL;
    +static GList *widgets = NULL;
    +
    +/******************************************************************************
    + * Main Stuff
    + *****************************************************************************/
    +static BTWidget *
    +bt_widget_new(PurpleConversation *conv) {
    + BTWidget *ret;
    +
    + g_return_val_if_fail(conv, NULL);
    +
    + ret = g_new0(BTWidget, 1);
    + ret->conv = conv;
    +
    + ret->ebox = gtk_event_box_new();
    +
    + ret->label = gtk_label_new("label");
    + gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label);
    +}
    +
    +/******************************************************************************
    + * Blist Stuff
    + *****************************************************************************/
    +static void
    +bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) {
    + BTDialog *dialog = (BTDialog *)data;
    +
    + dialogs = g_list_remove(dialogs, dialog);
    + g_free(dialog);
    +}
    +
    +static void
    +bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) {
    + BTDialog *dialog = (BTDialog *)data;
    +
    + dialogs = g_list_remove(dialogs, dialog);
    + g_free(dialog);
    +}
    +
    +static void
    +bt_show_dialog(PurpleBlistNode *node) {
    + BTDialog *dialog;
    + PurpleRequestFields *fields;
    + PurpleRequestFieldGroup *group;
    + PurpleAccount *account = NULL;
    + gint current = 0;
    +
    + dialog = g_new0(BTDialog, 1);
    +
    + if(!dialog)
    + return;
    +
    + dialog->node = node;
    +
    + current = purple_blist_node_get_int(node, BT_NODE_SETTING);
    +
    + /* TODO: set account from node */
    +
    + /* build the request fields */
    + fields = purple_request_fields_new();
    + group = purple_request_field_group_new(NULL);
    + purple_request_fields_add_group(fields, group);
    +
    + dialog->timezone = purple_request_field_choice_new("timezone",
    + _("_Timezone"), 1);
    + purple_request_field_group_add_field(group, dialog->timezone);
    +
    + purple_request_field_choice_add(dialog->timezone, _("Clear setting"));
    +
    + purple_request_field_choice_add(dialog->timezone, _("GMT-12"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-11"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-10"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-9"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-8"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-7"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-6"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-5"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-4"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-3"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-2"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT-1"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+1"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+2"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+3"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+4"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+5"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+6"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+7"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+8"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+9"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+10"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+11"));
    + purple_request_field_choice_add(dialog->timezone, _("GMT+12"));
    +
    +// purple_request_field_choice_set_default_value(dialog->timezone, current);
    +// purple_request_field_coice_set_value(dialog->timezone, current);
    +
    + /* TODO: set who from blist node */
    + dialog->handle =
    + purple_request_fields(NULL, _("Select timezone"),
    + NULL, "foo", fields,
    + _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb),
    + _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb),
    + account, NULL /* who */, NULL, dialog);
    +
    + dialogs = g_list_append(dialogs, dialog);
    +}
    +
    +static void
    +bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) {
    + bt_show_dialog(node);
    +}
    +
    +static void
    +bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) {
    + PurpleMenuAction *action;
    +
    + if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
    + return;
    +
    + /* ignore chats and groups */
    + if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node))
    + return;
    +
    + action = purple_menu_action_new(_("Timezone"),
    + PURPLE_CALLBACK(bt_edit_timezone_cb),
    + NULL,
    + NULL);
    + (*menu) = g_list_append(*menu, action);
    +}
    +
    +/******************************************************************************
    + * Conversation stuff
    + *****************************************************************************/
    +
    +
    +/******************************************************************************
    + * Plugin Stuff
    + *****************************************************************************/
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + purple_signal_connect(purple_blist_get_handle(),
    + "blist-node-extended-menu",
    + plugin,
    + PURPLE_CALLBACK(bt_blist_drawing_menu_cb),
    + NULL);
    +
    + 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 */
    + PIDGIN_PLUGIN_TYPE, /* 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 = _("Buddy Time");
    + info.summary = _("summary");
    + info.description = _("description");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/buddytime.h Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,52 @@
    +/*
    + * Buddy Time - Displays a buddy's local time
    + *
    + * A libpurple plugin that allows you to configure a timezone on a per-contact
    + * basis so it can display the localtime of your contact when a conversation
    + * starts. Convenient if you deal with contacts from many parts of the
    + * world.
    + *
    + * Copyright (C) 2006-2007, Richard Laager <rlaager@users.sf.net>
    + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org>
    + *
    + * This program is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU General Public License as
    + * published by the Free Software Foundation; either version 2 of the
    + * License, or (at your option) any later version.
    + *
    + * This program is distributed in the hope that it will be useful, but
    + * WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with this program; if not, write to the Free Software
    + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
    + * 02111-1307, USA.
    + */
    +#ifndef _BUDDYTIME_H_
    +#define _BUDDYTIME_H_
    +
    +#define CORE_PLUGIN_STATIC_NAME "buddytime"
    +#define CORE_PLUGIN_ID "core-kleptog-" CORE_PLUGIN_STATIC_NAME
    +
    +#define PLUGIN_AUTHOR "Martijn van Oosterhout <kleptog@svana.org>" \
    + "\n\t\t\tRichard Laager <rlaager@pidgin.im>"
    +
    +#define BUDDYTIME_BUDDY_GET_TIMEZONE "buddy_get_timezone"
    +#define BUDDYTIME_TIMEZONE_GET_TIME "timezone_get_time"
    +
    +typedef struct _BuddyTimeUiOps BuddyTimeUiOps;
    +
    +struct _BuddyTimeUiOps
    +{
    + void *(*create_menu)(const char *selected); /**< Creates a timezone menu. */
    + const char * (*get_timezone_menu_selection)(void *ui_data); /**< Retrieves the menu setting. */
    +
    + void (*_buddytime_reserved1)(void);
    + void (*_buddytime_reserved2)(void);
    + void (*_buddytime_reserved3)(void);
    + void (*_buddytime_reserved4)(void);
    +};
    +
    +#endif /* _BUDDYTIME_H_ */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/gtkbuddytime.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,150 @@
    +/*
    + * Buddy Time - Displays a buddy's local time
    + *
    + * A libpurple plugin that allows you to configure a timezone on a per-contact
    + * basis so it can display the localtime of your contact when a conversation
    + * starts. Convenient if you deal with contacts from many parts of the
    + * world.
    + *
    + * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im>
    + * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org>
    + *
    + * This program is free software; you can redistribute it and/or
    + * modify it under the terms of the GNU General Public License as
    + * published by the Free Software Foundation; either version 2 of the
    + * License, or (at your option) any later version.
    + *
    + * This program is distributed in the hope that it will be useful, but
    + * WITHOUT ANY WARRANTY; without even the implied warranty of
    + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    + * General Public License for more details.
    + *
    + * You should have received a copy of the GNU General Public License
    + * along with this program; if not, write to the Free Software
    + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
    + * 02111-1307, USA.
    + */
    +
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    +#include "../common/pp_internal.h"
    +
    +#define PLUGIN_STATIC_NAME "gtkbuddytime"
    +#define PLUGIN_ID PIDGIN_UI "-buddytime"
    +
    +#include "buddytime.h"
    +
    +#include <glib.h>
    +
    +#include "plugin.h"
    +#include "version.h"
    +
    +#include "gtkblist.h"
    +#include "gtkplugin.h"
    +
    +PurplePlugin *core_plugin = NULL;
    +
    +static void
    +buddytimezone_tooltip_cb(PurpleBlistNode * node, char **text, gboolean full, void *data)
    +{
    + char *newtext;
    + const char *timezone;
    + struct tm tm;
    + double diff;
    + int ret;
    +
    + if (!full)
    + return;
    +
    + timezone = purple_plugin_ipc_call(core_plugin, BUDDYTIME_BUDDY_GET_TIMEZONE,
    + NULL, node, TRUE);
    +
    + if (!timezone)
    + return;
    +
    + ret = GPOINTER_TO_INT(purple_plugin_ipc_call(core_plugin, BUDDYTIME_TIMEZONE_GET_TIME,
    + NULL, timezone, &tm, &diff));
    + if (ret < 0)
    + newtext = g_strdup_printf("%s\n<b>Timezone:</b> %s (error)", *text, timezone);
    + else if (ret == 0)
    + {
    + const char *timetext = purple_time_format(&tm);
    +
    + if (diff < 0)
    + {
    + diff = 0 - diff;
    + newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
    + "%s\n<b>Local Time:</b> %s (%.4g hour behind)",
    + "%s\n<b>Local Time:</b> %s (%.4g hours behind)", diff),
    + *text, timetext, diff);
    + }
    + else
    + {
    + newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
    + "%s\n<b>Local Time:</b> %s (%.4g hour ahead)",
    + "%s\n<b>Local Time:</b> %s (%.4g hours ahead)", diff),
    + *text, timetext, diff); }
    + }
    + else
    + return;
    +
    + g_free(*text);
    + *text = newtext;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin * plugin)
    +{
    + purple_signal_connect(pidgin_blist_get_handle(), "drawing-tooltip", plugin,
    + PURPLE_CALLBACK(buddytimezone_tooltip_cb), NULL);
    +
    + core_plugin = purple_plugins_find_with_id(CORE_PLUGIN_ID);
    +
    + return (core_plugin != NULL);
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + 0,
    + PURPLE_PLUGIN_STANDARD, /**< type */
    + PIDGIN_PLUGIN_TYPE, /**< ui_requirement */
    + PURPLE_PLUGIN_FLAG_INVISIBLE, /**< flags */
    + NULL, /**< dependencies */
    + PURPLE_PRIORITY_DEFAULT, /**< priority */
    + PLUGIN_ID, /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /**< summary */
    + NULL, /**< description */
    + PLUGIN_AUTHOR, /**< author */
    + PP_WEBSITE, /**< homepage */
    + plugin_load, /**< load */
    + NULL, /**< 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)
    +{
    + info.dependencies = g_list_append(info.dependencies, CORE_PLUGIN_ID);
    +
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("Buddy Time (Pidgin UI)");
    + info.summary = _("Pidgin user interface for the Buddy Time plugin.");
    + info.description = _("Pidgin user interface for the Buddy Time plugin.");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info);
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/gtktimezone.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,237 @@
    +/*************************************************************************
    + * GTK Timezone widget module
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *
    + * This module creates the GTK widget used to select timezones. It's here to
    + * clearly seperate the GTK stuff from the plugin itself.
    + *************************************************************************/
    +
    +#include <gtk/gtk.h>
    +#include <string.h>
    +#include <ctype.h>
    +#include "recurse.h"
    +
    +#define DISABLED_STRING "<Disabled>"
    +#define DEFAULT_STRING "<Default>"
    +#define MORE_STRING "More..."
    +
    +struct nodestate
    +{
    + GtkWidget *submenu;
    + gchar *string;
    +};
    +
    +struct state
    +{
    + GtkWidget *base;
    + GtkWidget *extra;
    + int currdepth;
    + struct nodestate stack[4];
    +};
    +
    +static inline const char *
    +menuitem_get_label(GtkMenuItem * menuitem)
    +{
    + return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
    +}
    +
    +static GtkMenuItem *
    +menu_get_first_menuitem(GtkWidget * menu)
    +{
    + GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
    + GtkMenuItem *selection = GTK_MENU_ITEM(g_list_nth_data(list, 0));
    + g_list_free(list);
    + return selection;
    +}
    +
    +static int
    +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu)
    +{
    + const char *label = menuitem_get_label(menuitem);
    +
    + if(label[0] == '<')
    + {
    + GtkWidget *selection;
    + gchar *selstring;
    +
    + if(strcmp(label, DEFAULT_STRING) == 0)
    + selstring = "";
    + else if(strcmp(label, DISABLED_STRING) == 0)
    + selstring = "none";
    +
    + selection = GTK_WIDGET(menu_get_first_menuitem(menu));
    + gtk_widget_hide(selection);
    + }
    + else
    + {
    + char *str = g_strdup(label);
    +
    + GtkWidget *parent;
    +
    + for (;;)
    + {
    + GtkMenuItem *parentitem;
    + const char *label2;
    + char *temp;
    +
    + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem));
    + if(menu == parent)
    + break;
    +
    + parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent)));
    + label2 = menuitem_get_label(parentitem);
    + if(strcmp(label2, MORE_STRING) != 0)
    + {
    + temp = g_strconcat(label2, "/", str, NULL);
    + g_free(str);
    + str = temp;
    + }
    +
    + menuitem = parentitem;
    + }
    + {
    + GtkLabel *label;
    +
    + GtkMenuItem *selection = menu_get_first_menuitem(menu);
    + GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu)));
    +
    + label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection)));
    +
    + gtk_label_set_text(label, str);
    + gtk_widget_show(GTK_WIDGET(selection));
    + gtk_option_menu_set_history(optionmenu, 0);
    +
    + printf("optionmenu=%p, menu=%p, menuitem=%p, label=%p\n", optionmenu, menu, selection,
    + label);
    + g_free(str);
    + }
    + }
    + return 0;
    +}
    +
    +static int
    +make_menu_cb(char *path, struct state *state)
    +{
    + int i, j;
    +
    + char **elements;
    +
    + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */
    + if(!isupper(path[0]))
    + return 0;
    +
    + elements = g_strsplit(path, "/", 4);
    +
    + for (i = 0; i < state->currdepth && state->stack[i].string; i++)
    + {
    + if(strcmp(elements[i], state->stack[i].string) != 0)
    + break;
    + }
    + /* i is now the index of the first non-matching element, so free the rest */
    + for (j = i; j < state->currdepth; j++)
    + g_free(state->stack[j].string);
    + state->currdepth = i;
    +
    + while (elements[i])
    + {
    + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu;
    + GtkWidget *menuitem;
    +
    + if(i == 0 && elements[1] == NULL)
    + parent = state->extra;
    +
    + menuitem = gtk_menu_item_new_with_label(elements[i]);
    + gtk_menu_append(parent, menuitem);
    +
    + if(elements[i + 1] != NULL) /* Has submenu */
    + {
    + state->stack[i].submenu = gtk_menu_new();
    + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu);
    +
    + state->currdepth++;
    + state->stack[i].string = g_strdup(elements[i]);
    + }
    + else
    + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb),
    + state->base);
    +
    + i++;
    + }
    + g_strfreev(elements);
    + return 0;
    +}
    +
    +void *
    +make_timezone_menu(const char *selected)
    +{
    + int i;
    + struct state state;
    +
    + GtkWidget *menu;
    + GtkWidget *optionmenu, *menuitem, *selection;
    +
    + if(selected == NULL)
    + selected = "";
    +
    + menu = gtk_menu_new();
    + menuitem = gtk_menu_item_new_with_label(selected);
    + gtk_menu_append(menu, menuitem);
    + selection = menuitem;
    +
    + menuitem = gtk_menu_item_new_with_label(DISABLED_STRING);
    + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
    + gtk_menu_append(menu, menuitem);
    + menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING);
    + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
    + gtk_menu_append(menu, menuitem);
    + menuitem = gtk_menu_item_new_with_label(MORE_STRING);
    + gtk_menu_append(menu, menuitem);
    +
    + state.base = menu;
    + state.extra = gtk_menu_new();
    + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra);
    +
    + state.currdepth = 0;
    +
    + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state);
    +
    + for (i = 0; i < state.currdepth; i++)
    + g_free(state.stack[i].string);
    +
    + optionmenu = gtk_option_menu_new();
    + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
    + gtk_widget_show_all(optionmenu);
    +
    + if(strcmp(selected, "") == 0)
    + {
    + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2);
    + gtk_widget_hide(selection);
    + }
    + else if(strcmp(selected, "none") == 0)
    + {
    + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1);
    + gtk_widget_hide(selection);
    + }
    + else
    + {
    + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0);
    + }
    +
    + return optionmenu;
    +}
    +
    +const char *
    +get_timezone_menu_selection(void *widget)
    +{
    + GtkOptionMenu *menu = GTK_OPTION_MENU(widget);
    +
    + int sel = gtk_option_menu_get_history(menu);
    + if(sel == 2) /* Default */
    + return NULL;
    + if(sel == 1) /* Disabled */
    + return "none";
    +
    + GtkLabel *l = GTK_LABEL(gtk_bin_get_child(GTK_BIN(menu)));
    + return gtk_label_get_text(l);
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/gtktimezonetest.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,343 @@
    +/*************************************************************************
    + * GTK Timezone test program
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *
    + * A test program to play with different ways that user could select from
    + * the huge list of timezones. Eventually things tested here should migrate
    + * to the module itself.
    + *************************************************************************/
    +
    +#include <gtk/gtk.h>
    +#include <string.h>
    +#include <ctype.h>
    +#include "recurse.h"
    +
    +#define PACKAGE "Hello World"
    +#define VERSION "0.1"
    +
    +#define DISABLED_STRING "<Disabled>"
    +#define DEFAULT_STRING "<Default>"
    +#define MORE_STRING "More..."
    +/*
    + * Terminate the main loop.
    + */
    +static void
    +on_destroy(GtkWidget * widget, gpointer data)
    +{
    + gtk_main_quit();
    +}
    +
    +enum
    +{
    + STRING_COLUMN,
    + N_COLUMNS
    +};
    +
    +struct nodestate
    +{
    +#ifdef USE_COMBOBOX
    + GtkTreeIter iter;
    +#else
    + GtkWidget *submenu;
    +#endif
    + gchar *string;
    +};
    +
    +struct state
    +{
    +#ifdef USE_COMBOBOX
    + GtkTreeStore *store;
    + GtkTreeIter *extra;
    +#else
    + GtkWidget *base;
    + GtkWidget *extra;
    +#endif
    + int currdepth;
    + struct nodestate stack[4];
    +};
    +
    +static inline const char *
    +menuitem_get_label(GtkMenuItem * menuitem)
    +{
    + return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
    +}
    +
    +static GtkWidget *
    +menu_get_first_menuitem(GtkMenu * menu)
    +{
    + GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
    + GtkWidget *selection = GTK_WIDGET(g_list_nth_data(list, 0));
    + g_list_free(list);
    + return selection;
    +}
    +
    +int
    +menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu)
    +{
    + const char *label = menuitem_get_label(menuitem);
    +
    +// printf( "menuitem = %s(%p), menu = %s(%p)\n", G_OBJECT_TYPE_NAME(menuitem), menuitem, G_OBJECT_TYPE_NAME(menu), menu );
    +
    + if(label[0] == '<')
    + {
    + GtkWidget *selection;
    + gchar *selstring;
    +
    + if(strcmp(label, DEFAULT_STRING) == 0)
    + selstring = "";
    + else if(strcmp(label, DISABLED_STRING) == 0)
    + selstring = "none";
    +
    + selection = menu_get_first_menuitem(GTK_MENU(menu));
    + gtk_widget_hide(selection);
    + }
    + else
    + {
    + char *str = g_strdup(label);
    +
    + GtkWidget *parent;
    +
    + for (;;)
    + {
    + GtkMenuItem *parentitem;
    + const char *label2;
    + char *temp;
    +
    + parent = gtk_widget_get_parent(GTK_WIDGET(menuitem));
    +// printf( "parent = %s(%p)\n", G_OBJECT_TYPE_NAME(parent), parent);
    + if(menu == parent)
    + break;
    +
    + parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent)));
    +// printf( "parentitem = %s(%p)\n", G_OBJECT_TYPE_NAME(parentitem), parentitem);
    + label2 = menuitem_get_label(parentitem);
    + if(strcmp(label2, MORE_STRING) != 0)
    + {
    + temp = g_strconcat(label2, "/", str, NULL);
    + g_free(str);
    + str = temp;
    + }
    +
    + menuitem = parentitem;
    + }
    + {
    + GtkLabel *label;
    +
    + GtkWidget *selection = menu_get_first_menuitem(GTK_MENU(menu));
    + GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu)));
    +
    + label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection)));
    +
    + gtk_label_set_text(label, str);
    + gtk_widget_show(GTK_WIDGET(selection));
    + gtk_option_menu_set_history(optionmenu, 0);
    + g_free(str);
    + }
    + }
    + return 0;
    +}
    +
    +int
    +make_menu_cb(char *path, struct state *state)
    +{
    + int i, j;
    +
    + char **elements;
    +
    + /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */
    + if(!isupper(path[0]))
    + return 0;
    +
    + elements = g_strsplit(path, "/", 4);
    +
    + for (i = 0; i < state->currdepth && state->stack[i].string; i++)
    + {
    + if(strcmp(elements[i], state->stack[i].string) != 0)
    + break;
    + }
    + /* i is now the index of the first non-matching element, so free the rest */
    + for (j = i; j < state->currdepth; j++)
    + g_free(state->stack[j].string);
    + state->currdepth = i;
    +
    + while (elements[i])
    + {
    +#ifdef USE_COMBOBOX
    + GtkTreeIter *parent = (i == 0) ? NULL : &state->stack[i - 1].iter;
    +#else
    + GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu;
    + GtkWidget *menuitem;
    +#endif
    +
    + if(i == 0 && elements[1] == NULL)
    + parent = state->extra;
    +
    +#ifdef USE_COMBOBOX
    + gtk_tree_store_append(state->store, &state->stack[i].iter, parent);
    + gtk_tree_store_set(state->store, &state->stack[i].iter, STRING_COLUMN, elements[i], -1);
    + state->stack[i].string = g_strdup(elements[i]);
    + state->currdepth++;
    +#else
    + menuitem = gtk_menu_item_new_with_label(elements[i]);
    + gtk_menu_append(parent, menuitem);
    +
    + if(elements[i + 1] != NULL) /* Has submenu */
    + {
    + state->stack[i].submenu = gtk_menu_new();
    + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu);
    +
    + state->currdepth++;
    + state->stack[i].string = g_strdup(elements[i]);
    + }
    + else
    + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb),
    + state->base);
    +
    +#endif
    +
    + i++;
    + }
    + g_strfreev(elements);
    + return 0;
    +}
    +
    +GtkWidget *
    +make_menu2(char *selected)
    +{
    + int i;
    + struct state state;
    +
    +#ifdef USE_COMBOBOX
    + GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, /* Total number of columns */
    + G_TYPE_STRING); /* Timezone */
    + GtkWidget *tree;
    +
    + GtkCellRenderer *renderer;
    + GtkTreeIter iter1, iter2;
    +
    + gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */
    + gtk_tree_store_set(store, &iter1, STRING_COLUMN, DISABLED_STRING, -1);
    + gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */
    + gtk_tree_store_set(store, &iter1, STRING_COLUMN, DEFAULT_STRING, -1);
    + gtk_tree_store_append(store, &iter2, &iter1);
    + gtk_tree_store_set(store, &iter1, STRING_COLUMN, MORE_STRING, -1);
    +
    + state.store = store;
    + state.extra = &iter1;
    +#else
    +
    + GtkWidget *menu;
    + GtkWidget *optionmenu, *menuitem, *selection;
    +
    + menu = gtk_menu_new();
    + menuitem = gtk_menu_item_new_with_label(selected);
    + gtk_menu_append(menu, menuitem);
    + selection = menuitem;
    +
    + menuitem = gtk_menu_item_new_with_label(DISABLED_STRING);
    + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
    + gtk_menu_append(menu, menuitem);
    + menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING);
    + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
    + gtk_menu_append(menu, menuitem);
    + menuitem = gtk_menu_item_new_with_label(MORE_STRING);
    + gtk_menu_append(menu, menuitem);
    +
    + state.base = menu;
    + state.extra = gtk_menu_new();
    + gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra);
    +#endif
    + state.currdepth = 0;
    +
    + recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state);
    +
    + for (i = 0; i < state.currdepth; i++)
    + g_free(state.stack[i].string);
    +
    +#ifdef USE_COMBOBOX
    + tree = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
    +
    + renderer = gtk_cell_renderer_text_new();
    + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(tree), renderer, TRUE);
    + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(tree), renderer, "text", 0, NULL);
    +
    + gtk_widget_show_all(tree);
    + return tree;
    +#else
    + optionmenu = gtk_option_menu_new();
    + gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
    + gtk_widget_show_all(optionmenu);
    +
    + if(strcmp(selected, "") == 0)
    + {
    + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2);
    + gtk_widget_hide(selection);
    + }
    + else if(strcmp(selected, "none") == 0)
    + {
    + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1);
    + gtk_widget_hide(selection);
    + }
    + else
    + {
    + gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0);
    + }
    +
    + return optionmenu;
    +#endif
    +
    +}
    +
    +int
    +main(int argc, char *argv[])
    +{
    + GtkWidget *window;
    + GtkWidget *label;
    + GtkWidget *menu;
    + GtkWidget *frame;
    +
    + gtk_init(&argc, &argv);
    +
    + /* create the main, top level, window */
    + window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    +
    + /* give the window a 20px wide border */
    + gtk_container_set_border_width(GTK_CONTAINER(window), 20);
    +
    + /* give it the title */
    + gtk_window_set_title(GTK_WINDOW(window), PACKAGE " " VERSION);
    +
    + /* open it a bit wider so that both the label and title show up */
    + gtk_window_set_default_size(GTK_WINDOW(window), 200, 50);
    +
    + /* Connect the destroy event of the window with our on_destroy function
    + * When the window is about to be destroyed we get a notificaiton and
    + * stop the main GTK loop
    + */
    + g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_destroy), NULL);
    +
    + /* Create the "Hello, World" label */
    + label = gtk_label_new("Select a timezone:");
    + gtk_widget_show(label);
    +
    + frame = gtk_vbox_new(FALSE, 0);
    + gtk_widget_show(frame);
    +
    + /* and insert it into the main window */
    + gtk_container_add(GTK_CONTAINER(window), frame);
    + gtk_container_add(GTK_CONTAINER(frame), label);
    +
    + menu = make_menu2("none");
    +
    + gtk_container_add(GTK_CONTAINER(frame), menu);
    + gtk_widget_show(menu);
    +
    + /* make sure that everything, window and label, are visible */
    + gtk_widget_show(window);
    +
    + /* start the main loop */
    + gtk_main();
    +
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/localtime.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,1250 @@
    +/*************************************************************************
    + * Timezone Module
    + * copied from the Olson Timezone code, licence unchanged.
    + * by Martijn van Oosterhout <kleptog@svana.org> April 2006
    + * Original Licence below (public domain).
    + *
    + * This code has been copied from the Olson Timezone code, but heavily
    + * adapted to meet my needs. In particular, you can load multiple timezones
    + * and specify which timezone to convert with.
    + *************************************************************************/
    +
    +/*
    +** This file is in the public domain, so clarified as of
    +** 1996-06-05 by Arthur David Olson.
    +*/
    +
    +#define TM_GMTOFF tm_gmtoff
    +#define TM_ZONE tm_zone
    +
    +/*
    +** Leap second handling from Bradley White.
    +** POSIX-style TZ environment variable handling from Guy Harris.
    +*/
    +
    +/*LINTLIBRARY*/
    +
    +#include "private.h"
    +#include "tzfile.h"
    +#include "fcntl.h"
    +#include "float.h" /* for FLT_MAX and DBL_MAX */
    +
    +#ifndef TZ_ABBR_MAX_LEN
    +#define TZ_ABBR_MAX_LEN 16
    +#endif /* !defined TZ_ABBR_MAX_LEN */
    +
    +#ifndef TZ_ABBR_CHAR_SET
    +#define TZ_ABBR_CHAR_SET \
    + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._"
    +#endif /* !defined TZ_ABBR_CHAR_SET */
    +
    +#ifndef TZ_ABBR_ERR_CHAR
    +#define TZ_ABBR_ERR_CHAR '_'
    +#endif /* !defined TZ_ABBR_ERR_CHAR */
    +
    +/*
    +** SunOS 4.1.1 headers lack O_BINARY.
    +*/
    +
    +#ifdef O_BINARY
    +#define OPEN_MODE (O_RDONLY | O_BINARY)
    +#endif /* defined O_BINARY */
    +#ifndef O_BINARY
    +#define OPEN_MODE O_RDONLY
    +#endif /* !defined O_BINARY */
    +
    +#ifndef WILDABBR
    +/*
    +** Someone might make incorrect use of a time zone abbreviation:
    +** 1. They might reference tzname[0] before calling tzset (explicitly
    +** or implicitly).
    +** 2. They might reference tzname[1] before calling tzset (explicitly
    +** or implicitly).
    +** 3. They might reference tzname[1] after setting to a time zone
    +** in which Daylight Saving Time is never observed.
    +** 4. They might reference tzname[0] after setting to a time zone
    +** in which Standard Time is never observed.
    +** 5. They might reference tm.TM_ZONE after calling offtime.
    +** What's best to do in the above cases is open to debate;
    +** for now, we just set things up so that in any of the five cases
    +** WILDABBR is used. Another possibility: initialize tzname[0] to the
    +** string "tzname[0] used before set", and similarly for the other cases.
    +** And another: initialize tzname[0] to "ERA", with an explanation in the
    +** manual page of what this "time zone abbreviation" means (doing this so
    +** that tzname[0] has the "normal" length of three characters).
    +*/
    +#define WILDABBR " "
    +#endif /* !defined WILDABBR */
    +
    +static char wildabbr[] = WILDABBR;
    +
    +static const char gmt[] = "GMT";
    +
    +static char *tzdir;
    +
    +/*
    +** The DST rules to use if TZ has no rules and we can't load TZDEFRULES.
    +** We default to US rules as of 1999-08-17.
    +** POSIX 1003.1 section 8.1.1 says that the default DST rules are
    +** implementation dependent; for historical reasons, US rules are a
    +** common default.
    +*/
    +#ifndef TZDEFRULESTRING
    +#define TZDEFRULESTRING ",M4.1.0,M10.5.0"
    +#endif /* !defined TZDEFDST */
    +
    +struct ttinfo { /* time type information */
    + long tt_gmtoff; /* UTC offset in seconds */
    + int tt_isdst; /* used to set tm_isdst */
    + int tt_abbrind; /* abbreviation list index */
    + int tt_ttisstd; /* TRUE if transition is std time */
    + int tt_ttisgmt; /* TRUE if transition is UTC */
    +};
    +
    +struct lsinfo { /* leap second information */
    + time_t ls_trans; /* transition time */
    + long ls_corr; /* correction to apply */
    +};
    +
    +#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
    +
    +#ifdef TZNAME_MAX
    +#define MY_TZNAME_MAX TZNAME_MAX
    +#endif /* defined TZNAME_MAX */
    +#ifndef TZNAME_MAX
    +#define MY_TZNAME_MAX 255
    +#endif /* !defined TZNAME_MAX */
    +
    +struct state {
    + int leapcnt;
    + int timecnt;
    + int typecnt;
    + int charcnt;
    + time_t ats[TZ_MAX_TIMES];
    + unsigned char types[TZ_MAX_TIMES];
    + struct ttinfo ttis[TZ_MAX_TYPES];
    + char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt),
    + (2 * (MY_TZNAME_MAX + 1)))];
    + struct lsinfo lsis[TZ_MAX_LEAPS];
    +};
    +
    +struct rule {
    + int r_type; /* type of rule--see below */
    + int r_day; /* day number of rule */
    + int r_week; /* week number of rule */
    + int r_mon; /* month number of rule */
    + long r_time; /* transition time of rule */
    +};
    +
    +#define JULIAN_DAY 0 /* Jn - Julian day */
    +#define DAY_OF_YEAR 1 /* n - day of year */
    +#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */
    +
    +/*
    +** Prototypes for static functions.
    +*/
    +
    +static long detzcode P((const char * codep));
    +static const char * getzname P((const char * strp));
    +static const char * getqzname P((const char * strp, const char delim));
    +static const char * getnum P((const char * strp, int * nump, int min,
    + int max));
    +static const char * getsecs P((const char * strp, long * secsp));
    +static const char * getoffset P((const char * strp, long * offsetp));
    +static const char * getrule P((const char * strp, struct rule * rulep));
    +static void gmtload P((struct state * sp));
    +struct tm * gmtsub P((const time_t * timep, long offset,
    + struct tm * tmp));
    +struct tm * localsub P((const time_t * timep, long offset,
    + struct tm * tmp, struct state *sp));
    +static int increment_overflow P((int * number, int delta));
    +static int leaps_thru_end_of P((int y));
    +static struct tm * timesub P((const time_t * timep, long offset,
    + const struct state * sp, struct tm * tmp));
    +static time_t transtime P((time_t janfirst, int year,
    + const struct rule * rulep, long offset));
    +static int tzload P((const char * name, struct state * sp));
    +static int tzparse P((const char * name, struct state * sp,
    + int lastditch));
    +
    +struct state *timezone_load P((const char * name));
    +
    +#ifdef ALL_STATE
    +static struct state * gmtptr;
    +#endif /* defined ALL_STATE */
    +
    +#ifndef ALL_STATE
    +static struct state gmtmem;
    +#define gmtptr (&gmtmem)
    +#endif /* State Farm */
    +
    +#ifndef TZ_STRLEN_MAX
    +#define TZ_STRLEN_MAX 255
    +#endif /* !defined TZ_STRLEN_MAX */
    +
    +//static char lcl_TZname[TZ_STRLEN_MAX + 1];
    +//static int lcl_is_set;
    +static int gmt_is_set;
    +
    +/*
    +** Section 4.12.3 of X3.159-1989 requires that
    +** Except for the strftime function, these functions [asctime,
    +** ctime, gmtime, localtime] return values in one of two static
    +** objects: a broken-down time structure and an array of char.
    +** Thanks to Paul Eggert for noting this.
    +*/
    +
    +//static struct tm tm;
    +
    +#ifdef USG_COMPAT
    +time_t timezone = 0;
    +int daylight = 0;
    +#endif /* defined USG_COMPAT */
    +
    +#ifdef ALTZONE
    +time_t altzone = 0;
    +#endif /* defined ALTZONE */
    +
    +static long
    +detzcode(codep)
    +const char * const codep;
    +{
    + register long result;
    + register int i;
    +
    + result = (codep[0] & 0x80) ? ~0L : 0L;
    + for (i = 0; i < 4; ++i)
    + result = (result << 8) | (codep[i] & 0xff);
    + return result;
    +}
    +
    +static int
    +tzload(name, sp)
    +register const char * name;
    +register struct state * const sp;
    +{
    + register const char * p;
    + register int i;
    + register int fid;
    +
    + if (name == NULL && (name = TZDEFAULT) == NULL)
    + return -1;
    + {
    + register int doaccess;
    + /*
    + ** Section 4.9.1 of the C standard says that
    + ** "FILENAME_MAX expands to an integral constant expression
    + ** that is the size needed for an array of char large enough
    + ** to hold the longest file name string that the implementation
    + ** guarantees can be opened."
    + */
    + char fullname[FILENAME_MAX + 1];
    +
    + if (name[0] == ':')
    + ++name;
    + doaccess = name[0] == '/';
    + if (!doaccess) {
    + if ((p = tzdir) == NULL)
    + return -1;
    + if ((strlen(p) + strlen(name) + 1) >= sizeof fullname)
    + return -1;
    + (void) strcpy(fullname, p);
    + (void) strcat(fullname, "/");
    + (void) strcat(fullname, name);
    + /*
    + ** Set doaccess if '.' (as in "../") shows up in name.
    + */
    + if (strchr(name, '.') != NULL)
    + doaccess = TRUE;
    + name = fullname;
    + }
    + if (doaccess && access(name, R_OK) != 0)
    + return -1;
    + if ((fid = open(name, OPEN_MODE)) == -1)
    + return -1;
    + }
    + {
    + struct tzhead * tzhp;
    + union {
    + struct tzhead tzhead;
    + char buf[sizeof *sp + sizeof *tzhp];
    + } u;
    + int ttisstdcnt;
    + int ttisgmtcnt;
    +
    + i = read(fid, u.buf, sizeof u.buf);
    + if (close(fid) != 0)
    + return -1;
    + ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt);
    + ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt);
    + sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt);
    + sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt);
    + sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt);
    + sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt);
    + p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt;
    + if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
    + sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
    + sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
    + sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
    + (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
    + (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
    + return -1;
    + if (i - (p - u.buf) < sp->timecnt * 4 + /* ats */
    + sp->timecnt + /* types */
    + sp->typecnt * (4 + 2) + /* ttinfos */
    + sp->charcnt + /* chars */
    + sp->leapcnt * (4 + 4) + /* lsinfos */
    + ttisstdcnt + /* ttisstds */
    + ttisgmtcnt) /* ttisgmts */
    + return -1;
    + for (i = 0; i < sp->timecnt; ++i) {
    + sp->ats[i] = detzcode(p);
    + p += 4;
    + }
    + for (i = 0; i < sp->timecnt; ++i) {
    + sp->types[i] = (unsigned char) *p++;
    + if (sp->types[i] >= sp->typecnt)
    + return -1;
    + }
    + for (i = 0; i < sp->typecnt; ++i) {
    + register struct ttinfo * ttisp;
    +
    + ttisp = &sp->ttis[i];
    + ttisp->tt_gmtoff = detzcode(p);
    + p += 4;
    + ttisp->tt_isdst = (unsigned char) *p++;
    + if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
    + return -1;
    + ttisp->tt_abbrind = (unsigned char) *p++;
    + if (ttisp->tt_abbrind < 0 ||
    + ttisp->tt_abbrind > sp->charcnt)
    + return -1;
    + }
    + for (i = 0; i < sp->charcnt; ++i)
    + sp->chars[i] = *p++;
    + sp->chars[i] = '\0'; /* ensure '\0' at end */
    + for (i = 0; i < sp->leapcnt; ++i) {
    + register struct lsinfo * lsisp;
    +
    + lsisp = &sp->lsis[i];
    + lsisp->ls_trans = detzcode(p);
    + p += 4;
    + lsisp->ls_corr = detzcode(p);
    + p += 4;
    + }
    + for (i = 0; i < sp->typecnt; ++i) {
    + register struct ttinfo * ttisp;
    +
    + ttisp = &sp->ttis[i];
    + if (ttisstdcnt == 0)
    + ttisp->tt_ttisstd = FALSE;
    + else {
    + ttisp->tt_ttisstd = *p++;
    + if (ttisp->tt_ttisstd != TRUE &&
    + ttisp->tt_ttisstd != FALSE)
    + return -1;
    + }
    + }
    + for (i = 0; i < sp->typecnt; ++i) {
    + register struct ttinfo * ttisp;
    +
    + ttisp = &sp->ttis[i];
    + if (ttisgmtcnt == 0)
    + ttisp->tt_ttisgmt = FALSE;
    + else {
    + ttisp->tt_ttisgmt = *p++;
    + if (ttisp->tt_ttisgmt != TRUE &&
    + ttisp->tt_ttisgmt != FALSE)
    + return -1;
    + }
    + }
    + /*
    + ** Out-of-sort ats should mean we're running on a
    + ** signed time_t system but using a data file with
    + ** unsigned values (or vice versa).
    + */
    + for (i = 0; i < sp->timecnt - 2; ++i)
    + if (sp->ats[i] > sp->ats[i + 1]) {
    + ++i;
    + if (TYPE_SIGNED(time_t)) {
    + /*
    + ** Ignore the end (easy).
    + */
    + sp->timecnt = i;
    + } else {
    + /*
    + ** Ignore the beginning (harder).
    + */
    + register int j;
    +
    + for (j = 0; j + i < sp->timecnt; ++j) {
    + sp->ats[j] = sp->ats[j + i];
    + sp->types[j] = sp->types[j + i];
    + }
    + sp->timecnt = j;
    + }
    + break;
    + }
    + }
    + return 0;
    +}
    +
    +struct state *timezone_load(name)
    +const char * name;
    +{
    + struct state *sp = malloc( sizeof(struct state) );
    + int res;
    +
    + if( !sp )
    + return NULL;
    + res = tzload( name, sp );
    + if( res < 0 )
    + {
    + free(sp);
    + return NULL;
    + }
    + return sp;
    +}
    +
    +
    +static const int mon_lengths[2][MONSPERYEAR] = {
    + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
    + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
    +};
    +
    +static const int year_lengths[2] = {
    + DAYSPERNYEAR, DAYSPERLYEAR
    +};
    +
    +/*
    +** Given a pointer into a time zone string, scan until a character that is not
    +** a valid character in a zone name is found. Return a pointer to that
    +** character.
    +*/
    +
    +static const char *
    +getzname(strp)
    +register const char * strp;
    +{
    + register char c;
    +
    + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' &&
    + c != '+')
    + ++strp;
    + return strp;
    +}
    +
    +/*
    +** Given a pointer into an extended time zone string, scan until the ending
    +** delimiter of the zone name is located. Return a pointer to the delimiter.
    +**
    +** As with getzname above, the legal character set is actually quite
    +** restricted, with other characters producing undefined results.
    +** We choose not to care - allowing almost anything to be in the zone abbrev.
    +*/
    +
    +static const char *
    +#if __STDC__
    +getqzname(register const char *strp, const char delim)
    +#else /* !__STDC__ */
    +getqzname(strp, delim)
    +register const char * strp;
    +const char delim;
    +#endif /* !__STDC__ */
    +{
    + register char c;
    +
    + while ((c = *strp) != '\0' && c != delim)
    + ++strp;
    + return strp;
    +}
    +
    +/*
    +** Given a pointer into a time zone string, extract a number from that string.
    +** Check that the number is within a specified range; if it is not, return
    +** NULL.
    +** Otherwise, return a pointer to the first character not part of the number.
    +*/
    +
    +static const char *
    +getnum(strp, nump, min, max)
    +register const char * strp;
    +int * const nump;
    +const int min;
    +const int max;
    +{
    + register char c;
    + register int num;
    +
    + if (strp == NULL || !is_digit(c = *strp))
    + return NULL;
    + num = 0;
    + do {
    + num = num * 10 + (c - '0');
    + if (num > max)
    + return NULL; /* illegal value */
    + c = *++strp;
    + } while (is_digit(c));
    + if (num < min)
    + return NULL; /* illegal value */
    + *nump = num;
    + return strp;
    +}
    +
    +/*
    +** Given a pointer into a time zone string, extract a number of seconds,
    +** in hh[:mm[:ss]] form, from the string.
    +** If any error occurs, return NULL.
    +** Otherwise, return a pointer to the first character not part of the number
    +** of seconds.
    +*/
    +
    +static const char *
    +getsecs(strp, secsp)
    +register const char * strp;
    +long * const secsp;
    +{
    + int num;
    +
    + /*
    + ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
    + ** "M10.4.6/26", which does not conform to Posix,
    + ** but which specifies the equivalent of
    + ** ``02:00 on the first Sunday on or after 23 Oct''.
    + */
    + strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
    + if (strp == NULL)
    + return NULL;
    + *secsp = num * (long) SECSPERHOUR;
    + if (*strp == ':') {
    + ++strp;
    + strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
    + if (strp == NULL)
    + return NULL;
    + *secsp += num * SECSPERMIN;
    + if (*strp == ':') {
    + ++strp;
    + /* `SECSPERMIN' allows for leap seconds. */
    + strp = getnum(strp, &num, 0, SECSPERMIN);
    + if (strp == NULL)
    + return NULL;
    + *secsp += num;
    + }
    + }
    + return strp;
    +}
    +
    +/*
    +** Given a pointer into a time zone string, extract an offset, in
    +** [+-]hh[:mm[:ss]] form, from the string.
    +** If any error occurs, return NULL.
    +** Otherwise, return a pointer to the first character not part of the time.
    +*/
    +
    +static const char *
    +getoffset(strp, offsetp)
    +register const char * strp;
    +long * const offsetp;
    +{
    + register int neg = 0;
    +
    + if (*strp == '-') {
    + neg = 1;
    + ++strp;
    + } else if (*strp == '+')
    + ++strp;
    + strp = getsecs(strp, offsetp);
    + if (strp == NULL)
    + return NULL; /* illegal time */
    + if (neg)
    + *offsetp = -*offsetp;
    + return strp;
    +}
    +
    +/*
    +** Given a pointer into a time zone string, extract a rule in the form
    +** date[/time]. See POSIX section 8 for the format of "date" and "time".
    +** If a valid rule is not found, return NULL.
    +** Otherwise, return a pointer to the first character not part of the rule.
    +*/
    +
    +static const char *
    +getrule(strp, rulep)
    +const char * strp;
    +register struct rule * const rulep;
    +{
    + if (*strp == 'J') {
    + /*
    + ** Julian day.
    + */
    + rulep->r_type = JULIAN_DAY;
    + ++strp;
    + strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);
    + } else if (*strp == 'M') {
    + /*
    + ** Month, week, day.
    + */
    + rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
    + ++strp;
    + strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
    + if (strp == NULL)
    + return NULL;
    + if (*strp++ != '.')
    + return NULL;
    + strp = getnum(strp, &rulep->r_week, 1, 5);
    + if (strp == NULL)
    + return NULL;
    + if (*strp++ != '.')
    + return NULL;
    + strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
    + } else if (is_digit(*strp)) {
    + /*
    + ** Day of year.
    + */
    + rulep->r_type = DAY_OF_YEAR;
    + strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
    + } else return NULL; /* invalid format */
    + if (strp == NULL)
    + return NULL;
    + if (*strp == '/') {
    + /*
    + ** Time specified.
    + */
    + ++strp;
    + strp = getsecs(strp, &rulep->r_time);
    + } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
    + return strp;
    +}
    +
    +/*
    +** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
    +** year, a rule, and the offset from UTC at the time that rule takes effect,
    +** calculate the Epoch-relative time that rule takes effect.
    +*/
    +
    +static time_t
    +transtime(janfirst, year, rulep, offset)
    +const time_t janfirst;
    +const int year;
    +register const struct rule * const rulep;
    +const long offset;
    +{
    + register int leapyear;
    + register time_t value;
    + register int i;
    + int d, m1, yy0, yy1, yy2, dow;
    +
    + INITIALIZE(value);
    + leapyear = isleap(year);
    + switch (rulep->r_type) {
    +
    + case JULIAN_DAY:
    + /*
    + ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
    + ** years.
    + ** In non-leap years, or if the day number is 59 or less, just
    + ** add SECSPERDAY times the day number-1 to the time of
    + ** January 1, midnight, to get the day.
    + */
    + value = janfirst + (rulep->r_day - 1) * SECSPERDAY;
    + if (leapyear && rulep->r_day >= 60)
    + value += SECSPERDAY;
    + break;
    +
    + case DAY_OF_YEAR:
    + /*
    + ** n - day of year.
    + ** Just add SECSPERDAY times the day number to the time of
    + ** January 1, midnight, to get the day.
    + */
    + value = janfirst + rulep->r_day * SECSPERDAY;
    + break;
    +
    + case MONTH_NTH_DAY_OF_WEEK:
    + /*
    + ** Mm.n.d - nth "dth day" of month m.
    + */
    + value = janfirst;
    + for (i = 0; i < rulep->r_mon - 1; ++i)
    + value += mon_lengths[leapyear][i] * SECSPERDAY;
    +
    + /*
    + ** Use Zeller's Congruence to get day-of-week of first day of
    + ** month.
    + */
    + m1 = (rulep->r_mon + 9) % 12 + 1;
    + yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
    + yy1 = yy0 / 100;
    + yy2 = yy0 % 100;
    + dow = ((26 * m1 - 2) / 10 +
    + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
    + if (dow < 0)
    + dow += DAYSPERWEEK;
    +
    + /*
    + ** "dow" is the day-of-week of the first day of the month. Get
    + ** the day-of-month (zero-origin) of the first "dow" day of the
    + ** month.
    + */
    + d = rulep->r_day - dow;
    + if (d < 0)
    + d += DAYSPERWEEK;
    + for (i = 1; i < rulep->r_week; ++i) {
    + if (d + DAYSPERWEEK >=
    + mon_lengths[leapyear][rulep->r_mon - 1])
    + break;
    + d += DAYSPERWEEK;
    + }
    +
    + /*
    + ** "d" is the day-of-month (zero-origin) of the day we want.
    + */
    + value += d * SECSPERDAY;
    + break;
    + }
    +
    + /*
    + ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in
    + ** question. To get the Epoch-relative time of the specified local
    + ** time on that day, add the transition time and the current offset
    + ** from UTC.
    + */
    + return value + rulep->r_time + offset;
    +}
    +
    +/*
    +** Given a POSIX section 8-style TZ string, fill in the rule tables as
    +** appropriate.
    +*/
    +
    +static int
    +tzparse(name, sp, lastditch)
    +const char * name;
    +register struct state * const sp;
    +const int lastditch;
    +{
    + const char * stdname;
    + const char * dstname;
    + size_t stdlen;
    + size_t dstlen;
    + long stdoffset;
    + long dstoffset;
    + register time_t * atp;
    + register unsigned char * typep;
    + register char * cp;
    + register int load_result;
    +
    + INITIALIZE(dstname);
    + stdname = name;
    + if (lastditch) {
    + stdlen = strlen(name); /* length of standard zone name */
    + name += stdlen;
    + if (stdlen >= sizeof sp->chars)
    + stdlen = (sizeof sp->chars) - 1;
    + stdoffset = 0;
    + } else {
    + if (*name == '<') {
    + name++;
    + stdname = name;
    + name = getqzname(name, '>');
    + if (*name != '>')
    + return (-1);
    + stdlen = name - stdname;
    + name++;
    + } else {
    + name = getzname(name);
    + stdlen = name - stdname;
    + }
    + if (*name == '\0')
    + return -1;
    + name = getoffset(name, &stdoffset);
    + if (name == NULL)
    + return -1;
    + }
    + load_result = tzload(TZDEFRULES, sp);
    + if (load_result != 0)
    + sp->leapcnt = 0; /* so, we're off a little */
    + if (*name != '\0') {
    + if (*name == '<') {
    + dstname = ++name;
    + name = getqzname(name, '>');
    + if (*name != '>')
    + return -1;
    + dstlen = name - dstname;
    + name++;
    + } else {
    + dstname = name;
    + name = getzname(name);
    + dstlen = name - dstname; /* length of DST zone name */
    + }
    + if (*name != '\0' && *name != ',' && *name != ';') {
    + name = getoffset(name, &dstoffset);
    + if (name == NULL)
    + return -1;
    + } else dstoffset = stdoffset - SECSPERHOUR;
    + if (*name == '\0' && load_result != 0)
    + name = TZDEFRULESTRING;
    + if (*name == ',' || *name == ';') {
    + struct rule start;
    + struct rule end;
    + register int year;
    + register time_t janfirst;
    + time_t starttime;
    + time_t endtime;
    +
    + ++name;
    + if ((name = getrule(name, &start)) == NULL)
    + return -1;
    + if (*name++ != ',')
    + return -1;
    + if ((name = getrule(name, &end)) == NULL)
    + return -1;
    + if (*name != '\0')
    + return -1;
    + sp->typecnt = 2; /* standard time and DST */
    + /*
    + ** Two transitions per year, from EPOCH_YEAR to 2037.
    + */
    + sp->timecnt = 2 * (2037 - EPOCH_YEAR + 1);
    + if (sp->timecnt > TZ_MAX_TIMES)
    + return -1;
    + sp->ttis[0].tt_gmtoff = -dstoffset;
    + sp->ttis[0].tt_isdst = 1;
    + sp->ttis[0].tt_abbrind = stdlen + 1;
    + sp->ttis[1].tt_gmtoff = -stdoffset;
    + sp->ttis[1].tt_isdst = 0;
    + sp->ttis[1].tt_abbrind = 0;
    + atp = sp->ats;
    + typep = sp->types;
    + janfirst = 0;
    + for (year = EPOCH_YEAR; year <= 2037; ++year) {
    + starttime = transtime(janfirst, year, &start,
    + stdoffset);
    + endtime = transtime(janfirst, year, &end,
    + dstoffset);
    + if (starttime > endtime) {
    + *atp++ = endtime;
    + *typep++ = 1; /* DST ends */
    + *atp++ = starttime;
    + *typep++ = 0; /* DST begins */
    + } else {
    + *atp++ = starttime;
    + *typep++ = 0; /* DST begins */
    + *atp++ = endtime;
    + *typep++ = 1; /* DST ends */
    + }
    + janfirst += year_lengths[isleap(year)] *
    + SECSPERDAY;
    + }
    + } else {
    + register long theirstdoffset;
    + register long theirdstoffset;
    + register long theiroffset;
    + register int isdst;
    + register int i;
    + register int j;
    +
    + if (*name != '\0')
    + return -1;
    + /*
    + ** Initial values of theirstdoffset and theirdstoffset.
    + */
    + theirstdoffset = 0;
    + for (i = 0; i < sp->timecnt; ++i) {
    + j = sp->types[i];
    + if (!sp->ttis[j].tt_isdst) {
    + theirstdoffset =
    + -sp->ttis[j].tt_gmtoff;
    + break;
    + }
    + }
    + theirdstoffset = 0;
    + for (i = 0; i < sp->timecnt; ++i) {
    + j = sp->types[i];
    + if (sp->ttis[j].tt_isdst) {
    + theirdstoffset =
    + -sp->ttis[j].tt_gmtoff;
    + break;
    + }
    + }
    + /*
    + ** Initially we're assumed to be in standard time.
    + */
    + isdst = FALSE;
    + theiroffset = theirstdoffset;
    + /*
    + ** Now juggle transition times and types
    + ** tracking offsets as you do.
    + */
    + for (i = 0; i < sp->timecnt; ++i) {
    + j = sp->types[i];
    + sp->types[i] = sp->ttis[j].tt_isdst;
    + if (sp->ttis[j].tt_ttisgmt) {
    + /* No adjustment to transition time */
    + } else {
    + /*
    + ** If summer time is in effect, and the
    + ** transition time was not specified as
    + ** standard time, add the summer time
    + ** offset to the transition time;
    + ** otherwise, add the standard time
    + ** offset to the transition time.
    + */
    + /*
    + ** Transitions from DST to DDST
    + ** will effectively disappear since
    + ** POSIX provides for only one DST
    + ** offset.
    + */
    + if (isdst && !sp->ttis[j].tt_ttisstd) {
    + sp->ats[i] += dstoffset -
    + theirdstoffset;
    + } else {
    + sp->ats[i] += stdoffset -
    + theirstdoffset;
    + }
    + }
    + theiroffset = -sp->ttis[j].tt_gmtoff;
    + if (sp->ttis[j].tt_isdst)
    + theirdstoffset = theiroffset;
    + else theirstdoffset = theiroffset;
    + }
    + /*
    + ** Finally, fill in ttis.
    + ** ttisstd and ttisgmt need not be handled.
    + */
    + sp->ttis[0].tt_gmtoff = -stdoffset;
    + sp->ttis[0].tt_isdst = FALSE;
    + sp->ttis[0].tt_abbrind = 0;
    + sp->ttis[1].tt_gmtoff = -dstoffset;
    + sp->ttis[1].tt_isdst = TRUE;
    + sp->ttis[1].tt_abbrind = stdlen + 1;
    + sp->typecnt = 2;
    + }
    + } else {
    + dstlen = 0;
    + sp->typecnt = 1; /* only standard time */
    + sp->timecnt = 0;
    + sp->ttis[0].tt_gmtoff = -stdoffset;
    + sp->ttis[0].tt_isdst = 0;
    + sp->ttis[0].tt_abbrind = 0;
    + }
    + sp->charcnt = stdlen + 1;
    + if (dstlen != 0)
    + sp->charcnt += dstlen + 1;
    + if ((size_t) sp->charcnt > sizeof sp->chars)
    + return -1;
    + cp = sp->chars;
    + (void) strncpy(cp, stdname, stdlen);
    + cp += stdlen;
    + *cp++ = '\0';
    + if (dstlen != 0) {
    + (void) strncpy(cp, dstname, dstlen);
    + *(cp + dstlen) = '\0';
    + }
    + return 0;
    +}
    +
    +static void
    +gmtload(sp)
    +struct state * const sp;
    +{
    + if (tzload(gmt, sp) != 0)
    + (void) tzparse(gmt, sp, TRUE);
    +}
    +
    +/*
    +** The easy way to behave "as if no library function calls" localtime
    +** is to not call it--so we drop its guts into "localsub", which can be
    +** freely called. (And no, the PANS doesn't require the above behavior--
    +** but it *is* desirable.)
    +**
    +** The unused offset argument is for the benefit of mktime variants.
    +*/
    +
    +struct tm *
    +localsub(timep, offset, tmp, sp)
    +const time_t * const timep;
    +const long offset;
    +struct tm * const tmp;
    +struct state * sp;
    +{
    + register const struct ttinfo * ttisp;
    + register int i;
    + register struct tm * result;
    + const time_t t = *timep;
    +
    +#ifdef ALL_STATE
    + if (sp == NULL)
    + return gmtsub(timep, offset, tmp);
    +#endif /* defined ALL_STATE */
    + if (sp->timecnt == 0 || t < sp->ats[0]) {
    + i = 0;
    + while (sp->ttis[i].tt_isdst)
    + if (++i >= sp->typecnt) {
    + i = 0;
    + break;
    + }
    + } else {
    + for (i = 1; i < sp->timecnt; ++i)
    + if (t < sp->ats[i])
    + break;
    + i = (int) sp->types[i - 1];
    + }
    + ttisp = &sp->ttis[i];
    + /*
    + ** To get (wrong) behavior that's compatible with System V Release 2.0
    + ** you'd replace the statement below with
    + ** t += ttisp->tt_gmtoff;
    + ** timesub(&t, 0L, sp, tmp);
    + */
    + result = timesub(&t, ttisp->tt_gmtoff, sp, tmp);
    + tmp->tm_isdst = ttisp->tt_isdst;
    + tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
    +#ifdef TM_ZONE
    + tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind];
    +#endif /* defined TM_ZONE */
    + return result;
    +}
    +
    +/*
    +** gmtsub is to gmtime as localsub is to localtime.
    +*/
    +
    +struct tm *
    +gmtsub(timep, offset, tmp)
    +const time_t * const timep;
    +const long offset;
    +struct tm * const tmp;
    +{
    + register struct tm * result;
    +
    + if (!gmt_is_set) {
    + gmt_is_set = TRUE;
    +#ifdef ALL_STATE
    + gmtptr = (struct state *) malloc(sizeof *gmtptr);
    + if (gmtptr != NULL)
    +#endif /* defined ALL_STATE */
    + gmtload(gmtptr);
    + }
    + result = timesub(timep, offset, gmtptr, tmp);
    +#ifdef TM_ZONE
    + /*
    + ** Could get fancy here and deliver something such as
    + ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero,
    + ** but this is no time for a treasure hunt.
    + */
    + if (offset != 0)
    + tmp->TM_ZONE = wildabbr;
    + else {
    +#ifdef ALL_STATE
    + if (gmtptr == NULL)
    + tmp->TM_ZONE = gmt;
    + else tmp->TM_ZONE = gmtptr->chars;
    +#endif /* defined ALL_STATE */
    +#ifndef ALL_STATE
    + tmp->TM_ZONE = gmtptr->chars;
    +#endif /* State Farm */
    + }
    +#endif /* defined TM_ZONE */
    + return result;
    +}
    +
    +/*
    +** Return the number of leap years through the end of the given year
    +** where, to make the math easy, the answer for year zero is defined as zero.
    +*/
    +
    +static int
    +leaps_thru_end_of(y)
    +register const int y;
    +{
    + return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
    + -(leaps_thru_end_of(-(y + 1)) + 1);
    +}
    +
    +static struct tm *
    +timesub(timep, offset, sp, tmp)
    +const time_t * const timep;
    +const long offset;
    +register const struct state * const sp;
    +register struct tm * const tmp;
    +{
    + register const struct lsinfo * lp;
    + register time_t tdays;
    + register int idays; /* unsigned would be so 2003 */
    + register long rem;
    + int y;
    + register const int * ip;
    + register long corr;
    + register int hit;
    + register int i;
    +
    + corr = 0;
    + hit = 0;
    +#ifdef ALL_STATE
    + i = (sp == NULL) ? 0 : sp->leapcnt;
    +#endif /* defined ALL_STATE */
    +#ifndef ALL_STATE
    + i = sp->leapcnt;
    +#endif /* State Farm */
    + while (--i >= 0) {
    + lp = &sp->lsis[i];
    + if (*timep >= lp->ls_trans) {
    + if (*timep == lp->ls_trans) {
    + hit = ((i == 0 && lp->ls_corr > 0) ||
    + lp->ls_corr > sp->lsis[i - 1].ls_corr);
    + if (hit)
    + while (i > 0 &&
    + sp->lsis[i].ls_trans ==
    + sp->lsis[i - 1].ls_trans + 1 &&
    + sp->lsis[i].ls_corr ==
    + sp->lsis[i - 1].ls_corr + 1) {
    + ++hit;
    + --i;
    + }
    + }
    + corr = lp->ls_corr;
    + break;
    + }
    + }
    + y = EPOCH_YEAR;
    + tdays = *timep / SECSPERDAY;
    + rem = *timep - tdays * SECSPERDAY;
    + while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
    + int newy;
    + register time_t tdelta;
    + register int idelta;
    + register int leapdays;
    +
    + tdelta = tdays / DAYSPERLYEAR;
    + idelta = tdelta;
    + if (tdelta - idelta >= 1 || idelta - tdelta >= 1)
    + return NULL;
    + if (idelta == 0)
    + idelta = (tdays < 0) ? -1 : 1;
    + newy = y;
    + if (increment_overflow(&newy, idelta))
    + return NULL;
    + leapdays = leaps_thru_end_of(newy - 1) -
    + leaps_thru_end_of(y - 1);
    + tdays -= ((time_t) newy - y) * DAYSPERNYEAR;
    + tdays -= leapdays;
    + y = newy;
    + }
    + {
    + register long seconds;
    +
    + seconds = tdays * SECSPERDAY + 0.5;
    + tdays = seconds / SECSPERDAY;
    + rem += seconds - tdays * SECSPERDAY;
    + }
    + /*
    + ** Given the range, we can now fearlessly cast...
    + */
    + idays = tdays;
    + rem += offset - corr;
    + while (rem < 0) {
    + rem += SECSPERDAY;
    + --idays;
    + }
    + while (rem >= SECSPERDAY) {
    + rem -= SECSPERDAY;
    + ++idays;
    + }
    + while (idays < 0) {
    + if (increment_overflow(&y, -1))
    + return NULL;
    + idays += year_lengths[isleap(y)];
    + }
    + while (idays >= year_lengths[isleap(y)]) {
    + idays -= year_lengths[isleap(y)];
    + if (increment_overflow(&y, 1))
    + return NULL;
    + }
    + tmp->tm_year = y;
    + if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
    + return NULL;
    + tmp->tm_yday = idays;
    + /*
    + ** The "extra" mods below avoid overflow problems.
    + */
    + tmp->tm_wday = EPOCH_WDAY +
    + ((y - EPOCH_YEAR) % DAYSPERWEEK) *
    + (DAYSPERNYEAR % DAYSPERWEEK) +
    + leaps_thru_end_of(y - 1) -
    + leaps_thru_end_of(EPOCH_YEAR - 1) +
    + idays;
    + tmp->tm_wday %= DAYSPERWEEK;
    + if (tmp->tm_wday < 0)
    + tmp->tm_wday += DAYSPERWEEK;
    + tmp->tm_hour = (int) (rem / SECSPERHOUR);
    + rem %= SECSPERHOUR;
    + tmp->tm_min = (int) (rem / SECSPERMIN);
    + /*
    + ** A positive leap second requires a special
    + ** representation. This uses "... ??:59:60" et seq.
    + */
    + tmp->tm_sec = (int) (rem % SECSPERMIN) + hit;
    + ip = mon_lengths[isleap(y)];
    + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
    + idays -= ip[tmp->tm_mon];
    + tmp->tm_mday = (int) (idays + 1);
    + tmp->tm_isdst = 0;
    +#ifdef TM_GMTOFF
    + tmp->TM_GMTOFF = offset;
    +#endif /* defined TM_GMTOFF */
    + return tmp;
    +}
    +
    +
    +/*
    +** Adapted from code provided by Robert Elz, who writes:
    +** The "best" way to do mktime I think is based on an idea of Bob
    +** Kridle's (so its said...) from a long time ago.
    +** It does a binary search of the time_t space. Since time_t's are
    +** just 32 bits, its a max of 32 iterations (even at 64 bits it
    +** would still be very reasonable).
    +*/
    +
    +#ifndef WRONG
    +#define WRONG (-1)
    +#endif /* !defined WRONG */
    +
    +/*
    +** Simplified normalize logic courtesy Paul Eggert.
    +*/
    +
    +static int
    +increment_overflow(number, delta)
    +int * number;
    +int delta;
    +{
    + int number0;
    +
    + number0 = *number;
    + *number += delta;
    + return (*number < number0) != (delta < 0);
    +}
    +
    +int tz_init( const char *zoneinfo_dir )
    +{
    + char *ptr;
    + int fd;
    +
    + if( zoneinfo_dir == NULL )
    + zoneinfo_dir = TZDIR;
    +
    + ptr = malloc( strlen(zoneinfo_dir) + 10 );
    + sprintf( ptr, "%s/zone.tab", zoneinfo_dir );
    + fd = open( ptr, O_RDONLY );
    + free(ptr);
    +
    + if( fd < 0 )
    + return -1;
    + close(fd);
    + if( tzdir )
    + free(tzdir);
    + tzdir = strdup(zoneinfo_dir);
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/localtime.h Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,16 @@
    +/*************************************************************************
    + * Header file for timezone module
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *************************************************************************/
    +
    +#include <time.h>
    +
    +struct state;
    +
    +struct state *timezone_load (const char * name);
    +struct tm * gmtsub (const time_t * timep, long offset,
    + struct tm * tmp);
    +struct tm * localsub (const time_t * timep, long offset,
    + struct tm * tmp, struct state *sp);
    +int tz_init(const char *zoneinfo_dir);
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/private.h Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,316 @@
    +/*************************************************************************
    + * Private Header file for timezone module
    + * copied from Olson timezone code, licence unchanged
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + *************************************************************************/
    +
    +#ifndef PRIVATE_H
    +
    +#define PRIVATE_H
    +
    +/*
    +** This file is in the public domain, so clarified as of
    +** 1996-06-05 by Arthur David Olson.
    +*/
    +
    +/*
    +** This header is for use ONLY with the time conversion code.
    +** There is no guarantee that it will remain unchanged,
    +** or that it will remain at all.
    +** Do NOT copy it to any system include directory.
    +** Thank you!
    +*/
    +
    +/*
    +** ID
    +*/
    +
    +#define GRANDPARENTED "Local time zone must be set--see zic manual page"
    +
    +/*
    +** Defaults for preprocessor symbols.
    +** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'.
    +*/
    +
    +#ifndef HAVE_ADJTIME
    +#define HAVE_ADJTIME 1
    +#endif /* !defined HAVE_ADJTIME */
    +
    +#ifndef HAVE_GETTEXT
    +#define HAVE_GETTEXT 0
    +#endif /* !defined HAVE_GETTEXT */
    +
    +#ifndef HAVE_INCOMPATIBLE_CTIME_R
    +#define HAVE_INCOMPATIBLE_CTIME_R 0
    +#endif /* !defined INCOMPATIBLE_CTIME_R */
    +
    +#ifndef HAVE_SETTIMEOFDAY
    +#define HAVE_SETTIMEOFDAY 3
    +#endif /* !defined HAVE_SETTIMEOFDAY */
    +
    +#ifndef HAVE_STRERROR
    +#define HAVE_STRERROR 1
    +#endif /* !defined HAVE_STRERROR */
    +
    +#ifndef HAVE_SYMLINK
    +#define HAVE_SYMLINK 1
    +#endif /* !defined HAVE_SYMLINK */
    +
    +#ifndef HAVE_SYS_STAT_H
    +#define HAVE_SYS_STAT_H 1
    +#endif /* !defined HAVE_SYS_STAT_H */
    +
    +#ifndef HAVE_SYS_WAIT_H
    +#define HAVE_SYS_WAIT_H 1
    +#endif /* !defined HAVE_SYS_WAIT_H */
    +
    +#ifndef HAVE_UNISTD_H
    +#define HAVE_UNISTD_H 1
    +#endif /* !defined HAVE_UNISTD_H */
    +
    +#ifndef HAVE_UTMPX_H
    +#define HAVE_UTMPX_H 0
    +#endif /* !defined HAVE_UTMPX_H */
    +
    +#ifndef LOCALE_HOME
    +#define LOCALE_HOME "/usr/lib/locale"
    +#endif /* !defined LOCALE_HOME */
    +
    +#if HAVE_INCOMPATIBLE_CTIME_R
    +#define asctime_r _incompatible_asctime_r
    +#define ctime_r _incompatible_ctime_r
    +#endif /* HAVE_INCOMPATIBLE_CTIME_R */
    +
    +/*
    +** Nested includes
    +*/
    +
    +#include "sys/types.h" /* for time_t */
    +#include "stdio.h"
    +#include "errno.h"
    +#include "string.h"
    +#include "limits.h" /* for CHAR_BIT */
    +#include "time.h"
    +#include "stdlib.h"
    +
    +#if HAVE_GETTEXT
    +#include "libintl.h"
    +#endif /* HAVE_GETTEXT */
    +
    +#if HAVE_SYS_WAIT_H
    +#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */
    +#endif /* HAVE_SYS_WAIT_H */
    +
    +#ifndef WIFEXITED
    +#define WIFEXITED(status) (((status) & 0xff) == 0)
    +#endif /* !defined WIFEXITED */
    +#ifndef WEXITSTATUS
    +#define WEXITSTATUS(status) (((status) >> 8) & 0xff)
    +#endif /* !defined WEXITSTATUS */
    +
    +#if HAVE_UNISTD_H
    +#include "unistd.h" /* for F_OK and R_OK */
    +#endif /* HAVE_UNISTD_H */
    +
    +#if !HAVE_UNISTD_H
    +#ifndef F_OK
    +#define F_OK 0
    +#endif /* !defined F_OK */
    +#ifndef R_OK
    +#define R_OK 4
    +#endif /* !defined R_OK */
    +#endif /* !HAVE_UNISTD_H */
    +
    +/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
    +#define is_digit(c) ((unsigned)(c) - '0' <= 9)
    +
    +/*
    +** Workarounds for compilers/systems.
    +*/
    +
    +/*
    +** SunOS 4.1.1 cc lacks prototypes.
    +*/
    +
    +#ifndef P
    +#if __STDC__
    +#define P(x) x
    +#else /* !__STDC__ */
    +#define P(x) ()
    +#endif /* !__STDC__ */
    +#endif /* !defined P */
    +
    +/*
    +** SunOS 4.1.1 headers lack EXIT_SUCCESS.
    +*/
    +
    +#ifndef EXIT_SUCCESS
    +#define EXIT_SUCCESS 0
    +#endif /* !defined EXIT_SUCCESS */
    +
    +/*
    +** SunOS 4.1.1 headers lack EXIT_FAILURE.
    +*/
    +
    +#ifndef EXIT_FAILURE
    +#define EXIT_FAILURE 1
    +#endif /* !defined EXIT_FAILURE */
    +
    +/*
    +** SunOS 4.1.1 headers lack FILENAME_MAX.
    +*/
    +
    +#ifndef FILENAME_MAX
    +
    +#ifndef MAXPATHLEN
    +#ifdef unix
    +#include "sys/param.h"
    +#endif /* defined unix */
    +#endif /* !defined MAXPATHLEN */
    +
    +#ifdef MAXPATHLEN
    +#define FILENAME_MAX MAXPATHLEN
    +#endif /* defined MAXPATHLEN */
    +#ifndef MAXPATHLEN
    +#define FILENAME_MAX 1024 /* Pure guesswork */
    +#endif /* !defined MAXPATHLEN */
    +
    +#endif /* !defined FILENAME_MAX */
    +
    +/*
    +** SunOS 4.1.1 libraries lack remove.
    +*/
    +
    +#ifndef remove
    +extern int unlink P((const char * filename));
    +#define remove unlink
    +#endif /* !defined remove */
    +
    +/*
    +** Some ancient errno.h implementations don't declare errno.
    +** But some newer errno.h implementations define it as a macro.
    +** Fix the former without affecting the latter.
    +*/
    +
    +#ifndef errno
    +extern int errno;
    +#endif /* !defined errno */
    +
    +/*
    +** Some time.h implementations don't declare asctime_r.
    +** Others might define it as a macro.
    +** Fix the former without affecting the latter.
    +*/
    +
    +#ifndef asctime_r
    +extern char * asctime_r();
    +#endif
    +
    +/*
    +** Private function declarations.
    +*/
    +
    +char * icalloc P((int nelem, int elsize));
    +char * icatalloc P((char * old, const char * new));
    +char * icpyalloc P((const char * string));
    +char * imalloc P((int n));
    +void * irealloc P((void * pointer, int size));
    +void icfree P((char * pointer));
    +void ifree P((char * pointer));
    +const char *scheck P((const char *string, const char *format));
    +
    +/*
    +** Finally, some convenience items.
    +*/
    +
    +#ifndef TRUE
    +#define TRUE 1
    +#endif /* !defined TRUE */
    +
    +#ifndef FALSE
    +#define FALSE 0
    +#endif /* !defined FALSE */
    +
    +#ifndef TYPE_BIT
    +#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
    +#endif /* !defined TYPE_BIT */
    +
    +#ifndef TYPE_SIGNED
    +#define TYPE_SIGNED(type) (((type) -1) < 0)
    +#endif /* !defined TYPE_SIGNED */
    +
    +/*
    +** Since the definition of TYPE_INTEGRAL contains floating point numbers,
    +** it cannot be used in preprocessor directives.
    +*/
    +
    +#ifndef TYPE_INTEGRAL
    +#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5)
    +#endif /* !defined TYPE_INTEGRAL */
    +
    +#ifndef INT_STRLEN_MAXIMUM
    +/*
    +** 302 / 1000 is log10(2.0) rounded up.
    +** Subtract one for the sign bit if the type is signed;
    +** add one for integer division truncation;
    +** add one more for a minus sign if the type is signed.
    +*/
    +#define INT_STRLEN_MAXIMUM(type) \
    + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
    + 1 + TYPE_SIGNED(type))
    +#endif /* !defined INT_STRLEN_MAXIMUM */
    +
    +/*
    +** INITIALIZE(x)
    +*/
    +
    +#ifndef GNUC_or_lint
    +#ifdef lint
    +#define GNUC_or_lint
    +#endif /* defined lint */
    +#ifndef lint
    +#ifdef __GNUC__
    +#define GNUC_or_lint
    +#endif /* defined __GNUC__ */
    +#endif /* !defined lint */
    +#endif /* !defined GNUC_or_lint */
    +
    +#ifndef INITIALIZE
    +#ifdef GNUC_or_lint
    +#define INITIALIZE(x) ((x) = 0)
    +#endif /* defined GNUC_or_lint */
    +#ifndef GNUC_or_lint
    +#define INITIALIZE(x)
    +#endif /* !defined GNUC_or_lint */
    +#endif /* !defined INITIALIZE */
    +
    +/*
    +** For the benefit of GNU folk...
    +** `_(MSGID)' uses the current locale's message library string for MSGID.
    +** The default is to use gettext if available, and use MSGID otherwise.
    +*/
    +
    +#ifndef _
    +#if HAVE_GETTEXT
    +#define _(msgid) gettext(msgid)
    +#else /* !HAVE_GETTEXT */
    +#define _(msgid) msgid
    +#endif /* !HAVE_GETTEXT */
    +#endif /* !defined _ */
    +
    +#ifndef TZ_DOMAIN
    +#define TZ_DOMAIN "tz"
    +#endif /* !defined TZ_DOMAIN */
    +
    +#if HAVE_INCOMPATIBLE_CTIME_R
    +#undef asctime_r
    +#undef ctime_r
    +char *asctime_r P((struct tm const *, char *));
    +char *ctime_r P((time_t const *, char *));
    +#endif /* HAVE_INCOMPATIBLE_CTIME_R */
    +
    +/*
    +** UNIX was a registered trademark of The Open Group in 2003.
    +*/
    +
    +#endif /* !defined PRIVATE_H */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/recurse.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,141 @@
    +/*************************************************************************
    + * Recursion module
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *
    + * Provides a function to recurse a directory and call a callback for each
    + * file found.
    + *************************************************************************/
    +
    +#define _GNU_SOURCE
    +#include <sys/types.h>
    +#include <dirent.h>
    +#include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <sys/stat.h>
    +
    +#include "recurse.h"
    +
    +#if 1
    +/* GLibc specific version. In this version, the entries are sorted */
    +/* We assume dirname ends in a /, prefix also unless empty */
    +static int
    +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data)
    +{
    + struct dirent **namelist;
    + int ents;
    + struct dirent *ent;
    + int i;
    + int ret = 0;
    +
    + if((ents = scandir(dirname, &namelist, 0, alphasort)) < 0)
    + return -1;
    +
    + for (i = 0; i < ents; i++)
    + {
    + char *ptr;
    + struct stat s;
    +
    + ent = namelist[i];
    + asprintf(&ptr, "%s%s", dirname, ent->d_name);
    + if(stat(ptr, &s) < 0)
    + {
    + free(ptr);
    + continue;
    + }
    +
    + if(S_ISREG(s.st_mode))
    + {
    + free(ptr);
    + asprintf(&ptr, "%s%s", prefix, ent->d_name);
    + ret = func(ptr, data);
    + }
    + else if(S_ISDIR(s.st_mode))
    + {
    + char *newdirname, *newprefix;
    +
    + if(ent->d_name[0] != '.')
    + {
    + asprintf(&newdirname, "%s%s/", dirname, ent->d_name);
    + asprintf(&newprefix, "%s%s/", prefix, ent->d_name);
    + ret = recurse_directory_int(newdirname, newprefix, func, data);
    + free(newdirname);
    + free(newprefix);
    + }
    + }
    + free(ptr);
    + if(ret < 0)
    + break;
    + }
    + free(namelist);
    + return 0;
    +}
    +#else
    +/* generic version, here they are unsorted */
    +/* We assume dirname ends in a /, prefix also unless empty */
    +static int
    +recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data)
    +{
    + DIR *dir;
    + struct dirent *ent;
    + int ret = 0;
    +
    + dir = opendir(dirname);
    + if(!dir)
    + return -1;
    + while ((ent = readdir(dir)) != NULL)
    + {
    + char *ptr;
    + struct stat s;
    +
    + asprintf(&ptr, "%s%s", dirname, ent->d_name);
    + if(stat(ptr, &s) < 0)
    + {
    + free(ptr);
    + continue;
    + }
    +
    + if(S_ISREG(s.st_mode))
    + {
    + free(ptr);
    + asprintf(&ptr, "%s%s", prefix, ent->d_name);
    + ret = func(ptr, data);
    + }
    + else if(S_ISDIR(s.st_mode))
    + {
    + char *newdirname, *newprefix;
    +
    + if(ent->d_name[0] != '.')
    + {
    + asprintf(&newdirname, "%s%s/", dirname, ent->d_name);
    + asprintf(&newprefix, "%s%s/", prefix, ent->d_name);
    + ret = recurse_directory_int(newdirname, newprefix, func, data);
    + free(newdirname);
    + free(newprefix);
    + }
    + }
    + free(ptr);
    + if(ret < 0)
    + break;
    + }
    + closedir(dir);
    + return ret;
    +}
    +#endif
    +
    +int
    +recurse_directory(char *dirname, DirRecurseMatch func, void *data)
    +{
    + char *newdirname = NULL;
    + int ret;
    +
    + if(dirname[strlen(dirname) - 1] != '/')
    + asprintf(&newdirname, "%s/", dirname);
    +
    + ret = recurse_directory_int(newdirname ? newdirname : dirname, "", func, data);
    +
    + if(newdirname)
    + free(newdirname);
    + return ret;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/recurse.h Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,9 @@
    +/*************************************************************************
    + * Header file for recursion module
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *************************************************************************/
    +
    +typedef int (*DirRecurseMatch)(char *filename, void *data);
    +int recurse_directory( char *dirname, DirRecurseMatch func, void *data );
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/recursetest.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,25 @@
    +/*************************************************************************
    + * Recursion test module
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *
    + * Code to test the recursion module.
    + *************************************************************************/
    +
    +#include <stdio.h>
    +
    +#include "recurse.h"
    +
    +int
    +process_entry(char *str, void *ptr)
    +{
    + printf("%s\n", str);
    + return 0;
    +}
    +
    +int
    +main()
    +{
    + recurse_directory("/usr/share/zoneinfo", process_entry, main);
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/timetest.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,28 @@
    +/*************************************************************************
    + * Timezone test module
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + * Licenced under the GNU General Public Licence version 2.
    + *
    + * Code to test the timezone module.
    + *************************************************************************/
    +
    +//#include "private.h"
    +//#include "tzfile.h"
    +#include "localtime.h"
    +
    +int
    +main()
    +{
    + struct state *state;
    + time_t now = time(NULL);
    + struct tm tm;
    +
    + state = timezone_load("Australia/Sydney");
    +
    + if(!state)
    + return 0;
    +
    + localsub(&now, 0, &tm, state);
    + gmtsub(&now, 0, &tm);
    + return 0;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/tzfile.h Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,175 @@
    +/*************************************************************************
    + * Private Header file for timezone module
    + * copied from Olson timezone code, licence unchanged
    + * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
    + *************************************************************************/
    +
    +#ifndef TZFILE_H
    +
    +#define TZFILE_H
    +
    +/*
    +** This file is in the public domain, so clarified as of
    +** 1996-06-05 by Arthur David Olson.
    +*/
    +
    +/*
    +** This header is for use ONLY with the time conversion code.
    +** There is no guarantee that it will remain unchanged,
    +** or that it will remain at all.
    +** Do NOT copy it to any system include directory.
    +** Thank you!
    +*/
    +
    +/*
    +** ID
    +*/
    +
    +/*
    +** Information about time zone files.
    +*/
    +
    +#ifndef TZDIR
    +#define TZDIR "/usr/share/zoneinfo" /* Default time zone object file directory */
    +#endif /* !defined TZDIR */
    +
    +#ifndef TZDEFAULT
    +#define TZDEFAULT "localtime"
    +#endif /* !defined TZDEFAULT */
    +
    +#ifndef TZDEFRULES
    +#define TZDEFRULES "posixrules"
    +#endif /* !defined TZDEFRULES */
    +
    +/*
    +** Each file begins with. . .
    +*/
    +
    +#define TZ_MAGIC "TZif"
    +
    +struct tzhead {
    + char tzh_magic[4]; /* TZ_MAGIC */
    + char tzh_reserved[16]; /* reserved for future use */
    + char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */
    + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
    + char tzh_leapcnt[4]; /* coded number of leap seconds */
    + char tzh_timecnt[4]; /* coded number of transition times */
    + char tzh_typecnt[4]; /* coded number of local time types */
    + char tzh_charcnt[4]; /* coded number of abbr. chars */
    +};
    +
    +/*
    +** . . .followed by. . .
    +**
    +** tzh_timecnt (char [4])s coded transition times a la time(2)
    +** tzh_timecnt (unsigned char)s types of local time starting at above
    +** tzh_typecnt repetitions of
    +** one (char [4]) coded UTC offset in seconds
    +** one (unsigned char) used to set tm_isdst
    +** one (unsigned char) that's an abbreviation list index
    +** tzh_charcnt (char)s '\0'-terminated zone abbreviations
    +** tzh_leapcnt repetitions of
    +** one (char [4]) coded leap second transition times
    +** one (char [4]) total correction after above
    +** tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition
    +** time is standard time, if FALSE,
    +** transition time is wall clock time
    +** if absent, transition times are
    +** assumed to be wall clock time
    +** tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition
    +** time is UTC, if FALSE,
    +** transition time is local time
    +** if absent, transition times are
    +** assumed to be local time
    +*/
    +
    +/*
    +** In the current implementation, "tzset()" refuses to deal with files that
    +** exceed any of the limits below.
    +*/
    +
    +#ifndef TZ_MAX_TIMES
    +/*
    +** The TZ_MAX_TIMES value below is enough to handle a bit more than a
    +** year's worth of solar time (corrected daily to the nearest second) or
    +** 138 years of Pacific Presidential Election time
    +** (where there are three time zone transitions every fourth year).
    +*/
    +#define TZ_MAX_TIMES 370
    +#endif /* !defined TZ_MAX_TIMES */
    +
    +#ifndef TZ_MAX_TYPES
    +#ifndef NOSOLAR
    +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
    +#endif /* !defined NOSOLAR */
    +#ifdef NOSOLAR
    +/*
    +** Must be at least 14 for Europe/Riga as of Jan 12 1995,
    +** as noted by Earl Chew.
    +*/
    +#define TZ_MAX_TYPES 20 /* Maximum number of local time types */
    +#endif /* !defined NOSOLAR */
    +#endif /* !defined TZ_MAX_TYPES */
    +
    +#ifndef TZ_MAX_CHARS
    +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
    + /* (limited by what unsigned chars can hold) */
    +#endif /* !defined TZ_MAX_CHARS */
    +
    +#ifndef TZ_MAX_LEAPS
    +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
    +#endif /* !defined TZ_MAX_LEAPS */
    +
    +#define SECSPERMIN 60
    +#define MINSPERHOUR 60
    +#define HOURSPERDAY 24
    +#define DAYSPERWEEK 7
    +#define DAYSPERNYEAR 365
    +#define DAYSPERLYEAR 366
    +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR)
    +#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY)
    +#define MONSPERYEAR 12
    +
    +#define TM_SUNDAY 0
    +#define TM_MONDAY 1
    +#define TM_TUESDAY 2
    +#define TM_WEDNESDAY 3
    +#define TM_THURSDAY 4
    +#define TM_FRIDAY 5
    +#define TM_SATURDAY 6
    +
    +#define TM_JANUARY 0
    +#define TM_FEBRUARY 1
    +#define TM_MARCH 2
    +#define TM_APRIL 3
    +#define TM_MAY 4
    +#define TM_JUNE 5
    +#define TM_JULY 6
    +#define TM_AUGUST 7
    +#define TM_SEPTEMBER 8
    +#define TM_OCTOBER 9
    +#define TM_NOVEMBER 10
    +#define TM_DECEMBER 11
    +
    +#define TM_YEAR_BASE 1900
    +
    +#define EPOCH_YEAR 1970
    +#define EPOCH_WDAY TM_THURSDAY
    +
    +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
    +
    +/*
    +** Since everything in isleap is modulo 400 (or a factor of 400), we know that
    +** isleap(y) == isleap(y % 400)
    +** and so
    +** isleap(a + b) == isleap((a + b) % 400)
    +** or
    +** isleap(a + b) == isleap(a % 400 + b % 400)
    +** This is true even if % means modulo rather than Fortran remainder
    +** (which is allowed by C89 but not C99).
    +** We use this to avoid addition overflow problems.
    +*/
    +
    +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
    +
    +#endif /* !defined TZFILE_H */
    --- a/chronic/chronic.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/chronic/chronic.c Mon Mar 24 05:08:00 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
    @@ -17,6 +17,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    /* libc */
    --- a/common/Makefile.am Tue Oct 09 06:21:19 2007 -0400
    +++ b/common/Makefile.am Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ /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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/common/glib_compat.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/common/gtk_template.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/common/pp_internal.h Mon Mar 24 05:08:00 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 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/configure.ac Mon Mar 24 05:08:00 2008 -0400
    @@ -1,4 +1,4 @@
    -AC_INIT([purple-plugin_pack], [2.2.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)
    @@ -230,10 +230,20 @@
    # switchspell
    gtkspell=yes
    -PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, , [gtkspell=no])
    +PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, [], [gtkspell=no])
    AC_SUBST(GTKSPELL_CFLAGS)
    AC_SUBST(GTKSPELL_LIBS)
    -AM_CONDITIONAL(HAVE_GTKSPELL, test x"$gtkspell" = x"yes")
    +
    +BUILD_SWITCH_SPELL=no
    +if test x"$gtkspell" = x"yes" ; then
    + AC_CHECK_HEADER([aspell.h], HAVE_ASPELL_H=yes, HAVE_ASPELL_H=no)
    + if test x"$HAVE_ASPELL_H" = x"yes" ; then
    + AC_CHECK_LIB([aspell], [new_aspell_config],BUILD_SWITCH_SPELL=yes,BUILD_SWITCH_SPELL=no)
    + else
    + BUILD_SWITCH_SPELL=no
    + fi
    +fi
    +AM_CONDITIONAL(BUILD_SWITCH_SPELL, test x"$BUILD_SWITCH_SPELL" = x"yes")
    # xmmsremote
    XMMS_LIBS=""
    @@ -300,18 +310,20 @@
    VERSION
    plugin_pack.spec
    album/Makefile
    + autoprofile/Makefile
    autoreply/Makefile
    awaynotify/Makefile
    bash/Makefile
    bit/Makefile
    blistops/Makefile
    - broadcast/Makefile
    buddytime/Makefile
    chronic/Makefile
    convbadger/Makefile
    + dewysiwygification/Makefile
    dice/Makefile
    difftopic/Makefile
    eight_ball/Makefile
    + enhancedhist/Makefile
    findip/Makefile
    flip/Makefile
    groupmsg/Makefile
    @@ -342,6 +354,7 @@
    stocker/Makefile
    switchspell/Makefile
    talkfilters/Makefile
    + timelog/Makefile
    xchat-chats/Makefile
    xmmsremote/Makefile
    xmmsremote/pixmaps/Makefile
    @@ -387,7 +400,7 @@
    echo Installing finch plugins to......: `eval eval echo $FINCH_LIBDIR`
    echo Installing finch plugin data to..: `eval eval echo $FINCH_DATADIR`
    if test x"$PP_FINCH_BUILD" = x"" ; then
    - echo Finch plugins to be built........: none
    + echo Finch plugins to be built........: none - THIS IS NORMAL
    else
    echo Finch plugins to be built........:
    echo $PP_FINCH_BUILD | xargs -n 4 echo " "
    --- a/convbadger/convbadger.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/convbadger/convbadger.c Mon Mar 24 05:08:00 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
    @@ -16,6 +16,8 @@
    * along with this program; if not, write to the Free Software
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <gtk/gtk.h>
    @@ -24,6 +26,7 @@
    #include <signals.h>
    #include <gtkmenutray.h>
    +#include <gtkplugin.h>
    #include <gtkutils.h>
    /******************************************************************************
    @@ -48,11 +51,9 @@
    cbd->win = NULL;
    cbd->conv = NULL;
    - if(GTK_IS_IMAGE(cbd->icon))
    + if(cbd->icon && GTK_IS_IMAGE(cbd->icon))
    gtk_widget_destroy(cbd->icon);
    - cbd->icon = NULL;
    -
    g_free(cbd);
    cbd = NULL;
    @@ -85,6 +86,9 @@
    pidgin_menu_tray_append(PIDGIN_MENU_TRAY(win->menu.tray), cbd->icon,
    NULL);
    gtk_widget_show(cbd->icon);
    +
    + g_signal_connect_swapped(G_OBJECT(cbd->icon), "destroy",
    + G_CALLBACK(g_nullify_pointer), &cbd->icon);
    }
    cbd->conv = conv;
    @@ -158,7 +162,7 @@
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    PURPLE_PLUGIN_STANDARD,
    - NULL,
    + PIDGIN_PLUGIN_TYPE,
    0,
    NULL,
    PURPLE_PRIORITY_DEFAULT,
    @@ -178,6 +182,11 @@
    NULL,
    NULL,
    NULL,
    + NULL,
    +
    + NULL,
    + NULL,
    + NULL,
    NULL
    };
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dewysiwygification/Makefile.am Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,26 @@
    +EXTRA_DIST = .purple-plugin .build Makefile.mingw
    +
    +dewysiwygificationdir = $(PURPLE_LIBDIR)
    +
    +dewysiwygification_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +dewysiwygification_LTLIBRARIES = dewysiwygification.la
    +
    +dewysiwygification_la_SOURCES = \
    + dewysiwygification.c
    +
    +dewysiwygification_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dewysiwygification/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for dewysiwygification plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = dewysiwygification
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dewysiwygification/dewysiwygification.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,131 @@
    +/*
    + * DeWYSIWYGification - Lets you type in HTML without it being escaped to entities.
    + * 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
    + * 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.
    + */
    +
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    +#include "../common/pp_internal.h"
    +
    +#include <debug.h>
    +#include <plugin.h>
    +#include <signals.h>
    +#include <util.h>
    +#include <version.h>
    +
    +#include <stdio.h>
    +#include <string.h>
    +#ifndef _WIN32
    +#include <strings.h>
    +#endif
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +
    +#define DEWYSIWYGIFICATION_PLUGIN_ID "dewysiwygification"
    +
    +
    +static gboolean
    +substitute_words_send_im(PurpleAccount *account, const char *receiver,
    + char **message)
    +{
    + char *tmp;
    +
    + if (message == NULL || *message == NULL)
    + return FALSE;
    +
    + tmp = purple_unescape_html(*message);
    + g_free(*message);
    + *message = tmp;
    +
    + purple_debug_misc("dewysiwygification", "it's now: %s", tmp);
    +
    + return FALSE;
    +}
    +
    +static gboolean
    +substitute_words_send_chat(PurpleAccount *account, char **message, int id)
    +{
    + char *tmp;
    +
    + if (message == NULL || *message == NULL)
    + return FALSE;
    +
    + tmp = purple_unescape_html(*message);
    + g_free(*message);
    + *message = tmp;
    +
    + purple_debug_misc("dewysiwygification", "it's now: %s", tmp);
    +
    + return FALSE;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + void *conv_handle = purple_conversations_get_handle();
    +
    +
    + purple_signal_connect(conv_handle, "sending-im-msg",
    + plugin, PURPLE_CALLBACK(substitute_words_send_im), NULL);
    + purple_signal_connect(conv_handle, "sending-chat-msg",
    + plugin, PURPLE_CALLBACK(substitute_words_send_chat), NULL);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + DEWYSIWYGIFICATION_PLUGIN_ID,
    + N_("DeWYSIWYGification Plugin"),
    + PP_VERSION,
    + N_("Lets you type in HTML without it being escaped to entities."),
    + N_("Lets you type in HTML without it being escaped to entities. This will not work well for some protocols. Use \"&lt;\" for a literal \"<\"."),
    + "Tim Ringenbach <omarvo@hotmail.com>",
    + PP_WEBSITE,
    + plugin_load,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +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 = _(info.name);
    + info.summary = _(info.summary);
    + info.description = _(info.description);
    +}
    +
    +PURPLE_INIT_PLUGIN(dewysiwygification, init_plugin, info)
    --- a/dice/dice.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/dice/dice.c Mon Mar 24 05:08:00 2008 -0400
    @@ -1,7 +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>
    + * 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
    @@ -18,6 +19,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <time.h>
    @@ -28,57 +30,286 @@
    #include <debug.h>
    #include <plugin.h>
    +#define DEFAULT_DICE 2
    +#define DEFAULT_SIDES 6
    +
    +#define BOUNDS_CHECK(var, min, min_def, max, max_def) { \
    + if((var) < (min)) \
    + (var) = (min_def); \
    + else if((var) > (max)) \
    + (var) = (max_def); \
    +}
    +
    +#define ROUND(val) ((gdouble)(val) + 0.5f)
    +
    static PurpleCmdId dice_cmd_id = 0;
    +static gchar *
    +old_school_roll(gint dice, gint sides) {
    + GString *str = g_string_new("");
    + gchar *ret = NULL;
    + gint c = 0, v = 0;
    +
    + BOUNDS_CHECK(dice, 1, 2, 15, 15);
    + BOUNDS_CHECK(sides, 2, 2, 999, 999);
    +
    + g_string_append_printf(str, "%d %d-sided %s:",
    + dice, sides,
    + (dice == 1) ? "die" : "dice");
    +
    + for(c = 0; c < dice; c++) {
    + v = rand() % sides + 1;
    +
    + g_string_append_printf(str, " %d", v);
    + }
    +
    + ret = str->str;
    + g_string_free(str, FALSE);
    +
    + return ret;
    +}
    +
    +static inline gboolean
    +is_dice_notation(const gchar *str) {
    + return (g_utf8_strchr(str, -1, 'd') != NULL);
    +}
    +
    +static gchar *
    +dice_notation_roll_helper(const gchar *dn, gint *value) {
    + GString *str = g_string_new("");
    + gchar *ret = NULL, *ms = NULL;
    + gchar op = '\0';
    + gint dice = 0, sides = 0, i = 0, t = 0, v = 0;
    + gdouble multiplier = 1.0;
    +
    + if(!dn || *dn == '\0')
    + return NULL;
    +
    + /* at this point, all we have is +/- number for our bonus, so we add it to
    + * our value
    + */
    + if(!is_dice_notation(dn)) {
    + gint bonus = atoi(dn);
    +
    + *value += bonus;
    +
    + /* the + makes sure we always have a + or - */
    + g_string_append_printf(str, "%s %d",
    + (bonus < 0) ? "-" : "+",
    + ABS(bonus));
    +
    + ret = str->str;
    + g_string_free(str, FALSE);
    +
    + return ret;
    + }
    +
    + /**************************************************************************
    + * Process our block
    + *************************************************************************/
    + purple_debug_info("dice", "processing '%s'\n", dn);
    +
    + /* get the number of dice */
    + dice = atoi(dn);
    + BOUNDS_CHECK(dice, 1, 1, 999, 999);
    +
    + /* find and move to the character after the d */
    + dn = g_utf8_strchr(dn, -1, 'd');
    + dn++;
    +
    + /* get the number of sides */
    + sides = atoi(dn);
    + BOUNDS_CHECK(sides, 2, 2, 999, 999);
    +
    + /* i've struggled with a better way to determine the next operator, i've
    + * opted for this.
    + */
    + for(t = sides; t > 0; t /= 10) {
    + dn++;
    + purple_debug_info("dice", "looking for the next operator: %s\n", dn);
    + }
    +
    + purple_debug_info("dice", "next operator: %s\n", dn);
    +
    + /* check if we're multiplying or dividing this block */
    + if(*dn == 'x' || *dn == '/') {
    + op = *dn;
    + dn++;
    +
    + multiplier = v = atof(dn);
    +
    + ms = g_strdup_printf("%d", (gint)multiplier);
    +
    + /* move past our multiplier */
    + for(t = v; t > 0; t /= 10) {
    + purple_debug_info("dice", "moving past the multiplier: %s\n", dn);
    + dn++;
    + }
    +
    + if(op == '/')
    + multiplier = 1 / multiplier;
    + }
    +
    + purple_debug_info("dice", "d=%d;s=%d;m=%f;\n", dice, sides, multiplier);
    +
    + /* calculate and output our block */
    + g_string_append_printf(str, " (");
    + for(i = 0; i < dice; i++) {
    + t = rand() % sides + 1;
    + v = ROUND(t * multiplier);
    +
    + g_string_append_printf(str, "%s%d", (i > 0) ? " " : "", t);
    +
    + purple_debug_info("dice", "die %d: %d(%d)\n", i, v, t);
    +
    + *value += v;
    + }
    +
    + g_string_append_printf(str, ")");
    +
    + /* if we have a multiplier, we need to output it as well */
    + if(multiplier != 1.0)
    + g_string_append_printf(str, "%c(%s)", op, ms);
    +
    + /* free our string of the multiplier */
    + g_free(ms);
    +
    + purple_debug_info("dice", "value=%d;str=%s\n", *value, str->str);
    +
    + /* we have more in our string, recurse! */
    + if(*dn != '\0') {
    + gchar *s = dice_notation_roll_helper(dn, value);
    +
    + if(s)
    + str = g_string_append(str, s);
    +
    + g_free(s);
    + }
    +
    + ret = str->str;
    + g_string_free(str, FALSE);
    +
    + return ret;
    +}
    +
    +static gchar *
    +dice_notation_roll(const gchar *dn) {
    + GString *str = g_string_new("");
    + gchar *ret = NULL, *normalized = NULL;
    + gint value = 0;
    +
    + g_string_append_printf(str, "%s:", dn);
    +
    + /* normalize the input and process it */
    + normalized = g_utf8_strdown(dn, -1);
    + g_string_append_printf(str, "%s",
    + dice_notation_roll_helper(normalized, &value));
    + g_free(normalized);
    +
    + g_string_append_printf(str, " = %d", value);
    +
    + ret = str->str;
    + g_string_free(str, FALSE);
    +
    + return ret;
    +}
    +
    static PurpleCmdRet
    -roll(PurpleConversation *conv, const gchar *cmd, gchar **args,
    - gchar *error, void *data)
    +roll(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar *error,
    + void *data)
    {
    - GString *str = NULL;
    - gint dice = 2, sides = 6, roll, i;
    + PurpleCmdStatus ret;
    + gchar *str = NULL, *newcmd = NULL;
    - srand(time(NULL));
    + if(!args[0]) {
    + str = old_school_roll(DEFAULT_DICE, DEFAULT_SIDES);
    + } else {
    + if(is_dice_notation(args[0])) {
    + str = dice_notation_roll(args[0]);
    + } else {
    + gint dice, sides;
    +
    + dice = atoi(args[0]);
    + sides = (args[1]) ? atoi(args[1]) : DEFAULT_SIDES;
    - if(args[0])
    - dice = atoi(args[0]);
    + str = old_school_roll(dice, sides);
    + }
    + }
    - if(args[1])
    - sides = atoi(args[1]);
    +#if 0
    + i = 1; /* Abuse that iterator! We're saying "We think this is dice notation!" */
    + splitted = g_strsplit(args[0], "d", 2); /* Split the description into two parts: (1)d(20+5); discard the 'd'. */
    + dice = atoi(splitted[0]); /* We should have the number of dice easily now. */
    + if(g_strstr_len(splitted[1], -1, "+") != NULL) /* If our second half contained a '+' (20+5) */
    + {
    + resplitted = g_strsplit(splitted[1], "+", 2); /* Split again: (20)+(5); discard the '+'. */
    + sides = atoi(resplitted[0]); /* Number of sides on the left. */
    + bonus += atoi(resplitted[1]); /* Bonus on the right. */
    + g_strfreev(resplitted); /* Free memory from the split. */
    + }
    + else if(g_strstr_len(splitted[1], -1, "-") != NULL) /* If our second half contained a '-' (20-3) */
    + {
    + resplitted = g_strsplit(splitted[1], "-", 2); /* Split again: (20)-(3); discard the '-'. */
    + sides = atoi(resplitted[0]); /* Number of sides on the left. */
    + bonus -= atoi(resplitted[1]); /* Penalty on the right. */
    + g_strfreev(resplitted); /* Free memory from the split. */
    + }
    + else /* There was neither a '+' nor a '-' in the second half. */
    + sides = atoi(splitted[1]); /* We're assuming it's just a number, then. Number of sides. */
    + g_strfreev(splitted); /* Free the original split. */
    + }
    + }
    - if(dice < 1)
    - dice = 2;
    - if(dice > 15)
    - dice = 15;
    -
    - if(sides < 2)
    - sides = 2;
    - if(sides > 999)
    - sides = 999;
    + if(args[1] && i == 0) /* If there was a second argument, and we care about it (not dice notation) */
    + sides = atoi(args[1]); /* Grab it and make it the number of sides the dice have. */
    str = g_string_new("");
    - g_string_append_printf(str, "Rolls %d %d-sided %s:", dice, sides,
    - (dice == 1) ? "die" : "dice");
    -
    - for(i = 0; i < dice; i++) {
    - roll = rand() % sides + 1;
    - g_string_append_printf(str, " %d", roll);
    + if(i) /* Show the output in dice notation format. */
    + {
    + g_string_append_printf(str, "%dd%d", dice, sides); /* For example, 1d20 */
    + if(bonus > 0)
    + g_string_append_printf(str, "+%d", bonus); /* 1d20+5 */
    + else if(bonus < 0)
    + g_string_append_printf(str, "%d", bonus); /* 1d20-3 (saying "-%d" would be redundant, since the '-' gets output with bonus automatically) */
    + g_string_append_printf(str, ":"); /* Final colon. 1d20-4: */
    }
    - if(conv->type == PURPLE_CONV_TYPE_IM)
    - purple_conv_im_send(PURPLE_CONV_IM(conv), str->str);
    - else if(conv->type == PURPLE_CONV_TYPE_CHAT)
    - purple_conv_chat_send(PURPLE_CONV_CHAT(conv), str->str);
    + for(i = 0; i < dice; i++) /* For each die... */
    + {
    + roll = rand() % sides + 1; /* Roll, and add bonus. */
    + accumulator += roll; /* Accumulate our rolls */
    + g_string_append_printf(str, " %d", roll); /* Append the result of our roll to our output string. */
    + }
    +
    + if(bonus != 0) /* If we had a bonus */
    + {
    + accumulator += bonus; /* Accumulate our bonus/penalty */
    + g_string_append_printf(str, " %s%d = %d", (bonus < 0) ? "penalty " : "bonus +", bonus, accumulator); /* Append our bonus/penalty to the output string */
    + }
    + else if(dice > 1) /* Or if we had more than one die */
    + {
    + g_string_append_printf(str, " = %d", accumulator); /* Append our accumulator */
    + }
    +#endif
    - g_string_free(str, TRUE);
    + newcmd = g_strdup_printf("me rolls %s", str);
    +
    + ret = purple_cmd_do_command(conv, newcmd, newcmd, &error);
    - return PURPLE_CMD_RET_OK;
    + g_free(str);
    + g_free(newcmd);
    +
    + return ret;
    }
    static gboolean
    -plugin_load(PurplePlugin *plugin) {
    +plugin_load(PurplePlugin *plugin)
    +{
    const gchar *help;
    - help = _("dice [dice] [sides]: rolls dice number of sides sided dice");
    + help = _("dice [dice] [sides]: rolls dice number of sides sided dice OR\n"
    + "dice [XdY+-Z]: rolls X number of Y sided dice, giving a Z "
    + "bonus/penalty to each. e.g. 1d20+2");
    dice_cmd_id = purple_cmd_register("dice", "wws", PURPLE_CMD_P_PLUGIN,
    PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
    @@ -86,11 +317,19 @@
    NULL, PURPLE_CMD_FUNC(roll),
    help, NULL);
    + /* we only want to seed this off of the seconds since the epoch once. If
    + * we do it every time, we'll give the same results for each time we
    + * process a roll within the same second. This is bad because it's not
    + * really random then.
    + */
    + srand(time(NULL));
    +
    return TRUE;
    }
    static gboolean
    -plugin_unload(PurplePlugin *plugin) {
    +plugin_unload(PurplePlugin *plugin)
    +{
    purple_cmd_unregister(dice_cmd_id);
    return TRUE;
    @@ -98,39 +337,40 @@
    static PurplePluginInfo info =
    {
    - PURPLE_PLUGIN_MAGIC, /**< magic */
    - PURPLE_MAJOR_VERSION, /**< major version */
    - PURPLE_MINOR_VERSION, /**< minor version */
    - PURPLE_PLUGIN_STANDARD, /**< type */
    - NULL, /**< ui_requirement */
    - 0, /**< flags */
    - NULL, /**< dependencies */
    - PURPLE_PRIORITY_DEFAULT, /**< priority */
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    - "core-plugin_pack-dice", /**< id */
    - NULL, /**< name */
    - PP_VERSION, /**< version */
    - NULL, /** summary */
    - NULL, /** description */
    - "Gary Kramlich <grim@reaperworld.com>", /**< author */
    - PP_WEBSITE, /**< homepage */
    + "core-plugin_pack-dice",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    - plugin_load, /**< load */
    - plugin_unload, /**< unload */
    - NULL, /**< destroy */
    + plugin_load,
    + plugin_unload,
    + NULL,
    - NULL, /**< ui_info */
    - NULL, /**< extra_info */
    - NULL, /**< prefs_info */
    - NULL, /**< actions */
    - NULL, /**< reserved 1 */
    - NULL, /**< reserved 2 */
    - NULL, /**< reserved 3 */
    - NULL /**< reserved 4 */
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    };
    static void
    -init_plugin(PurplePlugin *plugin) {
    +init_plugin(PurplePlugin *plugin)
    +{
    #ifdef ENABLE_NLS
    bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    @@ -139,7 +379,8 @@
    info.name = _("Dice");
    info.summary = _("Rolls dice in a chat or im");
    info.description = _("Adds a command (/dice) to roll an arbitrary "
    - "number of dice with an arbitrary number of sides");
    + "number of dice with an arbitrary number of sides. "
    + "Now supports dice notation! /help dice for details");
    }
    --- a/difftopic/difftopic.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/difftopic/difftopic.c Mon Mar 24 05:08:00 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
    @@ -18,6 +18,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #define PLUGIN_ID "gtk-plugin_pack-difftopic"
    --- a/eight_ball/eight_ball.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/eight_ball/eight_ball.c Mon Mar 24 05:08:00 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
    @@ -20,6 +20,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    /* libc */
    @@ -62,12 +63,199 @@
    "thats nots really nice",
    "Oh I at all do not understand a pancake about what you here talk.",
    "it shall be visible will be?",
    - "it becomes a complex rainbow of confusion."
    + "it becomes a complex rainbow of confusion.",
    + "dont sent any message any more stupit n idiot"
    };
    +#define MAXCRAPLEN 25
    +#define BOLLOCKS_SIZE 1024
    +
    +static const char verbs[][MAXCRAPLEN]={
    + "aggregate",
    + "architect",
    + "benchmark",
    + "brand",
    + "cultivate",
    + "deliver",
    + "deploy",
    + "disintermediate",
    + "drive",
    + "e-enable",
    + "embrace",
    + "empower",
    + "enable",
    + "engage",
    + "engineer",
    + "enhance",
    + "envisioneer",
    + "evolve",
    + "expedite",
    + "exploit",
    + "extend",
    + "facilitate",
    + "generate",
    + "grow",
    + "harness",
    + "implement",
    + "incentivize",
    + "incubate",
    + "innovate",
    + "integrate",
    + "iterate",
    + "leverage",
    + "maximize",
    + "mesh",
    + "monetize",
    + "morph",
    + "optimize",
    + "orchestrate",
    + "reintermediate",
    + "reinvent",
    + "repurpose",
    + "revolutionize",
    + "scale",
    + "seize",
    + "strategize",
    + "streamline",
    + "syndicate",
    + "synergize",
    + "synthesize",
    + "target",
    + "transform",
    + "transition",
    + "unleash",
    + "utilize",
    + "visualize",
    + "whiteboard" }
    +;
    +
    +static const char adjectives[][MAXCRAPLEN]= {
    + "24/365",
    + "24/7",
    + "B2B",
    + "B2C",
    + "back-end",
    + "best-of-breed",
    + "bleeding-edge",
    + "bricks-and-clicks",
    + "clicks-and-mortar",
    + "collaborative",
    + "compelling",
    + "cross-platform",
    + "cross-media",
    + "customized",
    + "cutting-edge",
    + "distributed",
    + "dot-com",
    + "dynamic",
    + "e-business",
    + "efficient",
    + "end-to-end",
    + "enterprise",
    + "extensible",
    + "frictionless",
    + "front-end",
    + "global",
    + "granular",
    + "holistic",
    + "impactful",
    + "innovative",
    + "integrated",
    + "interactive",
    + "intuitive",
    + "killer",
    + "leading-edge",
    + "magnetic",
    + "mission-critical",
    + "next-generation",
    + "one-to-one",
    + "open-source",
    + "out-of-the-box",
    + "plug-and-play",
    + "proactive",
    + "real-time",
    + "revolutionary",
    + "robust",
    + "scalable",
    + "seamless",
    + "sexy",
    + "sticky",
    + "strategic",
    + "synergistic",
    + "transparent",
    + "turn-key",
    + "ubiquitous",
    + "user-centric",
    + "value-added",
    + "vertical",
    + "viral",
    + "virtual",
    + "visionary",
    + "web-enabled",
    + "wireless",
    + "world-class"} ;
    +
    +static const char nouns[][MAXCRAPLEN]={
    + "action-items",
    + "applications",
    + "architectures",
    + "bandwidth",
    + "channels",
    + "communities",
    + "content",
    + "convergence",
    + "deliverables",
    + "e-business",
    + "e-commerce",
    + "e-markets",
    + "e-services",
    + "e-tailers",
    + "experiences",
    + "eyeballs",
    + "functionalities",
    + "infomediaries",
    + "infrastructures",
    + "initiatives",
    + "interfaces",
    + "markets",
    + "methodologies",
    + "metrics",
    + "mindshare",
    + "models",
    + "networks",
    + "niches",
    + "paradigms",
    + "partnerships",
    + "platforms",
    + "portals",
    + "relationships",
    + "ROI",
    + "synergies",
    + "web-readiness",
    + "schemas",
    + "solutions",
    + "supply-chains",
    + "systems",
    + "technologies",
    + "users",
    + "portals" };
    +
    +
    static PurpleCmdId eight_ball_cmd_id = 0,
    sg_ball_cmd_id = 0,
    - fullcrap_cmd_id = 0;
    + fullcrap_cmd_id = 0,
    + bollocks_cmd_id = 0;
    +
    +static char *mkbollocks(void)
    +{
    + int verb,adjective,noun;
    +
    + verb = rand() % G_N_ELEMENTS(verbs);
    + adjective = (rand()+2) % G_N_ELEMENTS(adjectives);
    + noun = (rand()+69) % G_N_ELEMENTS(nouns);
    + return g_strdup_printf("%s %s %s\n", verbs[verb], adjectives[adjective], nouns[noun]);
    +}
    +
    static PurpleCmdRet
    eight_ball_cmd_func(PurpleConversation *conv, const gchar *cmd, gchar **args,
    @@ -90,6 +278,10 @@
    numstrings = sizeof(fullcrap_strings) / sizeof(fullcrap_strings[0]);
    msgprefix = "The Purple Fullcrap Ball says";
    msgs = fullcrap_strings;
    + } else if(!strcmp(cmd, "bollocks")) {
    + numstrings = -1;
    + msgprefix = "/dev/bollocks says";
    + msgs = NULL;
    } else {
    numstrings = sizeof(eight_ball_strings) / sizeof(eight_ball_strings[0]);
    msgprefix = "The Purple 8 Ball says";
    @@ -107,7 +299,12 @@
    if(index > numstrings)
    index %= numstrings;
    - g_string_append_printf(msgstr, "%s: %s", msgprefix, msgs[index]);
    + if (!strcmp(cmd, "bollocks")) {
    + char *tmp = mkbollocks();
    + g_string_append_printf(msgstr, "%s: %s", msgprefix, tmp);
    + g_free(tmp);
    + } else
    + g_string_append_printf(msgstr, "%s: %s", msgprefix, msgs[index]);
    switch(purple_conversation_get_type(conv)) {
    case PURPLE_CONV_TYPE_IM:
    @@ -129,11 +326,12 @@
    static gboolean
    plugin_load(PurplePlugin *plugin)
    {
    - const gchar *eight_ball_help, *sg_ball_help, *fullcrap_help;
    + const gchar *eight_ball_help, *sg_ball_help, *fullcrap_help, *bollocks_help;
    eight_ball_help = _("8ball: sends a random 8ball message");
    sg_ball_help = _("sgball: sends a random Stargate Ball message");
    fullcrap_help = _("fullcrap: sends random fooling blabber");
    + bollocks_help = _("bollocks: sends random middle-manager bollocks");
    eight_ball_cmd_id = purple_cmd_register("8ball", "w", PURPLE_CMD_P_PLUGIN,
    PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
    @@ -153,6 +351,12 @@
    PURPLE_CMD_FUNC(eight_ball_cmd_func),
    fullcrap_help, NULL);
    + bollocks_cmd_id = purple_cmd_register("bollocks", "w", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
    + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL,
    + PURPLE_CMD_FUNC(eight_ball_cmd_func),
    + bollocks_help, NULL);
    +
    return TRUE;
    }
    @@ -161,6 +365,8 @@
    {
    purple_cmd_unregister(eight_ball_cmd_id);
    purple_cmd_unregister(sg_ball_cmd_id);
    + purple_cmd_unregister(fullcrap_cmd_id);
    + purple_cmd_unregister(bollocks_cmd_id);
    return TRUE;
    }
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/enhancedhist/Makefile.am Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,29 @@
    +EXTRA_DIST = \
    + .build \
    + .pidgin-plugin \
    + Makefile.mingw
    +
    +enhancedhistdir = $(PIDGIN_LIBDIR)
    +
    +enhancedhist_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +enhancedhist_LTLIBRARIES = enhancedhist.la
    +
    +enhancedhist_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GLIB_LIBS)
    +
    +enhancedhist_la_SOURCES = enhancedhist.c
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS) \
    + $(GTK_CFLAGS) \
    + $(GLIB_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/enhancedhist/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for enhancedhist plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = enhancedhist
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/enhancedhist/enhancedhist.c Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,394 @@
    +/*
    + * 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
    + * 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.
    + */
    +
    +/* SUMMARY: Puts log of past conversations in the user's IM window and
    + * allows the user to select the number of conversations to display from
    + * purple plugin preferences.
    + *
    + * Author:
    + * Andrew Pangborn - gaim@andrewpangborn.com
    + *
    + * Contributors:
    + * Ankit Singla
    + */
    +
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    +#include "../common/pp_internal.h"
    +
    +/* Purple headers */
    +#include <conversation.h>
    +#include <debug.h>
    +#include <log.h>
    +#include <plugin.h>
    +#include <pluginpref.h>
    +#include <prefs.h>
    +#include <signals.h>
    +#include <util.h>
    +
    +/* Pidgin headers */
    +#include <gtkconv.h>
    +#include <gtkimhtml.h>
    +#include <gtkplugin.h>
    +#include <gtkprefs.h>
    +#include <gtkutils.h>
    +#include <pidgin.h>
    +
    +/* libc headers */
    +#include <time.h>
    +
    +#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"
    +#define PREF_HOURS_PATH "/plugins/gtk/plugin_pack/enhanced_history/hours"
    +#define PREF_DAYS_PATH "/plugins/gtk/plugin_pack/enhanced_history/days"
    +#define PREF_DATES_PATH "/plugins/gtk/plugin_pack/enhanced_history/dates"
    +#define PREF_IM_PATH "/plugins/gtk/plugin_pack/enhanced_history/im"
    +#define PREF_CHAT_PATH "/plugins/gtk/plugin_pack/enhanced_history/chat"
    +
    +#define PREF_NUMBER_VAL purple_prefs_get_int(PREF_NUMBER_PATH)
    +#define PREF_MINS_VAL purple_prefs_get_int(PREF_MINS_PATH)
    +#define PREF_HOURS_VAL purple_prefs_get_int(PREF_HOURS_PATH)
    +#define PREF_DAYS_VAL purple_prefs_get_int(PREF_DAYS_PATH)
    +#define PREF_DATES_VAL purple_prefs_get_bool(PREF_DATES_PATH)
    +#define PREF_IM_VAL purple_prefs_get_bool(PREF_IM_PATH)
    +#define PREF_CHAT_VAL purple_prefs_get_bool(PREF_CHAT_PATH)
    +
    +static gboolean _scroll_imhtml_to_end(gpointer data)
    +{
    + GtkIMHtml *imhtml = data;
    + gtk_imhtml_scroll_to_end(GTK_IMHTML(imhtml), FALSE);
    + g_object_unref(G_OBJECT(imhtml));
    + return FALSE;
    +}
    +
    +static void historize(PurpleConversation *c)
    +{
    + PurpleAccount *account = NULL;
    + PurpleConversationType convtype;
    + PidginConversation *gtkconv = NULL;
    + GtkIMHtmlOptions options;
    + GList *logs = NULL;
    + GSList *buddies = NULL;
    + const char *name = NULL, *alias = NULL, *LOG_MODE = NULL;
    + char *header = NULL, *protocol = NULL, *history = NULL;
    + guint flags;
    + int size = 0;
    + int counter;
    +
    + account = purple_conversation_get_account(c);
    + name = purple_conversation_get_name(c);
    + alias = name;
    + LOG_MODE = purple_prefs_get_string("/purple/logging/format");
    +
    + /* If logging isn't enabled, don't show any history */
    + if(!purple_prefs_get_bool("/purple/logging/log_ims") &&
    + !purple_prefs_get_bool("/purple/logging/log_chats"))
    + return;
    +
    + /* If the user wants to show 0 logs, stop now */
    + if(PREF_NUMBER_VAL == 0) {
    + return;
    + }
    +
    + /* If the logging mode is html, set the output options to include no newline.
    + * Otherwise, it's normal text, so we don't need extra lines */
    + if(strcasecmp(LOG_MODE, "html")==0) {
    + options = GTK_IMHTML_NO_NEWLINE;
    + } else {
    + options = GTK_IMHTML_NO_COLOURS;
    + }
    +
    + /* Find buddies for this conversation. */
    + buddies = purple_find_buddies(account, name);
    +
    + /* If we found at least one buddy, save the first buddy's alias. */
    + if (buddies != NULL)
    + alias = purple_buddy_get_contact_alias((PurpleBuddy *)buddies->data);
    +
    + /* Determine whether this is an IM or a chat. In either case, if the user has that
    + * particular log type disabled, the logs file doesnt not get specified */
    + convtype = purple_conversation_get_type(c);
    + if (convtype == PURPLE_CONV_TYPE_IM && PREF_IM_VAL) {
    + logs = purple_log_get_logs(PURPLE_LOG_IM,
    + purple_conversation_get_name(c), purple_conversation_get_account(c));
    + } else if (convtype == PURPLE_CONV_TYPE_CHAT && PREF_CHAT_VAL) {
    + logs = purple_log_get_logs(PURPLE_LOG_CHAT,
    + purple_conversation_get_name(c), purple_conversation_get_account(c));
    + }
    +
    + /* The logs are non-existant or the user has disabled this type for log displaying. */
    + if (!logs) {
    + return;
    + }
    +
    + gtkconv = PIDGIN_CONVERSATION(c);
    +
    + size = g_list_length(logs);
    +
    + /* Make sure the user selected number of chats does not exceed the number of logs. */
    + if(size > PREF_NUMBER_VAL) {
    + size=PREF_NUMBER_VAL;
    + }
    +
    + /* No idea wth this does, it was in the original history plugin */
    + if (flags & PURPLE_LOG_READ_NO_NEWLINE) {
    + options |= GTK_IMHTML_NO_NEWLINE;
    + }
    +
    + /* Deal with time limitations */
    + counter = 0;
    + if(PREF_MINS_VAL == 0 && PREF_HOURS_VAL == 0 && PREF_DAYS_VAL == 0) {
    + /* No time limitations, advance the logs PREF_NUMBER_VAL-1 forward */
    + while(logs->next && counter < (PREF_NUMBER_VAL - 1)) {
    + logs = logs->next;
    + counter++;
    + purple_debug_info("ehnahcedhist", "Counter: %d\n", counter);
    + }
    + } else {
    + struct tm *log_tm = NULL, *local_tm = NULL;
    + time_t t, log_time;
    + double limit_time, diff_time;
    +
    + /* Grab current time and normalize it to UTC */
    + t = time(NULL);
    + local_tm = gmtime(&t);
    + t = mktime(local_tm);
    +
    + /* Pull the local time from the purple log, convert it to UTC time */
    + log_tm = gmtime(&((PurpleLog*)logs->data)->time);
    + log_time = mktime(log_tm);
    +
    + purple_debug_info("enhancedhist", "Local Time as int: %d \n", (int)t);
    + purple_debug_info("enhancedhist", "Log Time as int: %d \n", (int)mktime(log_tm));
    +
    + limit_time = (PREF_MINS_VAL * 60.0) + (PREF_HOURS_VAL * 60.0 * 60.0) +
    + (PREF_DAYS_VAL * 60.0 * 60.0 * 24.0);
    + diff_time = difftime(t, log_time);
    + purple_debug_info("enhancedhist", "Time difference between local and log: %.21f \n",
    + diff_time);
    +
    + /* The most recent log is already too old, so lets return */
    + if(diff_time > limit_time) {
    + return;
    + }
    + /* Iterate to the end of the list, stop while messages are under limit, we just
    + * want a count here */
    + while(logs->next && diff_time <= limit_time && counter < (PREF_NUMBER_VAL - 1)) {
    + logs = logs->next;
    + log_tm = gmtime(&((PurpleLog*)logs->data)->time);
    + log_time = mktime(log_tm);
    + diff_time = difftime(t, log_time);
    + counter++;
    + }
    + if(diff_time > limit_time) {
    + logs = logs->prev;
    + }
    + }
    +
    + if(counter == 0) {
    + return;
    + }
    + /* Loop through the logs and print them to the window */
    + while(logs) {
    + protocol = g_strdup(gtk_imhtml_get_protocol_name(GTK_IMHTML(gtkconv->imhtml)));
    + gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml),
    + purple_account_get_protocol_name(((PurpleLog*)logs->data)->account));
    +
    + if (gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(
    + GTK_TEXT_VIEW(gtkconv->imhtml))))
    + {
    + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), "<BR>", options);
    + }
    +
    + /* Print a header at the beginning of the log */
    + header = g_strdup_printf(_("<b>Conversation with %s on %s:</b><br>"), alias,
    + purple_date_format_full(localtime(&((PurpleLog *)logs->data)->time)));
    +
    + if(PREF_DATES_VAL) {
    + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), header, options);
    + }
    +
    + g_free(header);
    +
    + /* Copy the log string into the history array */
    + history = purple_log_read((PurpleLog*)logs->data, &flags);
    +
    + gtk_imhtml_append_text(GTK_IMHTML(gtkconv->imhtml), history, options);
    + g_free(history);
    +
    + /* Advance the list so that the next time through the loop we get the next log */
    + logs = logs->prev;
    + }
    +
    + gtk_imhtml_set_protocol_name(GTK_IMHTML(gtkconv->imhtml), protocol);
    + g_free(protocol);
    +
    + g_object_ref(G_OBJECT(gtkconv->imhtml));
    + g_idle_add(_scroll_imhtml_to_end, gtkconv->imhtml);
    +
    + /* Clear the allocated memory that the logs are using */
    + g_list_foreach(logs, (GFunc)purple_log_free, NULL);
    + g_list_free(logs);
    +
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + purple_signal_connect(purple_conversations_get_handle(),
    + "conversation-created", plugin, PURPLE_CALLBACK(historize), NULL);
    +
    + return TRUE;
    +}
    +
    +static GtkWidget *
    +eh_prefs_get_frame(PurplePlugin *plugin)
    +{
    + GtkSizeGroup *sg = NULL;
    + GtkWidget *vbox = NULL, *frame = NULL, *option = NULL;
    +
    + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
    + vbox = gtk_vbox_new(TRUE, PIDGIN_HIG_BOX_SPACE);
    +
    + /* heading for the more general options */
    + frame = pidgin_make_frame(vbox, _("Display Options"));
    +
    + /* the integer pref for the number of logs to display */
    + pidgin_prefs_labeled_spin_button(frame, _("Number of previous conversations to display:"),
    + PREF_NUMBER_PATH, 0, 255, NULL);
    +
    + /* the boolean preferences */
    + option = pidgin_prefs_checkbox(_("Show dates with text"), PREF_DATES_PATH, frame);
    + option = pidgin_prefs_checkbox(_("Show logs for IMs"), PREF_IM_PATH, frame);
    + option = pidgin_prefs_checkbox(_("Show logs for chats"), PREF_CHAT_PATH, frame);
    +
    + /* heading for the age limit options */
    + frame = pidgin_make_frame(vbox, _("Age Limit for Logs (0 to disable):"));
    +
    + /* the integer preferences for time limiting */
    + option = pidgin_prefs_labeled_spin_button(frame, "Days:", PREF_DAYS_PATH, 0, 255, sg);
    + option = pidgin_prefs_labeled_spin_button(frame, "Hours:", PREF_HOURS_PATH, 0, 255, sg);
    + option = pidgin_prefs_labeled_spin_button(frame, "Minutes:", PREF_MINS_PATH, 0, 255, sg);
    +
    + gtk_widget_show_all(vbox);
    +
    + return vbox;
    +}
    +
    +
    +static PidginPluginUiInfo ui_info = {
    + eh_prefs_get_frame, /* get prefs frame */
    + 0, /* page number - reserved */
    +
    + /* reserved pointers */
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + PIDGIN_PLUGIN_TYPE,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + ENHANCED_HISTORY_ID,
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    +
    + "Andrew Pangborn <gaim@andrewpangborn.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + NULL,
    + NULL,
    +
    + &ui_info,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void
    +init_plugin(PurplePlugin *plugin)
    +{
    + 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")) {
    + 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"))
    + ims = TRUE;
    + 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");
    + } else {
    + /* Create these prefs with sensible defaults */
    + purple_prefs_add_int(PREF_NUMBER_PATH, 1);
    + purple_prefs_add_int(PREF_MINS_PATH, 0);
    + purple_prefs_add_int(PREF_HOURS_PATH, 0);
    + purple_prefs_add_int(PREF_DAYS_PATH, 0);
    + purple_prefs_add_bool(PREF_DATES_PATH, TRUE);
    + purple_prefs_add_bool(PREF_IM_PATH, TRUE);
    + purple_prefs_add_bool(PREF_CHAT_PATH, FALSE);
    + }
    +
    + info.name = _("Enhanced History");
    + info.summary = _("An enhanced version of the history plugin.");
    + info.description = _("An enhanced versoin of the history plugin. Grants ability to "
    + "select the number of previous conversations to show instead of just one.");
    +}
    +
    +PURPLE_INIT_PLUGIN(enhanced_history, init_plugin, info)
    +
    --- a/findip/findip.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/findip/findip.c Mon Mar 24 05:08:00 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
    @@ -17,17 +17,12 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
    * 02111-1301, USA.
    */
    -#ifdef HAVE_CONFIG_H
    -# include "../pp_config.h"
    -#endif
    -
    -#define PURPLE_PLUGINS
    -#define PLUGIN_ID "findip"
    -#define PLUGIN_NAME "Find IP"
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    +#include "../common/pp_internal.h"
    +
    +#define PLUGIN_ID "core-plugin_pack-findip"
    #define PLUGIN_STATIC_NAME "findip"
    -#define PLUGIN_SUMMARY "Find the IP of a person in the buddylist."
    -#define PLUGIN_DESCRIPTION "Find the IP of a person in the buddylist. This doesn't really work."
    #define PLUGIN_AUTHOR "someone <someone@somewhere.tld>"
    /* System headers */
    @@ -40,10 +35,10 @@
    #include <server.h>
    #include <version.h>
    -/* Pack/Local headers */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    -#define PREF_ROOT "/plugins/core/" PLUGIN_ID
    +#define PREF_ROOT "/plugins/core/plugin_pack/" PLUGIN_STATIC_NAME
    #define PREF_NOTIFY PREF_ROOT "/notify"
    static gboolean
    @@ -138,22 +133,22 @@
    };
    static PurplePluginInfo info = {
    - PURPLE_PLUGIN_MAGIC, /* Magic */
    - PURPLE_MAJOR_VERSION, /* Purple Major Version */
    - PURPLE_MINOR_VERSION, /* Purple Minor Version */
    + 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 */
    + PURPLE_PRIORITY_DEFAULT, /* priority */
    PLUGIN_ID, /* plugin id */
    NULL, /* name */
    - PP_VERSION, /* version */
    + PP_VERSION, /* version */
    NULL, /* summary */
    NULL, /* description */
    PLUGIN_AUTHOR, /* author */
    - PP_WEBSITE, /* website */
    + PP_WEBSITE , /* website */
    plugin_load, /* load */
    plugin_unload, /* unload */
    @@ -172,9 +167,9 @@
    bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    #endif /* ENABLE_NLS */
    - info.name = _(PLUGIN_NAME);
    - info.summary = _(PLUGIN_SUMMARY);
    - info.description = _(PLUGIN_DESCRIPTION);
    + info.name = _("Find IP");
    + info.summary = _("Find the IP of a person in the buddylist.");
    + info.description = _("Find the IP of a person in the buddylist. This doesn't really work.");
    purple_prefs_add_none(PREF_ROOT);
    purple_prefs_add_bool(PREF_NOTIFY, TRUE);
    --- a/gRIM/gRIM.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/gRIM/gRIM.c Mon Mar 24 05:08:00 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
    @@ -26,6 +30,7 @@
    * 08:19 < grim> i was going to use my nick as an example but decided against that..
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <time.h>
    @@ -99,7 +104,7 @@
    PurpleConversation *conv;
    };
    -static PurpleCmdId rim_cmd_id = 0;
    +static PurpleCmdId rim_cmd_id = 0, base_cmd_id = 0;
    static gboolean
    timeout_func_cb(struct timeout_data *data)
    @@ -280,7 +285,7 @@
    /* THIS LINE IS NOT TRANSLATABLE. Patches to make it NLS capable will be
    * rejected without response */
    help = "gRIM: Take off every 'Zig'!!";
    - rim_cmd_id = purple_cmd_register("base", "", PURPLE_CMD_P_PLUGIN,
    + base_cmd_id = purple_cmd_register("base", "", PURPLE_CMD_P_PLUGIN,
    PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT,
    NULL, PURPLE_CMD_FUNC(rim),
    help, NULL);
    @@ -290,6 +295,7 @@
    static gboolean
    plugin_unload(PurplePlugin *plugin) {
    purple_cmd_unregister(rim_cmd_id);
    + purple_cmd_unregister(base_cmd_id);
    return TRUE;
    }
    --- a/groupmsg/groupmsg.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/groupmsg/groupmsg.c Mon Mar 24 05:08:00 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
    @@ -17,6 +17,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <debug.h>
    --- a/hideconv/Makefile.am Tue Oct 09 06:21:19 2007 -0400
    +++ b/hideconv/Makefile.am Mon Mar 24 05:08:00 2008 -0400
    @@ -1,4 +1,4 @@
    -EXTRA_DIST = .pidgin-plugin Makefile.mingw .build
    +EXTRA_DIST = .pidgin-plugin Makefile.mingw .incomplete
    hideconvdir = $(PIDGIN_LIBDIR)
    --- a/hideconv/hideconv.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/hideconv/hideconv.c Mon Mar 24 05:08:00 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
    @@ -18,6 +18,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #define PLUGIN_ID "gtk-plugin_pack-hideconv"
    @@ -149,7 +150,7 @@
    {
    PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
    - if (!gtkconv)
    + if (!gtkconv || !gtkconv->win || !gtkconv->win->window)
    return TRUE;
    if (!GTK_WIDGET_VISIBLE(gtkconv->win->window))
    return TRUE;
    --- a/highlight/highlight.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/highlight/highlight.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/ignore/ignore.c Mon Mar 24 05:08:00 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
    @@ -18,7 +18,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
    */
    -/* Pack/Local headers */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <account.h>
    @@ -27,6 +27,7 @@
    #include <conversation.h>
    #include <debug.h>
    #include <plugin.h>
    +#include <util.h>
    #include <string.h>
    @@ -116,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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/infopane/infopane.c Mon Mar 24 05:08:00 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
    @@ -18,6 +18,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include "pidgin.h"
    @@ -123,6 +124,12 @@
    #endif
    static void
    +call_ensure_tabs_are_showing(PurpleConversation *conv)
    +{
    + g_timeout_add(0, (GSourceFunc)ensure_tabs_are_showing, conv);
    +}
    +
    +static void
    pref_changed(gpointer data, ...)
    {
    GList *wins = pidgin_conv_windows_get_list();
    @@ -159,7 +166,7 @@
    #endif
    purple_signal_connect(pidgin_conversations_get_handle(),
    "conversation-switched",
    - plugin, PURPLE_CALLBACK(ensure_tabs_are_showing), NULL);
    + plugin, PURPLE_CALLBACK(call_ensure_tabs_are_showing), NULL);
    purple_prefs_connect_callback(plugin, PREF_POSITION, (PurplePrefCallback)pref_changed, NULL);
    purple_prefs_connect_callback(plugin, PREF_DRAG, (PurplePrefCallback)pref_changed, NULL);
    @@ -219,7 +226,7 @@
    PURPLE_PRIORITY_DEFAULT,
    PLUGIN_ID,
    NULL,
    - VERSION,
    + PP_VERSION,
    NULL,
    NULL,
    "Sadrul H Chowdhury <sadrul@pidgin.im>",
    --- a/irc-more/irc-more.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/irc-more/irc-more.c Mon Mar 24 05:08:00 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
    @@ -18,7 +19,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
    */
    -/* Pack/Local headers */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <accountopt.h>
    @@ -32,7 +33,8 @@
    #define CTCP_REPLY purple_account_get_string(account, "ctcp-message", "Purple IRC")
    #define PART_MESSAGE purple_account_get_string(account, "part-message", "Leaving.")
    #define QUIT_MESSAGE purple_account_get_string(account, "quit-message", "Leaving.")
    -#define UMODES purple_account_get_string(account, "umodes", "i")
    +#define SET_UMODES purple_account_get_string(account, "umodes", "i")
    +#define UNSET_UMODES purple_account_get_string(account, "umodes", NULL)
    #define PLUGIN_ID "core-plugin_pack-irc-more"
    @@ -95,8 +97,8 @@
    {
    /* should this be done on a timeout? */
    PurpleAccount *account = NULL;
    - const gchar *nick = NULL, *modes = NULL;
    - gchar *msg = NULL;
    + const gchar *nick = NULL, *setmodes = NULL, *unsetmodes = NULL;
    + gchar *msg = NULL, *msg2 = NULL;
    account = purple_connection_get_account(gc);
    @@ -105,16 +107,23 @@
    return;
    nick = purple_connection_get_display_name(gc);
    - modes = UMODES;
    - msg = g_strdup_printf("MODE %s +%s\r\n", nick, modes);
    + setmodes = SET_UMODES;
    + unsetmodes = UNSET_UMODES;
    + msg = g_strdup_printf("MODE %s +%s\r\n", nick, setmodes);
    irc_info->send_raw(gc, msg, strlen(msg));
    + g_free(msg);
    - g_free(msg);
    + if(unsetmodes && *unsetmodes) {
    + msg2 = g_strdup_printf("MODE %s -%s\r\n", nick, unsetmodes);
    + irc_info->send_raw(gc, msg2, strlen(msg2));
    + g_free(msg2);
    + }
    return;
    }
    +#if !PURPLE_VERSION_CHECK(2,4,0)
    static PurpleCmdRet
    notice_cmd_cb(PurpleConversation *conv, const gchar *cmd, gchar **args,
    gchar **error, void *data)
    @@ -157,19 +166,20 @@
    irc_info->send_raw(gc, msg, len);
    /* avoid a possible double-free crash */
    - if(msg != tmp);
    + if(msg != tmp)
    g_free(tmp);
    g_free(msg);
    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, ':');
    @@ -190,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
    @@ -211,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();
    @@ -238,9 +250,11 @@
    option = purple_account_option_string_new(_("Default Part Message"), "part-message", "Leaving.");
    irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
    - option = purple_account_option_string_new(_("User Modes On Connect"), "umodes", "i");
    + option = purple_account_option_string_new(_("Set User Modes On Connect"), "setumodes", "i");
    irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
    + option = purple_account_option_string_new(_("Unset User Modes On Connect"), "unsetumodes", "");
    + irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
    return TRUE;
    }
    --- a/irchelper/irchelper.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/irchelper/irchelper.c Mon Mar 24 05:08:00 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>
    @@ -23,6 +23,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <string.h>
    @@ -198,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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/datechange.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/datechange.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/irssi.c Mon Mar 24 05:08:00 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
    @@ -19,6 +20,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    /* define these so the plugin info struct way at the bottom is cleaner */
    --- a/irssi/lastlog.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/lastlog.c Mon Mar 24 05:08:00 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
    @@ -19,6 +20,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <string.h>
    --- a/irssi/lastlog.h Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/lastlog.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/layout.c Mon Mar 24 05:08:00 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
    @@ -19,6 +20,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <blist.h>
    --- a/irssi/layout.h Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/layout.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/textfmt.c Mon Mar 24 05:08:00 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
    @@ -19,6 +20,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #ifdef HAVE_REGEX_H
    @@ -102,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("");
    @@ -143,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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/textfmt.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/window.c Mon Mar 24 05:08:00 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
    @@ -19,6 +20,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <gtk/gtk.h>
    --- a/irssi/window.h Tue Oct 09 06:21:19 2007 -0400
    +++ b/irssi/window.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/lastseen/lastseen.c Mon Mar 24 05:08:00 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
    @@ -17,6 +17,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <plugin.h>
    --- a/listhandler/aim_blt_files.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/aim_blt_files.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/aim_blt_files.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/alias_xml_files.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/alias_xml_files.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/gen_xml_files.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/gen_xml_files.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/lh_util.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/lh_util.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/listhandler.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/listhandler.h Mon Mar 24 05:08:00 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
    @@ -21,6 +21,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <string.h>
    --- a/listhandler/migrate.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/migrate.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/migrate.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/purple_blist_xml.c Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/listhandler/purple_blist_xml.h Mon Mar 24 05:08:00 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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/m4/pluginpack.m4 Mon Mar 24 05:08:00 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
    @@ -96,29 +96,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/menuconfig Tue Oct 09 06:21:19 2007 -0400
    +++ b/menuconfig Mon Mar 24 05:08:00 2008 -0400
    @@ -17,10 +17,11 @@
    # build the list of plugins to pass to dialog
    PLUGIN_LIST=""
    -AVAILABLE_PLUGINS=`find -name .plugin | cut -d/ -f2 | sort`
    +AVAILABLE_PLUGINS=`find -regextype posix-extended -regex '^.+/\.(finch|pidgin|purple)-plugin' | \
    + cut -d/ -f2 | sort`
    for P in ${AVAILABLE_PLUGINS}
    do
    - if [ -f ${P}/.abusive ] ; then
    + if [ -f ${P}/.abusive -o -f ${P}/.incomplete ] ; then
    continue
    fi
    @@ -41,6 +42,7 @@
    show_dialog --single-quoted --file ${PC_CONFIG} 2>${PC_RESULT}
    if [ $? != 0 ] ; then
    + rm -f ${PC_CONFIG} ${PC_RESULT}
    exit 1
    fi
    @@ -48,9 +50,11 @@
    rm -f ${PC_CONFIG} ${PC_RESULT}
    -CONFIGURE_ARGS=""
    if [ -f configure.args ] ; then
    - CONFIGURE_ARGS=`cat configure.args`
    + . configure.args
    fi
    +echo "Running configure with '${CONFIGURE_ARGS} --with-plugins=${PLUGINS}"
    +echo;
    +
    ./configure ${CONFIGURE_ARGS} --with-plugins=${PLUGINS}
    --- a/mkinstalldirs Tue Oct 09 06:21:19 2007 -0400
    +++ b/mkinstalldirs Mon Mar 24 05:08:00 2008 -0400
    @@ -1,21 +1,33 @@
    #! /bin/sh
    # mkinstalldirs --- make directory hierarchy
    -# Author: Noah Friedman <friedman@prep.ai.mit.edu>
    +
    +scriptversion=2005-06-29.22
    +
    +# Original author: Noah Friedman <friedman@prep.ai.mit.edu>
    # Created: 1993-05-16
    -# Public domain
    +# Public domain.
    +#
    +# This file is maintained in Automake, please report
    +# bugs to <bug-automake@gnu.org> or send patches to
    +# <automake-patches@gnu.org>.
    errstatus=0
    -dirmode=""
    +dirmode=
    usage="\
    -Usage: mkinstalldirs [-h] [--help] [-m mode] dir ..."
    +Usage: mkinstalldirs [-h] [--help] [--version] [-m MODE] DIR ...
    +
    +Create each directory DIR (with mode MODE, if specified), including all
    +leading file name components.
    +
    +Report bugs to <bug-automake@gnu.org>."
    # process command line arguments
    while test $# -gt 0 ; do
    case $1 in
    -h | --help | --h*) # -h for help
    - echo "$usage" 1>&2
    - exit 0
    + echo "$usage"
    + exit $?
    ;;
    -m) # -m PERM arg
    shift
    @@ -23,6 +35,10 @@
    dirmode=$1
    shift
    ;;
    + --version)
    + echo "$0 $scriptversion"
    + exit $?
    + ;;
    --) # stop option processing
    shift
    break
    @@ -50,30 +66,58 @@
    0) exit 0 ;;
    esac
    +# Solaris 8's mkdir -p isn't thread-safe. If you mkdir -p a/b and
    +# mkdir -p a/c at the same time, both will detect that a is missing,
    +# one will create a, then the other will try to create a and die with
    +# a "File exists" error. This is a problem when calling mkinstalldirs
    +# from a parallel make. We use --version in the probe to restrict
    +# ourselves to GNU mkdir, which is thread-safe.
    case $dirmode in
    '')
    - if mkdir -p -- . 2>/dev/null; then
    + if mkdir -p --version . >/dev/null 2>&1 && test ! -d ./--version; then
    echo "mkdir -p -- $*"
    exec mkdir -p -- "$@"
    + else
    + # On NextStep and OpenStep, the `mkdir' command does not
    + # recognize any option. It will interpret all options as
    + # directories to create, and then abort because `.' already
    + # exists.
    + test -d ./-p && rmdir ./-p
    + test -d ./--version && rmdir ./--version
    fi
    ;;
    *)
    - if mkdir -m "$dirmode" -p -- . 2>/dev/null; then
    + if mkdir -m "$dirmode" -p --version . >/dev/null 2>&1 &&
    + test ! -d ./--version; then
    echo "mkdir -m $dirmode -p -- $*"
    exec mkdir -m "$dirmode" -p -- "$@"
    + else
    + # Clean up after NextStep and OpenStep mkdir.
    + for d in ./-m ./-p ./--version "./$dirmode";
    + do
    + test -d $d && rmdir $d
    + done
    fi
    ;;
    esac
    for file
    do
    - set fnord `echo ":$file" | sed -ne 's/^:\//#/;s/^://;s/\// /g;s/^#/\//;p'`
    + case $file in
    + /*) pathcomp=/ ;;
    + *) pathcomp= ;;
    + esac
    + oIFS=$IFS
    + IFS=/
    + set fnord $file
    shift
    + IFS=$oIFS
    - pathcomp=
    for d
    do
    - pathcomp="$pathcomp$d"
    + test "x$d" = x && continue
    +
    + pathcomp=$pathcomp$d
    case $pathcomp in
    -*) pathcomp=./$pathcomp ;;
    esac
    @@ -84,21 +128,21 @@
    mkdir "$pathcomp" || lasterr=$?
    if test ! -d "$pathcomp"; then
    - errstatus=$lasterr
    + errstatus=$lasterr
    else
    - if test ! -z "$dirmode"; then
    + if test ! -z "$dirmode"; then
    echo "chmod $dirmode $pathcomp"
    - lasterr=""
    - chmod "$dirmode" "$pathcomp" || lasterr=$?
    + lasterr=
    + chmod "$dirmode" "$pathcomp" || lasterr=$?
    - if test ! -z "$lasterr"; then
    - errstatus=$lasterr
    - fi
    - fi
    + if test ! -z "$lasterr"; then
    + errstatus=$lasterr
    + fi
    + fi
    fi
    fi
    - pathcomp="$pathcomp/"
    + pathcomp=$pathcomp/
    done
    done
    @@ -107,5 +151,8 @@
    # Local Variables:
    # mode: shell-script
    # sh-indentation: 2
    +# eval: (add-hook 'write-file-hooks 'time-stamp)
    +# time-stamp-start: "scriptversion="
    +# time-stamp-format: "%:y-%02m-%02d.%02H"
    +# time-stamp-end: "$"
    # End:
    -# mkinstalldirs ends here
    --- a/mystatusbox/mystatusbox.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/mystatusbox/mystatusbox.c Mon Mar 24 05:08:00 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
    @@ -18,6 +18,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #define PLUGIN_ID "gtk-plugin_pack-mystatusbox"
    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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/napster/Makefile.am Mon Mar 24 05:08:00 2008 -0400
    @@ -1,9 +1,10 @@
    EXTRA_DIST=\
    + .build \
    .purple-plugin \
    - .incomplete \
    - napster16x16.png \
    - napster22x22.png \
    - napster48x48.png
    + Makefile.mingw \
    + 16/napster.png \
    + 22/napster.png \
    + 48/napster.png
    napsterdir = $(PURPLE_LIBDIR)
    @@ -21,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
    @@ -45,3 +41,5 @@
    $(DEBUG_CFLAGS) \
    $(PURPLE_CFLAGS)
    +bullshit:
    + echo $(DESTDIR)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/napster/Makefile.mingw Mon Mar 24 05:08:00 2008 -0400
    @@ -0,0 +1,11 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for napster plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = napster
    +
    +include $(PP_TOP)/win_pp.mak
    --- a/napster/napster.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/napster/napster.c Mon Mar 24 05:08:00 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
    @@ -21,6 +22,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <account.h>
    @@ -269,14 +271,17 @@
    nap_callback(gpointer data, gint source, PurpleInputCondition condition) {
    PurpleConnection *gc = data;
    struct nap_data *ndata = gc->proto_data;
    - PurpleAccount *account = purple_connection_get_account(gc);
    - PurpleConversation *c;
    - gchar *buf, *buf2, *buf3, **res;
    - unsigned short header[2];
    - int len;
    - int command;
    + PurpleAccount *account = NULL;
    + PurpleConversation *c = NULL;
    + PurpleNotifyUserInfo *pnui = NULL;
    + gchar *buf = NULL, *buf2 = NULL, *buf3 = NULL, **res = NULL;
    + unsigned short header[2] = { 0 , 0 };
    + int len = 0;
    + int command = 0;
    int i;
    + account = purple_connection_get_account(gc);
    +
    if (read(source, (void*)header, 4) != 4) {
    purple_connection_error(gc, _("Unable to read header from server"));
    return;
    @@ -292,7 +297,7 @@
    int tmp = read(source, buf + i, len - i);
    if (tmp <= 0) {
    g_free(buf);
    - buf = g_strdup_printf(_("Unable to read message from server: %s. Command is %hd, length is %hd."), strerror(errno), len, command);
    + buf = g_strdup_printf(_("Unable to read message from server: %s. Command is %hd, length is %hd."), strerror(errno), len, command);
    purple_connection_error(gc, buf);
    g_free(buf);
    return;
    @@ -441,7 +446,9 @@
    /* XXX - Format is: "Elite" 37 " " "Active" 0 0 0 0 "purple 0.63cvs" 0 0 192.168.1.41 32798 0 unknown flounder */
    res = g_strsplit(buf, " ", 2);
    /* res[0] == username */
    - purple_notify_userinfo(gc, res[0], res[1], NULL, NULL);
    + pnui = purple_notify_user_info_new();
    + purple_notify_user_info_add_pair(pnui, _("Napster User Info:"), res[1]);
    + purple_notify_userinfo(gc, res[0], pnui, NULL, NULL);
    g_strfreev(res);
    break;
    @@ -538,7 +545,7 @@
    /* Write our signon data */
    nap_write_packet(gc, 2, "%s %s 0 \"purple %s\" 0",
    purple_account_get_username(gc->account),
    - purple_connection_get_password(gc), VERSION);
    + purple_connection_get_password(gc), PP_VERSION);
    /* And set up the input watcher */
    gc->inpa = purple_input_add(ndata->fd, PURPLE_INPUT_READ, nap_callback, gc);
    @@ -580,14 +587,6 @@
    return "napster";
    }
    -static void
    -nap_list_emblems(PurpleBuddy *b, const char **se, const char **sw,
    - const char **nw, const char **ne)
    -{
    - if(!PURPLE_BUDDY_IS_ONLINE(b))
    - *se = "offline";
    -}
    -
    static GList *
    nap_status_types(PurpleAccount *account) {
    GList *types = NULL;
    @@ -639,7 +638,7 @@
    NULL, /* protocol_options */
    NO_BUDDY_ICONS, /* icon_spec */
    nap_list_icon, /* list_icon */
    - nap_list_emblems, /* list_emblems */
    + NULL, /* list_emblems */
    NULL, /* status_text */
    NULL, /* tooltip_text */
    nap_status_types, /* status_types */
    @@ -694,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 */
    };
    @@ -704,21 +705,21 @@
    PURPLE_PLUGIN_MAGIC,
    PURPLE_MAJOR_VERSION,
    PURPLE_MINOR_VERSION,
    - PURPLE_PLUGIN_PROTOCOL, /**< type */
    + PURPLE_PLUGIN_PROTOCOL, /**< type */
    NULL, /**< ui_requirement */
    0, /**< flags */
    NULL, /**< dependencies */
    - PURPLE_PRIORITY_DEFAULT, /**< priority */
    + PURPLE_PRIORITY_DEFAULT, /**< priority */
    "prpl-napster", /**< id */
    - "Napster", /**< name */
    - VERSION, /**< version */
    + N_("Napster"), /**< name */
    + PP_VERSION, /**< version */
    /** summary */
    N_("NAPSTER Protocol Plugin"),
    /** description */
    N_("NAPSTER Protocol Plugin"),
    NULL, /**< author */
    - PP_WEBSITE, /**< homepage */
    + PP_WEBSITE, /**< homepage */
    NULL, /**< load */
    NULL, /**< unload */
    @@ -738,16 +739,21 @@
    init_plugin(PurplePlugin *plugin) {
    PurpleAccountOption *option;
    - option = purple_account_option_string_new(_("Server"), "server",
    - NAP_SERVER);
    - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
    - option);
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + option = purple_account_option_string_new(_("Server"), "server", NAP_SERVER);
    + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
    option = purple_account_option_int_new(_("Port"), "port", 8888);
    - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options,
    - option);
    + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
    my_protocol = plugin;
    +
    + info.description = _(info.description);
    + info.summary = _(info.summary);
    }
    PURPLE_INIT_PLUGIN(napster, init_plugin, info);
    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 Tue Oct 09 06:21:19 2007 -0400
    +++ b/nicksaid/nicksaid.c Mon Mar 24 05:08:00 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
    @@ -18,6 +18,7 @@
    * 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #define PLUGIN_ID "gtk-plugin_pack-nicksaid"
    @@ -221,7 +222,7 @@
    while (list)
    {
    NickSaid *said = list->data;
    - g_string_append_printf(str, "%s\n", said->what);
    + g_string_append_printf(str, "%s<br/>\n", said->what);
    list = list->next;
    }
    --- a/oldlogger/oldlogger.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/oldlogger/oldlogger.c Mon Mar 24 05:08:00 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
    @@ -17,6 +17,7 @@
    * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #define OLDLOGGER_PLUGIN_ID "core-plugin_pack-oldlogger"
    @@ -99,6 +100,7 @@
    "Could not create log file %s\n", filename);
    g_free(filename);
    g_free(data);
    + log->logger_data = NULL;
    return;
    }
    data->filename = filename;
    @@ -227,6 +229,11 @@
    char *logfile;
    struct stat st;
    + for (filename = guy; *filename != '\0'; filename++) {
    + if (*filename == '/')
    + *filename = '.';
    + }
    +
    if (log->type == PURPLE_LOG_CHAT) {
    chat = g_strdup_printf("%s.chat", guy);
    g_free(guy);
    @@ -254,6 +261,7 @@
    purple_debug(PURPLE_DEBUG_ERROR, "log", "Could not create log file %s\n", filename);
    g_free(filename);
    g_free(data);
    + log->logger_data = NULL;
    return_written;
    }
    data->filename = filename;
    @@ -336,6 +344,11 @@
    char *logfile;
    struct stat st;
    + for (filename = guy; *filename != '\0'; filename++) {
    + if (*filename == '/')
    + *filename = '.';
    + }
    +
    if (log->type == PURPLE_LOG_CHAT) {
    chat = g_strdup_printf("%s.chat", guy);
    g_free(guy);
    @@ -364,6 +377,7 @@
    "Could not create log file %s\n", filename);
    g_free(filename);
    g_free(data);
    + log->logger_data = NULL;
    return_written;
    }
    data->filename = filename;
    --- a/plonkers/plonkers.c Tue Oct 09 06:21:19 2007 -0400
    +++ b/plonkers/plonkers.c Mon Mar 24 05:08:00 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
    @@ -19,6 +19,7 @@
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301, USA.
    */
    +/* If you can't figure out what this line is for, DON'T TOUCH IT. */
    #include "../common/pp_internal.h"
    #include <gtk/gtk.h>
    --- a/plugin_pack.spec.in Tue Oct 09 06:21:19 2007 -0400
    +++ b/plugin_pack.spec.in Mon Mar 24 05:08:00 2008 -0400
    @@ -23,7 +23,8 @@
    Source0: %{name}-%{version}.tar.bz2
    BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
    -BuildRequires: pkgconfig, libtool, gettext, xmms-devel
    +BuildRequires: pkgconfig, libtool, gettext
    +%{!?_without_xmms:BuildRequires: xmms-devel}
    BuildRequires: pidgin-devel >= %{pidgin_major_ver}.%{pidgin_minor_ver}, pidgin-devel < %{pidgin_next_major_ver}
    %if "%{_vendor}" == "MandrakeSoft" || "%{_vendor}" == "Mandrakesoft"
    BuildRequires: libgtk+2.0_0-devel
    @@ -36,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
    @@ -74,6 +76,12 @@
    %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
    +
    * Mon Apr 30 2007 Stu Tomlinson <stu@nosnilmot.com>
    - Update for the rename of Gaim to Pidgin
    - New URL for our new website
    --- a/po/POTFILES.in Tue Oct 09 06:21:19 2007 -0400
    +++ b/po/POTFILES.in Mon Mar 24 05:08:00 2008 -0400
    @@ -5,17 +5,17 @@
    bash/bash.c
    bit/bit.c
    blistops/blistops.c
    -broadcast/broadcast.c
    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
    dice/dice.c
    difftopic/difftopic.c
    eight_ball/eight_ball.c
    +enhancedhist/enhancedhist.c
    findip/findip.c
    findip/findip.c
    flip/flip.c
    @@ -62,5 +62,10 @@
    stocker/stocker_prefs.c
    switchspell/switchspell.c
    talkfilters/talkfilters.c
    +timelog/log-widget.c
    +timelog/range-widget.c
    +timelog/timelog.c
    +timelog/timelog.h
    xchat-chats/xchat-chats.c
    xchat-chats/xchat-chats.c
    +xmmsremote/xmmsremote.c
    --- a/po/en_AU.po Tue Oct 09 06:21:19 2007 -0400
    +++ b/po/en_AU.po Mon Mar 24 05:08:00 2008 -0400
    @@ -8,7 +8,7 @@
    msgstr ""
    "Project-Id-Version: plugin_pack 0.1cvs\n"
    "Report-Msgid-Bugs-To: \n"
    -"POT-Creation-Date: 2007-10-09 00:11-0400\n"
    +"POT-Creation-Date: 2007-10-22 00:43-0400\n"
    "PO-Revision-Date: 2005-11-21 10:36+1100\n"
    "Last-Translator: Peter Lawler <trans@six-by-nine.com.au>\n"
    "Language-Team: English/AU <trans@six-by-nine.com.au>\n"
    @@ -16,6 +16,18 @@
    "Content-Type: text/plain; charset=UTF-8\n"
    "Content-Transfer-Encoding: 8bit\n"
    +#: ../album/album.c:268
    +msgid "Album"
    +msgstr ""
    +
    +#: ../album/album.c:269
    +msgid "Archives buddy icons."
    +msgstr ""
    +
    +#: ../album/album.c:270
    +msgid "Enable this plugin to automatically archive all buddy icons."
    +msgstr ""
    +
    #: ../album/album-ui.c:293
    msgid ""
    "<span size='larger' weight='bold'>Unrecognized file type</span>\n"
    @@ -92,6 +104,7 @@
    #: ../album/album-ui.c:1142 ../broadcast/broadcast.c:117
    #: ../buddytime/buddytime.c:163 ../groupmsg/groupmsg.c:111
    +#: ../timelog/timelog.c:130
    msgid "Cancel"
    msgstr ""
    @@ -99,120 +112,113 @@
    msgid "_View Buddy Icons"
    msgstr ""
    -#: ../album/album.c:268
    -msgid "Album"
    -msgstr ""
    -
    -#: ../album/album.c:269
    -msgid "Archives buddy icons."
    -msgstr ""
    -
    -#: ../album/album.c:270
    -msgid "Enable this plugin to automatically archive all buddy icons."
    -msgstr ""
    -
    #. XXX: There should be a way to reset to the default/account-default autoreply
    -#: ../autoreply/autoreply.c:221
    +#: ../autoreply/autoreply.c:226
    #, c-format
    msgid "Set autoreply message for %s"
    msgstr ""
    -#: ../autoreply/autoreply.c:223
    -msgid "Set Autoreply Message"
    -msgstr ""
    -
    -#: ../autoreply/autoreply.c:224
    -msgid ""
    -"The following message will be sent to the buddy when the buddy sends you a "
    -"message and autoreply is enabled."
    -msgstr ""
    -
    #: ../autoreply/autoreply.c:228
    +msgid "Set Autoreply Message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:229
    +msgid ""
    +"The following message will be sent to the buddy when the buddy sends you a "
    +"message and autoreply is enabled."
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:233
    msgid "_Save"
    msgstr ""
    -#: ../autoreply/autoreply.c:229 ../listhandler/aim_blt_files.c:301
    -#: ../listhandler/aim_blt_files.c:465 ../listhandler/gen_xml_files.c:163
    +#: ../autoreply/autoreply.c:234 ../listhandler/aim_blt_files.c:301
    +#: ../listhandler/aim_blt_files.c:465 ../listhandler/alias_xml_files.c:210
    +#: ../listhandler/alias_xml_files.c:269 ../listhandler/gen_xml_files.c:163
    #: ../listhandler/gen_xml_files.c:355 ../listhandler/migrate.c:145
    -#: ../listhandler/migrate.c:184
    +#: ../listhandler/migrate.c:184 ../listhandler/purple_blist_xml.c:229
    msgid "_Cancel"
    msgstr ""
    -#: ../autoreply/autoreply.c:245
    +#: ../autoreply/autoreply.c:250
    msgid "Set _Autoreply Message"
    msgstr ""
    -#: ../autoreply/autoreply.c:258
    +#: ../autoreply/autoreply.c:263
    msgid "Autoreply message"
    msgstr ""
    -#: ../autoreply/autoreply.c:357
    +#: ../autoreply/autoreply.c:265
    +msgid "Turn off autoreply"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:370
    msgid "Send autoreply messages when"
    msgstr ""
    -#: ../autoreply/autoreply.c:361
    +#: ../autoreply/autoreply.c:374
    msgid "When my account is _away"
    msgstr ""
    -#: ../autoreply/autoreply.c:365
    +#: ../autoreply/autoreply.c:378
    msgid "When my account is _idle"
    msgstr ""
    -#: ../autoreply/autoreply.c:369
    +#: ../autoreply/autoreply.c:382
    msgid "_Default reply"
    msgstr ""
    -#: ../autoreply/autoreply.c:376
    +#: ../autoreply/autoreply.c:389
    msgid ""
    "Autoreply Prefix\n"
    "(only when necessary)"
    msgstr ""
    -#: ../autoreply/autoreply.c:379
    +#: ../autoreply/autoreply.c:392
    msgid "Status message"
    msgstr ""
    -#: ../autoreply/autoreply.c:383
    +#: ../autoreply/autoreply.c:396
    msgid "Autoreply with status message"
    msgstr ""
    -#: ../autoreply/autoreply.c:385
    -msgid "Never"
    -msgstr ""
    -
    -#: ../autoreply/autoreply.c:387
    -msgid "Always when there is a status message"
    -msgstr ""
    -
    -#: ../autoreply/autoreply.c:389
    -msgid "Only when there's no autoreply message"
    -msgstr ""
    -
    -#: ../autoreply/autoreply.c:394
    -msgid "Delay between autoreplies"
    -msgstr ""
    -
    #: ../autoreply/autoreply.c:398
    -msgid "_Minimum delay (mins)"
    +msgid "Never"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:400
    +msgid "Always when there is a status message"
    msgstr ""
    #: ../autoreply/autoreply.c:402
    +msgid "Only when there's no autoreply message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:407
    +msgid "Delay between autoreplies"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:411
    +msgid "_Minimum delay (mins)"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:415
    msgid "Times to send autoreplies"
    msgstr ""
    -#: ../autoreply/autoreply.c:406
    +#: ../autoreply/autoreply.c:419
    msgid "Ma_ximum count"
    msgstr ""
    -#: ../autoreply/autoreply.c:464
    +#: ../autoreply/autoreply.c:477
    msgid "Autoreply"
    msgstr ""
    -#: ../autoreply/autoreply.c:465
    +#: ../autoreply/autoreply.c:478
    msgid "Autoreply for all the protocols"
    msgstr ""
    -#: ../autoreply/autoreply.c:466
    +#: ../autoreply/autoreply.c:479
    msgid ""
    "This plugin lets you set autoreply message for any protocol. You can set the "
    "global autoreply message from the plugin options dialog. To set some "
    @@ -221,13 +227,13 @@
    "the `Advanced' tab of the account edit dialog."
    msgstr ""
    -#: ../autoreply/autoreply.c:476
    +#: ../autoreply/autoreply.c:489
    msgid ""
    "I am currently not available. Please leave your message, and I will get back "
    "to you as soon as possible."
    msgstr ""
    -#: ../autoreply/autoreply.c:481
    +#: ../autoreply/autoreply.c:494
    msgid "This is an autoreply: "
    msgstr ""
    @@ -505,11 +511,13 @@
    msgid "Buddy Time"
    msgstr ""
    -#: ../buddytime/buddytime.c:256
    +#: ../buddytime/buddytime.c:256 ../common/core_template.c:80
    +#: ../common/gtk_template.c:84
    msgid "summary"
    msgstr ""
    -#: ../buddytime/buddytime.c:257
    +#: ../buddytime/buddytime.c:257 ../common/core_template.c:81
    +#: ../common/gtk_template.c:85
    msgid "description"
    msgstr ""
    @@ -529,6 +537,10 @@
    "FUNCTIONAL! IT IS USELESS!"
    msgstr ""
    +#: ../common/core_template.c:79 ../common/gtk_template.c:83
    +msgid "unnamed"
    +msgstr ""
    +
    #: ../convbadger/convbadger.c:192
    msgid "Conversation Badger"
    msgstr ""
    @@ -537,6 +549,20 @@
    msgid "Badges conversations with the protocol icon."
    msgstr ""
    +#: ../dewysiwygification/dewysiwygification.c:99
    +msgid "DeWYSIWYGification Plugin"
    +msgstr ""
    +
    +#: ../dewysiwygification/dewysiwygification.c:101
    +msgid "Lets you type in HTML without it being escaped to entities."
    +msgstr ""
    +
    +#: ../dewysiwygification/dewysiwygification.c:102
    +msgid ""
    +"Lets you type in HTML without it being escaped to entities. This will not "
    +"work well for some protocols. Use \"&lt;\" for a literal \"<\"."
    +msgstr ""
    +
    #: ../dice/dice.c:81
    msgid "dice [dice] [sides]: rolls dice number of sides sided dice"
    msgstr ""
    @@ -568,33 +594,53 @@
    msgid "Show the old topic when the topic in a chat room changes."
    msgstr ""
    -#: ../eight_ball/eight_ball.c:134
    +#: ../eight_ball/eight_ball.c:135
    msgid "8ball: sends a random 8ball message"
    msgstr ""
    -#: ../eight_ball/eight_ball.c:135
    -msgid "sgball: sends a random Stargate Ball message"
    -msgstr ""
    -
    #: ../eight_ball/eight_ball.c:136
    +msgid "sgball: sends a random Stargate Ball message"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:137
    msgid "fullcrap: sends random fooling blabber"
    msgstr ""
    -#: ../eight_ball/eight_ball.c:209
    -msgid "Magic 8 Ball"
    -msgstr ""
    -
    #: ../eight_ball/eight_ball.c:210
    -msgid "Provides Magic 8-ball like functionality"
    +msgid "Magic 8 Ball"
    msgstr ""
    #: ../eight_ball/eight_ball.c:211
    +msgid "Provides Magic 8-ball like functionality"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:212
    msgid ""
    "Provides Magic 8-ball like functionality with the /8ball command, as well as "
    "similar functionality for common Stargate words or phrases with the /sg-ball "
    "command."
    msgstr ""
    +#: ../findip/findip.c:60
    +msgid "Looked up IP: 127.0.0.1\n"
    +msgstr ""
    +
    +#: ../findip/findip.c:64
    +msgid "Yo! What's your IP?"
    +msgstr ""
    +
    +#: ../findip/findip.c:83
    +msgid "Looking up the IP ...\n"
    +msgstr ""
    +
    +#: ../findip/findip.c:96
    +msgid "Find IP"
    +msgstr ""
    +
    +#: ../findip/findip.c:124
    +msgid "Notify the user that you are trying to get the IP"
    +msgstr ""
    +
    #: ../flip/flip.c:60
    msgid "Outputs the results of flipping a coin"
    msgstr ""
    @@ -885,44 +931,51 @@
    "Please do not use if you have amnesia."
    msgstr ""
    -#. specify our help string and register our command
    -#: ../irc-more/irc-more.c:212
    -msgid "notice target message: Send a notice to the specified target."
    -msgstr ""
    -
    -#. Alphabetize the option label strings
    -#: ../irc-more/irc-more.c:232
    -msgid "CTCP Version reply"
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:235
    -msgid "Default Quit Message"
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:238
    -msgid "Default Part Message"
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:241
    -msgid "User Modes On Connect"
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:264
    -msgid "Seconds to wait before rejoining"
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:325
    -msgid "IRC More"
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:326
    -msgid "Adds additional IRC features."
    -msgstr ""
    -
    -#: ../irc-more/irc-more.c:327
    +#: ../infopane/infopane.c:148 ../infopane/infopane.c:262
    +msgid "Libpurple and Pidgin are too old!\n"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:149
    +msgid "Incompatible Plugin"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:150
    +msgid "You need to update Pidgin!"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:151
    msgid ""
    -"Adds additional IRC features, including a customizable quit message, a "
    -"customizable CTCP VERSION reply, and the /notice command for notices."
    +"This plugin is incompatible with the running version of Pidgin and Libpurple "
    +"because it is too old. Please upgrade to the newest version of Pidgin."
    +msgstr ""
    +
    +#. XXX: Is there a better way than this? There really should be.
    +#: ../infopane/infopane.c:183
    +msgid "Position of the infopane ('top', 'bottom' or 'none')"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:187
    +msgid "Show icon in the tabs"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:191
    +msgid "Always show the tab"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:251
    +msgid "Infopane Options"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:252 ../infopane/infopane.c:253
    +msgid "Allow customizing the details information in conversation windows."
    +msgstr ""
    +
    +#: ../infopane/infopane.c:264
    +msgid "Incompatible Plugin! - Check plugin details!"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:265 ../infopane/infopane.c:266
    +msgid "This plugin is NOT compatible with this version of Pidgin!"
    msgstr ""
    #: ../irchelper/irchelper.c:937
    @@ -998,6 +1051,50 @@
    "- Suppression of various useless messages"
    msgstr ""
    +#. specify our help string and register our command
    +#: ../irc-more/irc-more.c:219
    +msgid "notice target message: Send a notice to the specified target."
    +msgstr ""
    +
    +#. Alphabetize the option label strings
    +#: ../irc-more/irc-more.c:239
    +msgid "CTCP Version reply"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:242
    +msgid "Default Quit Message"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:245
    +msgid "Default Part Message"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:248
    +msgid "Set User Modes On Connect"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:251
    +msgid "Unset User Modes On Connect"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:273
    +msgid "Seconds to wait before rejoining"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:334
    +msgid "IRC More"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:335
    +msgid "Adds additional IRC features."
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:336
    +msgid ""
    +"Adds additional IRC features, including a customizable quit message, a "
    +"customizable CTCP VERSION reply, and the /notice command for notices."
    +msgstr ""
    +
    #: ../irssi/datechange.c:79
    #, c-format
    msgid "Day changed to %s"
    @@ -1139,9 +1236,10 @@
    #. create a field
    #: ../listhandler/aim_blt_files.c:287 ../listhandler/aim_blt_files.c:453
    +#: ../listhandler/alias_xml_files.c:203 ../listhandler/alias_xml_files.c:260
    #: ../listhandler/gen_xml_files.c:148 ../listhandler/gen_xml_files.c:341
    #: ../listhandler/migrate.c:129 ../listhandler/migrate.c:169
    -#: ../schedule/pidgin-schedule.c:283
    +#: ../listhandler/purple_blist_xml.c:221 ../schedule/pidgin-schedule.c:283
    msgid "Account"
    msgstr ""
    @@ -1150,11 +1248,13 @@
    msgid "List Handler: Importing"
    msgstr ""
    -#: ../listhandler/aim_blt_files.c:298 ../listhandler/gen_xml_files.c:160
    +#: ../listhandler/aim_blt_files.c:298 ../listhandler/alias_xml_files.c:266
    +#: ../listhandler/gen_xml_files.c:160
    msgid "Choose the account to import to:"
    msgstr ""
    -#: ../listhandler/aim_blt_files.c:299 ../listhandler/gen_xml_files.c:161
    +#: ../listhandler/aim_blt_files.c:299 ../listhandler/alias_xml_files.c:267
    +#: ../listhandler/gen_xml_files.c:161 ../listhandler/purple_blist_xml.c:228
    msgid "_Import"
    msgstr ""
    @@ -1167,11 +1267,13 @@
    msgid "List Handler: Exporting"
    msgstr ""
    -#: ../listhandler/aim_blt_files.c:464 ../listhandler/gen_xml_files.c:354
    +#: ../listhandler/aim_blt_files.c:464 ../listhandler/alias_xml_files.c:209
    +#: ../listhandler/gen_xml_files.c:354
    msgid "Choose the account to export from:"
    msgstr ""
    -#: ../listhandler/aim_blt_files.c:465 ../listhandler/gen_xml_files.c:355
    +#: ../listhandler/aim_blt_files.c:465 ../listhandler/alias_xml_files.c:210
    +#: ../listhandler/gen_xml_files.c:355
    msgid "_Export"
    msgstr ""
    @@ -1179,24 +1281,29 @@
    msgid "Choose An AIM .blt File To Import"
    msgstr ""
    +#: ../listhandler/alias_xml_files.c:182
    +msgid "Save Generic .alist File"
    +msgstr ""
    +
    #. and finally we can create the request
    -#: ../listhandler/gen_xml_files.c:159
    +#: ../listhandler/alias_xml_files.c:208 ../listhandler/gen_xml_files.c:353
    +msgid "Listhandler - Exporting"
    +msgstr ""
    +
    +#. and finally we can create the request
    +#: ../listhandler/alias_xml_files.c:265 ../listhandler/gen_xml_files.c:159
    +#: ../listhandler/purple_blist_xml.c:226
    msgid "Listhandler - Importing"
    msgstr ""
    +#: ../listhandler/alias_xml_files.c:296 ../listhandler/gen_xml_files.c:366
    +msgid "Choose A Generic Buddy List File To Import"
    +msgstr ""
    +
    #: ../listhandler/gen_xml_files.c:315
    msgid "Save Generic .blist File"
    msgstr ""
    -#. and finally we can create the request
    -#: ../listhandler/gen_xml_files.c:353
    -msgid "Listhandler - Exporting"
    -msgstr ""
    -
    -#: ../listhandler/gen_xml_files.c:366
    -msgid "Choose A Generic Buddy List File To Import"
    -msgstr ""
    -
    #: ../listhandler/listhandler.c:39
    msgid "Copy Buddies From One Account to Another"
    msgstr ""
    @@ -1266,6 +1373,14 @@
    msgid "C_opy"
    msgstr ""
    +#: ../listhandler/purple_blist_xml.c:227
    +msgid "Choose the account whose buddy list you wish to restore:"
    +msgstr ""
    +
    +#: ../listhandler/purple_blist_xml.c:253
    +msgid "Choose a Libpurple blist.xml File To Import"
    +msgstr ""
    +
    #: ../mystatusbox/mystatusbox.c:376
    msgid "All"
    msgstr ""
    @@ -1981,5 +2096,74 @@
    "pansy, pirate, postmodern, redneck, valspeak, and warez."
    msgstr ""
    +#: ../timelog/log-widget.c:224
    +#, c-format
    +msgid "Conversation in %s on %s"
    +msgstr ""
    +
    +#: ../timelog/log-widget.c:226
    +#, c-format
    +msgid "Conversation with %s on %s"
    +msgstr ""
    +
    +#. No logs were found.
    +#: ../timelog/log-widget.c:288
    +msgid "No logs were found"
    +msgstr ""
    +
    +#: ../timelog/range-widget.c:226
    +msgid "Start Time"
    +msgstr ""
    +
    +#: ../timelog/range-widget.c:230
    +msgid "End Time"
    +msgstr ""
    +
    +#: ../timelog/range-widget.c:330
    +msgid "Select Time Range"
    +msgstr ""
    +
    +#: ../timelog/timelog.c:128
    +msgid "Select account to view logs for:"
    +msgstr ""
    +
    +#: ../timelog/timelog.c:129
    +msgid "Select Account"
    +msgstr ""
    +
    +#: ../timelog/timelog.c:139
    +msgid "Select Account/Time"
    +msgstr ""
    +
    +#. *< type
    +#. *< ui_req
    +#. *< flags
    +#. *< deps
    +#. *< priority
    +#. *< id
    +#. *< name
    +#. *< version
    +#. * summary