pidgin/purple-plugin-pack

Merge with default.
org.guifications.plugins.debian
2009-08-27, John Bailey
cb50572d5f9e
Merge with default.
  • +43 -0
    .hgignore
  • +17 -0
    .hgtags
  • +18 -0
    .todo
  • +66 -0
    AUTHORS
  • +341 -0
    COPYING
  • +246 -0
    ChangeLog
  • +229 -0
    INSTALL
  • +41 -0
    INSTALL.WIN32
  • +59 -0
    Makefile.am
  • +53 -0
    Makefile.mingw
  • +25 -0
    NEWS
  • +11 -0
    README
  • +1 -0
    VERSION
  • +1 -0
    VERSION.in
  • +31 -0
    album/ChangeLog
  • +34 -0
    album/Makefile.am
  • +14 -0
    album/Makefile.mingw
  • +15 -0
    album/README
  • +3 -0
    album/TODO
  • +1219 -0
    album/album-ui.c
  • +47 -0
    album/album-ui.h
  • +280 -0
    album/album.c
  • +36 -0
    album/album.h
  • +9 -0
    album/plugins.cfg
  • +159 -0
    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
  • +351 -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
  • +88 -0
    autoprofile/component.c
  • +77 -0
    autoprofile/component.h
  • +341 -0
    autoprofile/gtk_actions.c
  • +486 -0
    autoprofile/gtk_away_msgs.c
  • +778 -0
    autoprofile/gtk_widget.c
  • +9 -0
    autoprofile/plugins.cfg
  • +750 -0
    autoprofile/preferences.c
  • +49 -0
    autoprofile/sizes.h
  • +221 -0
    autoprofile/utility.c
  • +41 -0
    autoprofile/utility.h
  • +189 -0
    autoprofile/utility_rfc822.c
  • +607 -0
    autoprofile/widget.c
  • +96 -0
    autoprofile/widget.h
  • +28 -0
    autoreply/Makefile.am
  • +12 -0
    autoreply/Makefile.mingw
  • +506 -0
    autoreply/autoreply.c
  • +9 -0
    autoreply/plugins.cfg
  • +25 -0
    awaynotify/Makefile.am
  • +268 -0
    awaynotify/awaynotify.c
  • +9 -0
    awaynotify/plugins.cfg
  • +28 -0
    bash/Makefile.am
  • +12 -0
    bash/Makefile.mingw
  • +171 -0
    bash/bash.c
  • +9 -0
    bash/plugins.cfg
  • +28 -0
    bit/Makefile.am
  • +12 -0
    bit/Makefile.mingw
  • +241 -0
    bit/bit.c
  • +9 -0
    bit/plugins.cfg
  • +29 -0
    blistops/Makefile.am
  • +12 -0
    blistops/Makefile.mingw
  • +311 -0
    blistops/blistops.c
  • +9 -0
    blistops/plugins.cfg
  • +50 -0
    buddytime/Makefile.am
  • +412 -0
    buddytime/buddyedit.c
  • +490 -0
    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
  • +18 -0
    buddytime/plugins.cfg
  • +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
  • +26 -0
    chronic/Makefile.am
  • +112 -0
    chronic/chronic.c
  • +9 -0
    chronic/plugins.cfg
  • +28 -0
    colorize/Makefile.am
  • +12 -0
    colorize/Makefile.mingw
  • +298 -0
    colorize/colorize.c
  • +9 -0
    colorize/plugins.cfg
  • +5 -0
    common/Makefile.am
  • +154 -0
    common/glib_compat.h
  • +89 -0
    common/gtk_template.c
  • +53 -0
    common/pp_internal.h
  • +84 -0
    common/purple_template.c
  • +465 -0
    configure.ac
  • +29 -0
    convbadger/Makefile.am
  • +12 -0
    convbadger/Makefile.mingw
  • +206 -0
    convbadger/convbadger.c
  • +10 -0
    convbadger/plugins.cfg
  • +28 -0
    dewysiwygification/Makefile.am
  • +12 -0
    dewysiwygification/Makefile.mingw
  • +131 -0
    dewysiwygification/dewysiwygification.c
  • +9 -0
    dewysiwygification/plugins.cfg
  • +28 -0
    dice/Makefile.am
  • +12 -0
    dice/Makefile.mingw
  • +387 -0
    dice/dice.c
  • +9 -0
    dice/plugins.cfg
  • +28 -0
    difftopic/Makefile.am
  • +12 -0
    difftopic/Makefile.mingw
  • +224 -0
    difftopic/difftopic.c
  • +9 -0
    difftopic/plugins.cfg
  • +2 -0
    doc/Makefile.am
  • +29 -0
    doc/quickhack.txt
  • +28 -0
    eight_ball/Makefile.am
  • +12 -0
    eight_ball/Makefile.mingw
  • +426 -0
    eight_ball/eight_ball.c
  • +9 -0
    eight_ball/plugins.cfg
  • +28 -0
    enhancedhist/Makefile.am
  • +12 -0
    enhancedhist/Makefile.mingw
  • +414 -0
    enhancedhist/enhancedhist.c
  • +9 -0
    enhancedhist/plugins.cfg
  • +28 -0
    findip/Makefile.am
  • +12 -0
    findip/Makefile.mingw
  • +178 -0
    findip/findip.c
  • +9 -0
    findip/plugins.cfg
  • +29 -0
    flip/Makefile.am
  • +12 -0
    flip/Makefile.mingw
  • +119 -0
    flip/flip.c
  • +10 -0
    flip/plugins.cfg
  • +28 -0
    gRIM/Makefile.am
  • +12 -0
    gRIM/Makefile.mingw
  • +380 -0
    gRIM/gRIM.c
  • +9 -0
    gRIM/plugins.cfg
  • +28 -0
    google/Makefile.am
  • +12 -0
    google/Makefile.mingw
  • +325 -0
    google/google.c
  • +8 -0
    google/plugins.cfg
  • +28 -0
    groupmsg/Makefile.am
  • +11 -0
    groupmsg/Makefile.mingw
  • +189 -0
    groupmsg/groupmsg.c
  • +9 -0
    groupmsg/plugins.cfg
  • +28 -0
    hideconv/Makefile.am
  • +12 -0
    hideconv/Makefile.mingw
  • +291 -0
    hideconv/hideconv.c
  • +10 -0
    hideconv/plugins.cfg
  • +28 -0
    highlight/Makefile.am
  • +12 -0
    highlight/Makefile.mingw
  • +339 -0
    highlight/highlight.c
  • +9 -0
    highlight/plugins.cfg
  • +60 -0
    ignorance/Makefile.am
  • +701 -0
    ignorance/callbacks.c
  • +60 -0
    ignorance/callbacks.h
  • +1209 -0
    ignorance/ignorance.c
  • +20 -0
    ignorance/ignorance.conf
  • +45 -0
    ignorance/ignorance.h
  • +62 -0
    ignorance/ignorance_denizen.c
  • +42 -0
    ignorance/ignorance_denizen.h
  • +72 -0
    ignorance/ignorance_internal.h
  • +328 -0
    ignorance/ignorance_level.c
  • +99 -0
    ignorance/ignorance_level.h
  • +243 -0
    ignorance/ignorance_rule.c
  • +117 -0
    ignorance/ignorance_rule.h
  • +45 -0
    ignorance/ignorance_violation.c
  • +32 -0
    ignorance/ignorance_violation.h
  • +284 -0
    ignorance/interface.c
  • +48 -0
    ignorance/interface.h
  • +10 -0
    ignorance/plugins.cfg
  • +4952 -0
    ignorance/regex.c
  • +490 -0
    ignorance/regex.h
  • +158 -0
    ignorance/support.c
  • +58 -0
    ignorance/support.h
  • +28 -0
    ignore/Makefile.am
  • +12 -0
    ignore/Makefile.mingw
  • +311 -0
    ignore/ignore.c
  • +9 -0
    ignore/plugins.cfg
  • +28 -0
    infopane/Makefile.am
  • +12 -0
    infopane/Makefile.mingw
  • +261 -0
    infopane/infopane.c
  • +10 -0
    infopane/plugins.cfg
  • +28 -0
    irc-more/Makefile.am
  • +12 -0
    irc-more/Makefile.mingw
  • +355 -0
    irc-more/irc-more.c
  • +10 -0
    irc-more/plugins.cfg
  • +131 -0
    irchelper/ChangeLog
  • +37 -0
    irchelper/IDEAS
  • +28 -0
    irchelper/Makefile.am
  • +12 -0
    irchelper/Makefile.mingw
  • +31 -0
    irchelper/PHILOSOPHY
  • +91 -0
    irchelper/README
  • +1303 -0
    irchelper/irchelper.c
  • +9 -0
    irchelper/plugins.cfg
  • +44 -0
    irssi/Makefile.am
  • +20 -0
    irssi/Makefile.mingw
  • +136 -0
    irssi/datechange.c
  • +30 -0
    irssi/datechange.h
  • +146 -0
    irssi/irssi.c
  • +41 -0
    irssi/irssi.h
  • +126 -0
    irssi/lastlog.c
  • +29 -0
    irssi/lastlog.h
  • +348 -0
    irssi/layout.c
  • +30 -0
    irssi/layout.h
  • +10 -0
    irssi/plugins.cfg
  • +247 -0
    irssi/textfmt.c
  • +29 -0
    irssi/textfmt.h
  • +181 -0
    irssi/window.c
  • +29 -0
    irssi/window.h
  • +29 -0
    lastseen/Makefile.am
  • +12 -0
    lastseen/Makefile.mingw
  • +248 -0
    lastseen/lastseen.c
  • +10 -0
    lastseen/plugins.cfg
  • +44 -0
    listhandler/Makefile.am
  • +22 -0
    listhandler/Makefile.mingw
  • +479 -0
    listhandler/aim_blt_files.c
  • +31 -0
    listhandler/aim_blt_files.h
  • +301 -0
    listhandler/alias_xml_files.c
  • +30 -0
    listhandler/alias_xml_files.h
  • +372 -0
    listhandler/gen_xml_files.c
  • +30 -0
    listhandler/gen_xml_files.h
  • +85 -0
    listhandler/lh_util.c
  • +31 -0
    listhandler/lh_util.h
  • +130 -0
    listhandler/listhandler.c
  • +42 -0
    listhandler/listhandler.h
  • +186 -0
    listhandler/migrate.c
  • +28 -0
    listhandler/migrate.h
  • +9 -0
    listhandler/plugins.cfg
  • +256 -0
    listhandler/purple_blist_xml.c
  • +29 -0
    listhandler/purple_blist_xml.h
  • +28 -0
    listlog/Makefile.am
  • +12 -0
    listlog/Makefile.mingw
  • +137 -0
    listlog/listlog.c
  • +9 -0
    listlog/plugins.cfg
  • +158 -0
    mkinstalldirs
  • +29 -0
    msglen/Makefile.am
  • +12 -0
    msglen/Makefile.mingw
  • +258 -0
    msglen/msglen.c
  • +8 -0
    msglen/plugins.cfg
  • +28 -0
    mystatusbox/Makefile.am
  • +12 -0
    mystatusbox/Makefile.mingw
  • +574 -0
    mystatusbox/mystatusbox.c
  • +9 -0
    mystatusbox/plugins.cfg
  • +0 -0
    napster/16/napster.png
  • +0 -0
    napster/22/napster.png
  • +0 -0
    napster/48/napster.png
  • +47 -0
    napster/Makefile.am
  • +11 -0
    napster/Makefile.mingw
  • +759 -0
    napster/napster.c
  • +10 -0
    napster/plugins.cfg
  • +28 -0
    nicksaid/Makefile.am
  • +12 -0
    nicksaid/Makefile.mingw
  • +672 -0
    nicksaid/nicksaid.c
  • +9 -0
    nicksaid/plugins.cfg
  • +28 -0
    nomobility/Makefile.am
  • +12 -0
    nomobility/Makefile.mingw
  • +314 -0
    nomobility/nomobility.c
  • +9 -0
    nomobility/plugins.cfg
  • +0 -0
    nsis/header.bmp
  • +17 -0
    nsis/translations/english.nsh
  • +28 -0
    oldlogger/Makefile.am
  • +12 -0
    oldlogger/Makefile.mingw
  • +520 -0
    oldlogger/oldlogger.c
  • +8 -0
    oldlogger/plugins.cfg
  • +30 -0
    plonkers/Makefile.am
  • +360 -0
    plonkers/plonkers.c
  • +9 -0
    plonkers/plugins.cfg
  • +66 -0
    plugin_pack.nsi
  • +613 -0
    plugin_pack.py
  • +115 -0
    plugin_pack.spec.in
  • +68 -0
    po/Makefile.mingw
  • +96 -0
    po/POTFILES.in
  • +1 -0
    po/POTFILES.skip
  • +380 -0
    po/check_po.pl
  • +3514 -0
    po/en_AU.po
  • +3653 -0
    po/es_ES.po
  • +3901 -0
    po/fr.po
  • +100 -0
    po/stats.pl
  • +3893 -0
    po/vi.po
  • +116 -0
    pp_config.h.mingw
  • +32 -0
    schedule/Makefile.am
  • +714 -0
    schedule/pidgin-schedule.c
  • +10 -0
    schedule/plugins.cfg
  • +604 -0
    schedule/schedule.c
  • +117 -0
    schedule/schedule.h
  • +28 -0
    sepandtab/Makefile.am
  • +12 -0
    sepandtab/Makefile.mingw
  • +9 -0
    sepandtab/plugins.cfg
  • +158 -0
    sepandtab/sepandtab.c
  • +28 -0
    showoffline/Makefile.am
  • +12 -0
    showoffline/Makefile.mingw
  • +9 -0
    showoffline/plugins.cfg
  • +145 -0
    showoffline/showoffline.c
  • +28 -0
    simfix/Makefile.am
  • +12 -0
    simfix/Makefile.mingw
  • +8 -0
    simfix/plugins.cfg
  • +141 -0
    simfix/simfix.c
  • +28 -0
    slashexec/Makefile.am
  • +11 -0
    slashexec/Makefile.mingw
  • +9 -0
    slashexec/plugins.cfg
  • +540 -0
    slashexec/slashexec.c
  • +58 -0
    smartear/Makefile.am
  • +122 -0
    smartear/gtksmartear.c
  • +566 -0
    smartear/interface.c
  • +27 -0
    smartear/plugins.cfg
  • +266 -0
    smartear/smartear.c
  • +25 -0
    snpp/Makefile.am
  • +11 -0
    snpp/Makefile.mingw
  • +9 -0
    snpp/plugins.cfg
  • +600 -0
    snpp/snpp.c
  • +0 -0
    snpp/snpp16.png
  • +71 -0
    splitter/ChangeLog
  • +32 -0
    splitter/Makefile.am
  • +12 -0
    splitter/Makefile.mingw
  • +91 -0
    splitter/README
  • +9 -0
    splitter/plugins.cfg
  • +563 -0
    splitter/splitter.c
  • +29 -0
    sslinfo/Makefile.am
  • +12 -0
    sslinfo/Makefile.mingw
  • +9 -0
    sslinfo/plugins.cfg
  • +149 -0
    sslinfo/sslinfo.c
  • +35 -0
    stocker/Makefile.am
  • +513 -0
    stocker/gtkticker.c
  • +99 -0
    stocker/gtkticker.h
  • +10 -0
    stocker/plugins.cfg
  • +412 -0
    stocker/stocker.c
  • +323 -0
    stocker/stocker_prefs.c
  • +39 -0
    stocker/stocker_prefs.h
  • +37 -0
    switchspell/Makefile.am
  • +26 -0
    switchspell/Makefile.mingw
  • +10 -0
    switchspell/plugins.cfg
  • +357 -0
    switchspell/switchspell.c
  • +28 -0
    talkfilters/Makefile.am
  • +10 -0
    talkfilters/plugins.cfg
  • +416 -0
    talkfilters/talkfilters.c
  • +32 -0
    timelog/Makefile.am
  • +18 -0
    timelog/Makefile.mingw
  • +381 -0
    timelog/log-widget.c
  • +28 -0
    timelog/log-widget.h
  • +9 -0
    timelog/plugins.cfg
  • +364 -0
    timelog/range-widget.c
  • +30 -0
    timelog/range-widget.h
  • +199 -0
    timelog/timelog.c
  • +34 -0
    timelog/timelog.h
  • +111 -0
    win_pp.mak
  • +4 -0
    xchat-chats/.todo
  • +29 -0
    xchat-chats/Makefile.am
  • +9 -0
    xchat-chats/plugins.cfg
  • +498 -0
    xchat-chats/xchat-chats.c
  • +5074 -0
    xchat-chats/xtext.c
  • +271 -0
    xchat-chats/xtext.h
  • +42 -0
    xmmsremote/Makefile.am
  • +0 -0
    xmmsremote/next.png
  • +0 -0
    xmmsremote/pause.png
  • +0 -0
    xmmsremote/play.png
  • +10 -0
    xmmsremote/plugins.cfg
  • +0 -0
    xmmsremote/previous.png
  • +0 -0
    xmmsremote/stop.png
  • +0 -0
    xmmsremote/xmms.png
  • +1164 -0
    xmmsremote/xmmsremote.c
  • +28 -0
    xmppprio/Makefile.am
  • +8 -0
    xmppprio/plugins.cfg
  • +235 -0
    xmppprio/xmppprio.c
  • --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/.hgignore Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,43 @@
    +syntax: glob
    +Makefile
    +Makefile.in
    +*.gmo
    +*.la
    +*.lo
    +*.loT
    +*.o
    +*.Plo
    +*.so
    +
    +syntax: regexp
    +.+/\.deps
    +.+/\.libs
    +autom4te\.cache
    +.*/?.+\.loT
    +(pre|pp)_config.h(\.in)?$
    +.+\.spec
    +.*\.dll
    +aclocal.m4
    +autogen.args
    +buddytime/gtktimezonetest
    +buddytime/recursetest
    +^compile$
    +config.(cache|guess|log|status|sub)
    +configure
    +depcomp
    +install-sh
    +intltool-.*
    +libtool
    +local.mak
    +ltmain.sh
    +missing
    +^plugin_pack\.(list|m4|stats)$
    +po/Makefile.in.in
    +po/missing
    +po/notexist
    +po/POTFILES$
    +po/stamp-it
    +po/.+\.pot
    +purple-plugin_pack-[0-9]+\.[0-9]+([Bb][Ee][Tt][Aa][0-9]+)?(mtn)?\/?
    +stamp-h1
    +win32-dist
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/.hgtags Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,17 @@
    +0bd7be51775ed7f2b9c798b09ff7fb92bd6320aa v0.2
    +0ef79cf33012cd48f36b307d6961085a0e44c20e pp_2_5_1
    +11f498dba239456b388c0ed0b614cb0362eda0fe v0.5
    +190c64ca83baac9f4b1e28f3077ba379565b7268 v_2_2_0_win32fix
    +227ac3a1209c63a3ae838983a56f4aac7ca4937b pp_2_4_0
    +3a6043f84caa7c25ead0157b9be3e2794e3fe282 v0.4pre2
    +795c59e0159b2ced02f6dd883d047a2986d7e908 v0.4pre1
    +7a66dcaf0f7fabd3dfcaa305cba38e6ba1e4501a pp_2_0_0
    +7ea00c0f467309c4bcfe64a54cd557d0543e025d pp_2_5_0
    +82307058f797a2210c25186d1521fd8e30fc4920 pp_2_1_1
    +88b6f9dff70bb2afe240c1854091c81327b9dead pp_2_1_0
    +94894265de4b3b3df2129c91f28d59206b77804e pp_1_0_beta7
    +a92697959778a501dc6dbd60d5a7b73478110a14 pp_2_2_0
    +ac3ed87f88ebe6cf03c0c8be0c539403bc876917 v0.1
    +cf05a49c5091fe9c9a4ee9923ac3e3791d18913a v0.4pre1-orig
    +daf127d4f26054304be01b0082b19c01e2c1bf5a pp_1_0
    +fa640f2d83a59e5b011c7b51cb09f3f89a33a286 v0.3
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/.todo Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,18 @@
    +<?xml version="1.0"?>
    +<todo version="0.1.20">
    + <note priority="veryhigh" time="1189374060" done="1189374109">
    + get cia working on a different project name...
    + <comment>
    + this is working but we have some munged stuffs...
    + </comment>
    + </note>
    + <note priority="high" time="1189373878" done="1224391092">
    + Investigate using the pre-processor to template stuff
    + <comment>
    + i'm pretty sure this was killed with the new buildsystem...
    + </comment>
    + </note>
    + <note priority="low" time="1189373907">
    + make it possible to compile a plugin outside of the plugin-pack
    + </note>
    +</todo>
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/AUTHORS Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,66 @@
    +Authors
    +=======
    +Gary Kramlich <grim@reaperworld.com>
    +Stu Tomlinson <stu@nosnilmot.com>
    +Peter Lawler <bleeter@users.sf.net>
    +John Bailey <rekkanoryo@rekkanoryo.org>
    +Kevin Stange <kstange@users.sourceforge.net>
    +Lennert Van Alboom <alverino@users.sourceforge.net>
    +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
    +Paul Aurich
    +
    +Translators
    +===========
    +es_ES - Máximo Castañeda <antiswen@yahoo.es>
    +fr - Yannick LE NY <y.le.ny@ifrance.com>
    +vi - Nguyen Huu Phuoc
    +
    +Packagers
    +=========
    +Daniel Attallah <datallah@users.sourceforge.net>
    + - Windows
    +Benjamin Seidenberg <benjamin@debian.org>
    + - Debian
    +Stu Tomlinson <stu@nosnilmot.com>
    + - Source RPM
    + - Fedora Core 4
    + - Fedora Core 5
    + - Fedora Core 6
    + - Red Hat Enterprise Linux 4/CentOS 4
    +
    +Accepted Patches
    +================
    +Chris Banal
    +Daniel Beardsmore
    +Joshua Cowan
    +Basil Gor
    +Mathias Hasselmann
    +Björn Nilsson
    +Amish Mehta
    +Lucas Paul
    +Chris Petersen
    +Jérôme Poulin (TiCPU)
    +qwert
    +Lee Roach
    +Elliott Sales de Andrade
    +Ryan Seeber
    +Ankit Singla
    +Anthony Sofocleous
    +William Thompson
    +Ignacio Vazquez-Abrams
    +Chris Weyl
    +
    +Special Thanks
    +==============
    +Eoin Coffey - For being the channel vagrant and the person who *almost*
    + completes shit before being distracted by shiny objects
    +Kathryn Kulick - For providing a female presence in our IRC channel
    +Robert O'Connor - Kicktoy... 'nuff said :)
    +Dennis Ristuccia - For random gibberish that forces us to take a break from coding
    +Ankit Singla - Ticket triaging! (and TLC for the gRIM plugin)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/COPYING Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,341 @@
    + GNU GENERAL PUBLIC LICENSE
    + Version 2, June 1991
    +
    + Copyright (C) 1989, 1991 Free Software Foundation, Inc.
    + 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
    + Everyone is permitted to copy and distribute verbatim copies
    + of this license document, but changing it is not allowed.
    +
    + Preamble
    +
    + The licenses for most software are designed to take away your
    +freedom to share and change it. By contrast, the GNU General Public
    +License is intended to guarantee your freedom to share and change free
    +software--to make sure the software is free for all its users. This
    +General Public License applies to most of the Free Software
    +Foundation's software and to any other program whose authors commit to
    +using it. (Some other Free Software Foundation software is covered by
    +the GNU Library General Public License instead.) You can apply it to
    +your programs, too.
    +
    + When we speak of free software, we are referring to freedom, not
    +price. Our General Public Licenses are designed to make sure that you
    +have the freedom to distribute copies of free software (and charge for
    +this service if you wish), that you receive source code or can get it
    +if you want it, that you can change the software or use pieces of it
    +in new free programs; and that you know you can do these things.
    +
    + To protect your rights, we need to make restrictions that forbid
    +anyone to deny you these rights or to ask you to surrender the rights.
    +These restrictions translate to certain responsibilities for you if you
    +distribute copies of the software, or if you modify it.
    +
    + For example, if you distribute copies of such a program, whether
    +gratis or for a fee, you must give the recipients all the rights that
    +you have. You must make sure that they, too, receive or can get the
    +source code. And you must show them these terms so they know their
    +rights.
    +
    + We protect your rights with two steps: (1) copyright the software, and
    +(2) offer you this license which gives you legal permission to copy,
    +distribute and/or modify the software.
    +
    + Also, for each author's protection and ours, we want to make certain
    +that everyone understands that there is no warranty for this free
    +software. If the software is modified by someone else and passed on, we
    +want its recipients to know that what they have is not the original, so
    +that any problems introduced by others will not reflect on the original
    +authors' reputations.
    +
    + Finally, any free program is threatened constantly by software
    +patents. We wish to avoid the danger that redistributors of a free
    +program will individually obtain patent licenses, in effect making the
    +program proprietary. To prevent this, we have made it clear that any
    +patent must be licensed for everyone's free use or not licensed at all.
    +
    + The precise terms and conditions for copying, distribution and
    +modification follow.
    +
    + GNU GENERAL PUBLIC LICENSE
    + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
    +
    + 0. This License applies to any program or other work which contains
    +a notice placed by the copyright holder saying it may be distributed
    +under the terms of this General Public License. The "Program", below,
    +refers to any such program or work, and a "work based on the Program"
    +means either the Program or any derivative work under copyright law:
    +that is to say, a work containing the Program or a portion of it,
    +either verbatim or with modifications and/or translated into another
    +language. (Hereinafter, translation is included without limitation in
    +the term "modification".) Each licensee is addressed as "you".
    +
    +Activities other than copying, distribution and modification are not
    +covered by this License; they are outside its scope. The act of
    +running the Program is not restricted, and the output from the Program
    +is covered only if its contents constitute a work based on the
    +Program (independent of having been made by running the Program).
    +Whether that is true depends on what the Program does.
    +
    + 1. You may copy and distribute verbatim copies of the Program's
    +source code as you receive it, in any medium, provided that you
    +conspicuously and appropriately publish on each copy an appropriate
    +copyright notice and disclaimer of warranty; keep intact all the
    +notices that refer to this License and to the absence of any warranty;
    +and give any other recipients of the Program a copy of this License
    +along with the Program.
    +
    +You may charge a fee for the physical act of transferring a copy, and
    +you may at your option offer warranty protection in exchange for a fee.
    +
    + 2. You may modify your copy or copies of the Program or any portion
    +of it, thus forming a work based on the Program, and copy and
    +distribute such modifications or work under the terms of Section 1
    +above, provided that you also meet all of these conditions:
    +
    + a) You must cause the modified files to carry prominent notices
    + stating that you changed the files and the date of any change.
    +
    + b) You must cause any work that you distribute or publish, that in
    + whole or in part contains or is derived from the Program or any
    + part thereof, to be licensed as a whole at no charge to all third
    + parties under the terms of this License.
    +
    + c) If the modified program normally reads commands interactively
    + when run, you must cause it, when started running for such
    + interactive use in the most ordinary way, to print or display an
    + announcement including an appropriate copyright notice and a
    + notice that there is no warranty (or else, saying that you provide
    + a warranty) and that users may redistribute the program under
    + these conditions, and telling the user how to view a copy of this
    + License. (Exception: if the Program itself is interactive but
    + does not normally print such an announcement, your work based on
    + the Program is not required to print an announcement.)
    +
    +These requirements apply to the modified work as a whole. If
    +identifiable sections of that work are not derived from the Program,
    +and can be reasonably considered independent and separate works in
    +themselves, then this License, and its terms, do not apply to those
    +sections when you distribute them as separate works. But when you
    +distribute the same sections as part of a whole which is a work based
    +on the Program, the distribution of the whole must be on the terms of
    +this License, whose permissions for other licensees extend to the
    +entire whole, and thus to each and every part regardless of who wrote it.
    +
    +Thus, it is not the intent of this section to claim rights or contest
    +your rights to work written entirely by you; rather, the intent is to
    +exercise the right to control the distribution of derivative or
    +collective works based on the Program.
    +
    +In addition, mere aggregation of another work not based on the Program
    +with the Program (or with a work based on the Program) on a volume of
    +a storage or distribution medium does not bring the other work under
    +the scope of this License.
    +
    + 3. You may copy and distribute the Program (or a work based on it,
    +under Section 2) in object code or executable form under the terms of
    +Sections 1 and 2 above provided that you also do one of the following:
    +
    + a) Accompany it with the complete corresponding machine-readable
    + source code, which must be distributed under the terms of Sections
    + 1 and 2 above on a medium customarily used for software interchange; or,
    +
    + b) Accompany it with a written offer, valid for at least three
    + years, to give any third party, for a charge no more than your
    + cost of physically performing source distribution, a complete
    + machine-readable copy of the corresponding source code, to be
    + distributed under the terms of Sections 1 and 2 above on a medium
    + customarily used for software interchange; or,
    +
    + c) Accompany it with the information you received as to the offer
    + to distribute corresponding source code. (This alternative is
    + allowed only for noncommercial distribution and only if you
    + received the program in object code or executable form with such
    + an offer, in accord with Subsection b above.)
    +
    +The source code for a work means the preferred form of the work for
    +making modifications to it. For an executable work, complete source
    +code means all the source code for all modules it contains, plus any
    +associated interface definition files, plus the scripts used to
    +control compilation and installation of the executable. However, as a
    +special exception, the source code distributed need not include
    +anything that is normally distributed (in either source or binary
    +form) with the major components (compiler, kernel, and so on) of the
    +operating system on which the executable runs, unless that component
    +itself accompanies the executable.
    +
    +If distribution of executable or object code is made by offering
    +access to copy from a designated place, then offering equivalent
    +access to copy the source code from the same place counts as
    +distribution of the source code, even though third parties are not
    +compelled to copy the source along with the object code.
    +
    + 4. You may not copy, modify, sublicense, or distribute the Program
    +except as expressly provided under this License. Any attempt
    +otherwise to copy, modify, sublicense or distribute the Program is
    +void, and will automatically terminate your rights under this License.
    +However, parties who have received copies, or rights, from you under
    +this License will not have their licenses terminated so long as such
    +parties remain in full compliance.
    +
    + 5. You are not required to accept this License, since you have not
    +signed it. However, nothing else grants you permission to modify or
    +distribute the Program or its derivative works. These actions are
    +prohibited by law if you do not accept this License. Therefore, by
    +modifying or distributing the Program (or any work based on the
    +Program), you indicate your acceptance of this License to do so, and
    +all its terms and conditions for copying, distributing or modifying
    +the Program or works based on it.
    +
    + 6. Each time you redistribute the Program (or any work based on the
    +Program), the recipient automatically receives a license from the
    +original licensor to copy, distribute or modify the Program subject to
    +these terms and conditions. You may not impose any further
    +restrictions on the recipients' exercise of the rights granted herein.
    +You are not responsible for enforcing compliance by third parties to
    +this License.
    +
    + 7. If, as a consequence of a court judgment or allegation of patent
    +infringement or for any other reason (not limited to patent issues),
    +conditions are imposed on you (whether by court order, agreement or
    +otherwise) that contradict the conditions of this License, they do not
    +excuse you from the conditions of this License. If you cannot
    +distribute so as to satisfy simultaneously your obligations under this
    +License and any other pertinent obligations, then as a consequence you
    +may not distribute the Program at all. For example, if a patent
    +license would not permit royalty-free redistribution of the Program by
    +all those who receive copies directly or indirectly through you, then
    +the only way you could satisfy both it and this License would be to
    +refrain entirely from distribution of the Program.
    +
    +If any portion of this section is held invalid or unenforceable under
    +any particular circumstance, the balance of the section is intended to
    +apply and the section as a whole is intended to apply in other
    +circumstances.
    +
    +It is not the purpose of this section to induce you to infringe any
    +patents or other property right claims or to contest validity of any
    +such claims; this section has the sole purpose of protecting the
    +integrity of the free software distribution system, which is
    +implemented by public license practices. Many people have made
    +generous contributions to the wide range of software distributed
    +through that system in reliance on consistent application of that
    +system; it is up to the author/donor to decide if he or she is willing
    +to distribute software through any other system and a licensee cannot
    +impose that choice.
    +
    +This section is intended to make thoroughly clear what is believed to
    +be a consequence of the rest of this License.
    +
    + 8. If the distribution and/or use of the Program is restricted in
    +certain countries either by patents or by copyrighted interfaces, the
    +original copyright holder who places the Program under this License
    +may add an explicit geographical distribution limitation excluding
    +those countries, so that distribution is permitted only in or among
    +countries not thus excluded. In such case, this License incorporates
    +the limitation as if written in the body of this License.
    +
    + 9. The Free Software Foundation may publish revised and/or new versions
    +of the General Public License from time to time. Such new versions will
    +be similar in spirit to the present version, but may differ in detail to
    +address new problems or concerns.
    +
    +Each version is given a distinguishing version number. If the Program
    +specifies a version number of this License which applies to it and "any
    +later version", you have the option of following the terms and conditions
    +either of that version or of any later version published by the Free
    +Software Foundation. If the Program does not specify a version number of
    +this License, you may choose any version ever published by the Free Software
    +Foundation.
    +
    + 10. If you wish to incorporate parts of the Program into other free
    +programs whose distribution conditions are different, write to the author
    +to ask for permission. For software which is copyrighted by the Free
    +Software Foundation, write to the Free Software Foundation; we sometimes
    +make exceptions for this. Our decision will be guided by the two goals
    +of preserving the free status of all derivatives of our free software and
    +of promoting the sharing and reuse of software generally.
    +
    + NO WARRANTY
    +
    + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
    +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
    +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
    +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
    +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
    +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
    +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
    +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
    +REPAIR OR CORRECTION.
    +
    + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
    +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
    +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
    +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
    +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
    +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
    +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
    +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
    +POSSIBILITY OF SUCH DAMAGES.
    +
    + END OF TERMS AND CONDITIONS
    +
    + How to Apply These Terms to Your New Programs
    +
    + If you develop a new program, and you want it to be of the greatest
    +possible use to the public, the best way to achieve this is to make it
    +free software which everyone can redistribute and change under these terms.
    +
    + To do so, attach the following notices to the program. It is safest
    +to attach them to the start of each source file to most effectively
    +convey the exclusion of warranty; and each file should have at least
    +the "copyright" line and a pointer to where the full notice is found.
    +
    + <one line to give the program's name and a brief idea of what it does.>
    + Copyright (C) <year> <name of author>
    +
    + 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
    +
    +
    +Also add information on how to contact you by electronic and paper mail.
    +
    +If the program is interactive, make it output a short notice like this
    +when it starts in an interactive mode:
    +
    + Gnomovision version 69, Copyright (C) year name of author
    + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    + This is free software, and you are welcome to redistribute it
    + under certain conditions; type `show c' for details.
    +
    +The hypothetical commands `show w' and `show c' should show the appropriate
    +parts of the General Public License. Of course, the commands you use may
    +be called something other than `show w' and `show c'; they could even be
    +mouse-clicks or menu items--whatever suits your program.
    +
    +You should also get your employer (if you work as a programmer) or your
    +school, if any, to sign a "copyright disclaimer" for the program, if
    +necessary. Here is a sample; alter the names:
    +
    + Yoyodyne, Inc., hereby disclaims all copyright interest in the program
    + `Gnomovision' (which makes passes at compilers) written by James Hacker.
    +
    + <signature of Ty Coon>, 1 April 1989
    + Ty Coon, President of Vice
    +
    +This General Public License does not permit incorporating your program into
    +proprietary programs. If your program is a subroutine library, you may
    +consider it more useful to permit linking proprietary applications with the
    +library. If this is what you want to do, use the GNU Library General
    +Public License instead of this License.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ChangeLog Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,246 @@
    +Version 2.6.0: ??/??/??
    + * Fix grouping of radio buttons in switchspell menu for enchant-enabled
    + GtkSpell. This has the side effect of eliminating slow tab switching
    + and multiple language switches during menu drawing. (Basil Gor)
    + * Fixed a NULL deref crash in the slashexec plugin when processing a
    + chat room message.
    + * Added XMPP Priority plugin. This allows users to configure the
    + priority attribute for XMPP statuses on a per-account basis.
    +
    +Version 2.5.1: 12/25/08
    + * Really fixed switch spell now...
    +
    +Version 2.5.0: 12/20/08
    + * Fixed some crashes in autprofile related to old preference paths
    + (amishmm)
    + * ListHandler now handles "FriendlyName" lines in .blt files generated by
    + newer Windows AIM versions when importing.
    + * Added Vietnamese translation - thanks Nguyen Huu Phuoc
    + * findip and groupmsg are now default plugins.
    + * Added nomobility plugin (does not yet work, doesn't build by default)
    + * Fixed --with-plugins (yet again...)
    + * Support dependency scenarios where dependency A OR dependency B is the
    + correct solution. This fixes dependencies for switchspell, which can
    + use GTKSpell with either the Enchant backend or the aspell backend.
    + Let us know if you still have trouble building switchspell but have
    + the correct development packages installed.
    +
    +Version 2.4.0: 08/03/08
    + * Rewrote the build system to make our lifes easier, as well as support
    + the requirements of some plugins. This has added a dependency on
    + python.
    + * Removed 'menuconfig' since the new build system should be extending
    + to support it's behavior instead.
    + * Merged the Autoprofile plugin into our build system.
    + * Fixed convbadger's failure to update on conversation switch.
    + * Added Ike Gingerich's colorize plugin
    + * Added Ike Gingerich's splitter plugin
    + * Fixed dewysiwygification's debug messages not properly ending lines.
    + * Added Google plugin for "I'm Feeling Lucky" searches.
    + * Fixed aspell dependency in switchspell (fixes gentoo bug #196693)
    + * Made switchspell work with gtkspell that uses enchant; this behavior is
    + now the default. To use gtkspell using aspell, use the
    + --disable-enchant argument to ./configure.
    + * Added preferences to the irssi plugin that allow changing its behavior
    + * Fixed the preference strings in slashexec so mnemonics are no longer
    + incorrectly interpreted from the strings.
    + * Added Message Length plugin (short name: msglen).
    + * Added Chat User List Logging plugin (short name: listlog).
    + * The Enhanced History plugin is now contact-aware.
    + * Slashexec now strips ASCII control characters. Fixes, among others,
    + bug #452, where commands can cause XMPP disconnection. May cause some
    + characters to be sent that are part of escape sequences.
    +
    +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.
    + * Fixed a crash in Slashexec that has only shown itself when using the
    + Offline Message Emulation plugin to emulate an offline message at the
    + same time as Slashexec is loaded.
    + * Fixed the version number for highlight and ignore so they show the
    + correct Plugin Pack version instead of the version of Pidgin they were
    + built against.
    + * Fixed building with ancient glib. (Bodo Bellut)
    + * Removed the .build file from hideconv to remove it from default
    + 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
    +
    +Version 2.1.0: 8/18/07
    + * Completed the convbadger plugin. It adds a protocol icon to the menu
    + bar of the conversation window.
    + * Added the Pidgin-SNPP protocol plugin as snpp
    + * Added the /notice command for the irc-more plugin.
    + * Added an additional string to the eight_ball plugin (resiak)
    + * Autorejoin now has a preference to set the delay time before
    + rejoining. Fixes #372
    +
    +Version 2.0.0: 7/13/07
    + * Blistops now offers the ability to stretch the display of screen names
    + or aliases into the buddy icon column when no icon exists
    + * Blistops now shows the menu bar when hovering near the top of the buddy
    + list window. Resolves a number of Pidgin bug reports
    + * Listhandler now supports alias-only list exports and imports (rseeber)
    + * Listhandler now exports and imports buddy notes created with the
    + buddynote plugin
    + * Added ignore plugin
    + * Added irc-more plugin
    + * Added highlight plugin
    + * Added incomplete convbadger plugin
    + * Added internationalization support to plonkers
    + * Added more humorous stuff to eight_ball (resiak)
    + * Added support for spaces in filenames to gRIM (rageboy04)
    + * Added a stop subcommand in gRIM (rageboy04)
    + * Changed order of arguments to gRIM command (rageboy04)
    + * Fixed build-by-default status for switchspell
    + * Fixed crash in album when using Pidgin 2.0.2 or newer
    + * Fixed potential crashes in some other plugins when using Pidgin 2.0.2 or
    + newer
    + * Fixed potential random crashes in irssi plugin
    + * Fixed make distcheck in the development code
    +
    +Version 1.0: 5/4/07
    + * Updated for Pidgin 2.0.0
    + * Fixed build-by-default and win32 building for several plugins
    + * Added hideconv plugin
    + * Took autoreply back from Pidgin
    +
    +Version 1.0beta7: 4/30/07
    + * Added the Ignorance plugin to the main development line
    + * Added broadcast plugin
    + * Imported the IRC Helper plugin
    + * Imported the "Gaim Album" plugin, as "Album"
    + * Updated all namespaces for purple, pidgin, and finch API
    + * Completed Broadcaster plugin (abusive)
    + * Changed .plugin files to .purple-plugin, .pidgin-plugin, and
    + .finch-plugin
    + * Added .incomplete to specify plugins that shouldn't ever build
    + automagically.
    +
    +Version 1.0beta6: 1/19/07
    + * Extensive changes to the irssi plugin, especially in its text formatting.
    + * Added a menuconfig script that allows a text-mode menu selection of
    + which plugins to build. Inspired by the Linux kernel's 'make
    + menuconfig'.
    + * Fixed a bug in blistops that caused the saved preferences not to take
    + effect after restarting Gaim.
    + * Slashexec no longer prints annoying system messages to the conversation
    + * Moved autoaccept to Gaim
    + * Moved autoreply to Gaim
    + * Moved buddynote to Gaim
    + * Moved convcolors to Gaim
    + * Moved markerline to Gaim
    + * Moved newline to Gaim
    + * Moved offlinemsg to Gaim
    +
    +Version 1.0beta5: 11/11/06
    + * Removed chronic.wav due to potential copyright issues
    + * Fixed some missing library links in Makefiles
    + * Fixed irssi plugin's text formatting so that it isn't so aggressive
    +
    +Version 1.0beta4: 10/18/06
    + * Updated to work with Gaim 2.0.0beta4
    + * Moved from gettext to intltool
    + * Added difftopic plugin
    + * Added /layout and text formatting capabilities to irssi plugin
    + * Added bangexec features to slashexec plugin. Commands can be executed
    + by typing "!command" in the entry area for a conversation. Check
    + http://gaim.guifications.org/wiki/slashexec for details.
    +
    +Version 1.0beta3.1: 8/22/06
    + * Updated to work with Gaim 2.0.0beta3.1
    + * Renamed gaim-xmms-remote plugin to xmmsremote for consistency within the
    + plugin's directory
    + * Added .abusive marking to conditional building. Plugins with .abusive
    + files in their directories never build automatically.
    + * Renamed irssidate plugin to irssi and added /win and /window commands.
    +
    +Version 1.0beta3: 3/29/06
    + * Added slashexec plugin - formerly a separate package
    + * Added gaim-xmms-remote plugin - formerly a separate package
    + * Fixed bugs in the m4 code that controls the conditional building
    + * Updated to work with Gaim 2.0.0beta3
    +
    +Version 1.0beta2: 1/25/06
    + * Added plonkers plugin
    + * Updated to work with Gaim 2.0.0beta2
    +
    +Version 1.0beta1: 12/17/05
    + * Added adium_xml_logger plugin
    + * Added autoaccept plugin
    + * Added autoreply plugin
    + * Added bash plugin
    + * Added bit plugin - DANGEROUS!!!
    + * Added blistops plugin
    + * Added buddynote plugin
    + * Added dice plugin
    + * Added eight_ball plugin
    + * Added flip plugin
    + * Added gRIM plugin
    + * Added gaim-schedule
    + * Added gaim-xchat plugin
    + * Added groupmsg plugin
    + * Added irssidate plugin
    + * Added lastseen plugin
    + * Added listhandler plugin
    + * Added markerline plugin
    + * Added mystatusbox plugin
    + * Added newline plugin
    + * Added nicksaid plugin
    + * Added offlinemsg plugin
    + * Added oldlogger plugin
    + * Added sepandtab plugin
    + * Added showoffline plugin
    + * Added simfix plugin
    + * Added sslinfo plugin
    + * Added stocker plugin - not yet functional
    + * Added template plugins
    + * Added talkfilters plugin
    + * Added conditional building - all plugins require .plugin files in their
    + directories. To build by default, plugins require .build files in their
    + directories.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/INSTALL Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,229 @@
    +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software
    +Foundation, Inc.
    +
    + This file is free documentation; the Free Software Foundation gives
    +unlimited permission to copy, distribute and modify it.
    +
    +Basic Installation
    +==================
    +
    + These are generic installation instructions.
    +
    + The `configure' shell script attempts to guess correct values for
    +various system-dependent variables used during compilation. It uses
    +those values to create a `Makefile' in each directory of the package.
    +It may also create one or more `.h' files containing system-dependent
    +definitions. Finally, it creates a shell script `config.status' that
    +you can run in the future to recreate the current configuration, and a
    +file `config.log' containing compiler output (useful mainly for
    +debugging `configure').
    +
    + It can also use an optional file (typically called `config.cache'
    +and enabled with `--cache-file=config.cache' or simply `-C') that saves
    +the results of its tests to speed up reconfiguring. (Caching is
    +disabled by default to prevent problems with accidental use of stale
    +cache files.)
    +
    + If you need to do unusual things to compile the package, please try
    +to figure out how `configure' could check whether to do them, and mail
    +diffs or instructions to the address given in the `README' so they can
    +be considered for the next release. If you are using the cache, and at
    +some point `config.cache' contains results you don't want to keep, you
    +may remove or edit it.
    +
    + The file `configure.ac' (or `configure.in') is used to create
    +`configure' by a program called `autoconf'. You only need
    +`configure.ac' if you want to change it or regenerate `configure' using
    +a newer version of `autoconf'.
    +
    +The simplest way to compile this package is:
    +
    + 1. `cd' to the directory containing the package's source code and type
    + `./configure' to configure the package for your system. If you're
    + using `csh' on an old version of System V, you might need to type
    + `sh ./configure' instead to prevent `csh' from trying to execute
    + `configure' itself.
    +
    + Running `configure' takes awhile. While running, it prints some
    + messages telling which features it is checking for.
    +
    + 2. Type `make' to compile the package.
    +
    + 3. Optionally, type `make check' to run any self-tests that come with
    + the package.
    +
    + 4. Type `make install' to install the programs and any data files and
    + documentation.
    +
    + 5. You can remove the program binaries and object files from the
    + source code directory by typing `make clean'. To also remove the
    + files that `configure' created (so you can compile the package for
    + a different kind of computer), type `make distclean'. There is
    + also a `make maintainer-clean' target, but that is intended mainly
    + for the package's developers. If you use it, you may have to get
    + all sorts of other programs in order to regenerate files that came
    + with the distribution.
    +
    +Compilers and Options
    +=====================
    +
    + Some systems require unusual options for compilation or linking that
    +the `configure' script does not know about. Run `./configure --help'
    +for details on some of the pertinent environment variables.
    +
    + You can give `configure' initial values for configuration parameters
    +by setting variables in the command line or in the environment. Here
    +is an example:
    +
    + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix
    +
    + *Note Defining Variables::, for more details.
    +
    +Compiling For Multiple Architectures
    +====================================
    +
    + You can compile the package for more than one kind of computer at the
    +same time, by placing the object files for each architecture in their
    +own directory. To do this, you must use a version of `make' that
    +supports the `VPATH' variable, such as GNU `make'. `cd' to the
    +directory where you want the object files and executables to go and run
    +the `configure' script. `configure' automatically checks for the
    +source code in the directory that `configure' is in and in `..'.
    +
    + If you have to use a `make' that does not support the `VPATH'
    +variable, you have to compile the package for one architecture at a
    +time in the source code directory. After you have installed the
    +package for one architecture, use `make distclean' before reconfiguring
    +for another architecture.
    +
    +Installation Names
    +==================
    +
    + By default, `make install' will install the package's files in
    +`/usr/local/bin', `/usr/local/man', etc. You can specify an
    +installation prefix other than `/usr/local' by giving `configure' the
    +option `--prefix=PATH'.
    +
    + You can specify separate installation prefixes for
    +architecture-specific files and architecture-independent files. If you
    +give `configure' the option `--exec-prefix=PATH', the package will use
    +PATH as the prefix for installing programs and libraries.
    +Documentation and other data files will still use the regular prefix.
    +
    + In addition, if you use an unusual directory layout you can give
    +options like `--bindir=PATH' to specify different values for particular
    +kinds of files. Run `configure --help' for a list of the directories
    +you can set and what kinds of files go in them.
    +
    + If the package supports it, you can cause programs to be installed
    +with an extra prefix or suffix on their names by giving `configure' the
    +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
    +
    +Optional Features
    +=================
    +
    + Some packages pay attention to `--enable-FEATURE' options to
    +`configure', where FEATURE indicates an optional part of the package.
    +They may also pay attention to `--with-PACKAGE' options, where PACKAGE
    +is something like `gnu-as' or `x' (for the X Window System). The
    +`README' should mention any `--enable-' and `--with-' options that the
    +package recognizes.
    +
    + For packages that use the X Window System, `configure' can usually
    +find the X include and library files automatically, but if it doesn't,
    +you can use the `configure' options `--x-includes=DIR' and
    +`--x-libraries=DIR' to specify their locations.
    +
    +Specifying the System Type
    +==========================
    +
    + There may be some features `configure' cannot figure out
    +automatically, but needs to determine by the type of machine the package
    +will run on. Usually, assuming the package is built to be run on the
    +_same_ architectures, `configure' can figure that out, but if it prints
    +a message saying it cannot guess the machine type, give it the
    +`--build=TYPE' option. TYPE can either be a short name for the system
    +type, such as `sun4', or a canonical name which has the form:
    +
    + CPU-COMPANY-SYSTEM
    +
    +where SYSTEM can have one of these forms:
    +
    + OS KERNEL-OS
    +
    + See the file `config.sub' for the possible values of each field. If
    +`config.sub' isn't included in this package, then this package doesn't
    +need to know the machine type.
    +
    + If you are _building_ compiler tools for cross-compiling, you should
    +use the `--target=TYPE' option to select the type of system they will
    +produce code for.
    +
    + If you want to _use_ a cross compiler, that generates code for a
    +platform different from the build platform, you should specify the
    +"host" platform (i.e., that on which the generated programs will
    +eventually be run) with `--host=TYPE'.
    +
    +Sharing Defaults
    +================
    +
    + If you want to set default values for `configure' scripts to share,
    +you can create a site shell script called `config.site' that gives
    +default values for variables like `CC', `cache_file', and `prefix'.
    +`configure' looks for `PREFIX/share/config.site' if it exists, then
    +`PREFIX/etc/config.site' if it exists. Or, you can set the
    +`CONFIG_SITE' environment variable to the location of the site script.
    +A warning: not all `configure' scripts look for a site script.
    +
    +Defining Variables
    +==================
    +
    + Variables not defined in a site shell script can be set in the
    +environment passed to `configure'. However, some packages may run
    +configure again during the build, and the customized values of these
    +variables may be lost. In order to avoid this problem, you should set
    +them in the `configure' command line, using `VAR=value'. For example:
    +
    + ./configure CC=/usr/local2/bin/gcc
    +
    +will cause the specified gcc to be used as the C compiler (unless it is
    +overridden in the site shell script).
    +
    +`configure' Invocation
    +======================
    +
    + `configure' recognizes the following options to control how it
    +operates.
    +
    +`--help'
    +`-h'
    + Print a summary of the options to `configure', and exit.
    +
    +`--version'
    +`-V'
    + Print the version of Autoconf used to generate the `configure'
    + script, and exit.
    +
    +`--cache-file=FILE'
    + Enable the cache: use and save the results of the tests in FILE,
    + traditionally `config.cache'. FILE defaults to `/dev/null' to
    + disable caching.
    +
    +`--config-cache'
    +`-C'
    + Alias for `--cache-file=config.cache'.
    +
    +`--quiet'
    +`--silent'
    +`-q'
    + Do not print messages saying which checks are being made. To
    + suppress all normal output, redirect it to `/dev/null' (any error
    + messages will still be shown).
    +
    +`--srcdir=DIR'
    + Look for the package's source code in directory DIR. Usually
    + `configure' can determine that directory automatically.
    +
    +`configure' also accepts some other, not widely useful, options. Run
    +`configure --help' for more details.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/INSTALL.WIN32 Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,41 @@
    +Install
    +=======
    +
    +To install the Purple Plugin Pack, simply extract the contents of this zip file
    +to your purple install directory, which is C:\Program Files\Purple by
    +default.
    +
    +To Compile
    +==========
    +
    +You need a complete Purple build environment set up, and Purple already compiled.
    +
    +Then extract the Purple Plugin Pack source into:
    +
    + purple-source-tree/plugins/
    +
    +purple-source being what ever path you extracted the gaim source tree to.
    +
    +For example, if your purple source tree is in /home/user/gaim-VERSION
    +you want to "cd" over to /home/user/purple-VERSION/plugins/ and type:
    +
    + tar zxvf purple-plugin-pack-VERSION.tar.gz
    +
    +Then cd into the source dir
    +
    + cd purple-plugin-pack
    +
    +Followed by
    +
    + make -f Makefile.mingw
    + make -f Makefile.mingw install
    +
    +When it's done building copy
    +
    + win32-install-dir/plugins/*.dll
    +
    +to your purple install dir, (Keeping the already defined tree structure of
    +course)
    +
    +And you should be good to go. Restart purple and load 'em up.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,59 @@
    +EXTRA_DIST = \
    + AUTHORS \
    + COPYING \
    + ChangeLog \
    + INSTALL \
    + INSTALL.WIN32 \
    + intltool-extract.in \
    + intltool-merge.in \
    + intltool-update.in \
    + Makefile.am \
    + Makefile.mingw \
    + NEWS \
    + README \
    + VERSION \
    + VERSION.in \
    + plugin_pack.list \
    + plugin_pack.m4 \
    + plugin_pack.py \
    + plugin_pack.spec \
    + plugin_pack.stats \
    + pp_config.h.mingw \
    + win_pp.mak \
    + po/Makefile.in.in \
    + po/Makefile.mingw
    +
    +if INSTALL_I18N
    +PO_DIR=po
    +endif
    +
    +DIST_SUBDIRS = common doc po $(PP_DIST_DIRS)
    +
    +SUBDIRS = common doc $(PO_DIR) $(PP_BUILD_DIRS)
    +
    +DISTCLEANFILES=\
    + pp_config.h \
    + intltool-extract \
    + intltool-update \
    + intltool-merge \
    + plugin_pack.list \
    + plugin_pack.stats
    +
    +BUILT_SOURCES = pp_config.h plugin_pack.list plugin_pack.stats
    +
    +$(OBJECTS): $(BUILT_SOURCES)
    +
    +pp_config.h: pre_config.h
    + cp -f po/Makefile po/.tmp-Makefile
    + sed -e "s#localedir = .*#localedir = $(PURPLE_DATADIR)/locale#g" < po/.tmp-Makefile > po/Makefile
    + rm -f po/.tmp-Makefile
    + $(sedpath) 's/\#define PACKAGE/\#define PP_PACKAGE/g' pre_config.h > $@
    +
    +signatures: dist
    + echo $(DIST_ARCHIVES) | xargs -n 1 gpg -a -b
    +
    +plugin_pack.stats: plugin_pack.py $(SUBDIRS)
    + @$(PYTHON) $(top_srcdir)/plugin_pack.py stats > $@
    +
    +plugin_pack.list: plugin_pack.py $(SUBDIRS)
    + @$(PYTHON) $(top_srcdir)/plugin_pack.py list > $@
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,53 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for win32 (mingw) purple plugin pack
    +#
    +
    +PIDGIN_TREE_TOP ?= ../../..
    +include $(PIDGIN_TREE_TOP)/libpurple/win32/global.mak
    +
    +VERSION := $(shell cat ./VERSION)
    +
    +ALL_PLUGINS_LIST := $(shell python plugin_pack.py build_dirs purple,pidgin,gtkspell,aspell all)
    +
    +comma:= ,
    +empty:=
    +space:= $(empty) $(empty)
    +
    +ALL_PLUGINS := $(subst $(comma),$(space),$(ALL_PLUGINS_LIST))
    +
    +all:
    + list='$(ALL_PLUGINS)'; for subdir in $$list; do \
    + if test -d $$subdir; then if test -e $$subdir/Makefile.mingw; then \
    + $(MAKE) -C $$subdir -f Makefile.mingw || exit 1; \
    + fi; fi; \
    + done;
    +
    +install: all
    + list='$(ALL_PLUGINS)'; for subdir in $$list; do \
    + if test -d $$subdir; then if test -e $$subdir/Makefile.mingw; then \
    + $(MAKE) -C $$subdir -f Makefile.mingw install || exit 1;\
    + fi; fi; \
    + done;
    +
    +installer: all
    + $(MAKENSIS) -DPP_VERSION="$(VERSION)" plugin-pack-installer.nsi
    +
    +clean:
    + list='$(ALL_PLUGINS)'; for subdir in $$list; do \
    + if test -d $$subdir; then if test -e $$subdir/Makefile.mingw; then \
    + $(MAKE) -C $$subdir -f Makefile.mingw clean || exit 1;\
    + fi; fi; \
    + done; \
    + rm -rf pp_config.h win32-dist purple-plugin_pack*.zip
    +
    +install_zip:
    + list='$(ALL_PLUGINS)'; for subdir in $$list; do \
    + if test -d $$subdir; then if test -e $$subdir/Makefile.mingw; then \
    + $(MAKE) -C $$subdir -f Makefile.mingw install_zip || exit 1;\
    + fi; fi; \
    + done; \
    + pushd win32-dist; \
    + zip ../purple-plugin_pack-$(VERSION).zip *.dll; \
    + popd
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/NEWS Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,25 @@
    +Purple Plugin Pack
    +
    +The news entries below are provided simply because it was a first release. Please
    +see the ChangeLog and our website for current news, changes, etc.
    +
    +1.0beta (12/16/05):
    + John: Well, this is our first release of the Plugin Pack. Some of the
    + plugins are useful, some are pointless, and some are for annoying buddies.
    + We've put a lot of work into some of these plugins, and just banged out
    + some of them in a quick hacking session, but we hope everyone enjoys the
    + package, regardless of how much work went into your favorite plugin.
    + On a semi-related note, happy birthday Peter!
    +
    + Gary: Here is the fabled plugin pack. Fabled because it's been in
    + development forever, and we've finally got it to a maintainable state. So
    + we hope you enjoy all of these plugins, since we couldn't justify packaging
    + them separately.
    +
    + Peter: Are we using tabs or spaces at the start of each line here?
    + Oh, Tabs it is then. And it's the 17th here, which is my birthday, not the
    + 16th. Why's r0bby not out on the street? Is this file unicode safe? ↑?
    + So I expect more pressies tomorrow your time. Already have new coffee tool,
    + so send more coffee.
    +
    + Stu: I'm still here. Some of my pathetic plugins are too!
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/README Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,11 @@
    +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 over 50.
    +
    +Also, many more developers have continued to add to it, including John Bailey,
    +Peter Lawler, Sadrul Habib Chowdhury, and most recently Richard Laager. It has
    +also become a place to save plugins who's authors have since abandoned them.
    +
    +More information on the Plugin Pack can be found at
    +http://plugins.guifications.org/trac/wiki/PluginPack
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/VERSION Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,1 @@
    +2.6.0dev
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/VERSION.in Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,1 @@
    +@VERSION@
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/ChangeLog Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,31 @@
    +This ChangeLog documents changes prior to when this plugin was merged into the
    +Plugin Pack. See the ChangeLog file a directory up for post-merge changes.
    +
    +version 1.4
    + * Eliminate strictly identical duplicates from the viewer. This is
    + useful when a contact has multiple accounts and uses Gaim's global
    + buddy icon selector to set the same icon across accounts. However,
    + some duplicates still appear when the file formats are different
    + (for example, if one account has a .png icon and another is .jpg).
    +
    +version 1.3
    + * Update to build properly with 2.0.0beta4 on win32
    +
    +version 1.2
    + * Update the album window from an idle loop, so Gaim doesn't hang
    + when you have lots of icons for a buddy
    + * Update release for Gaim 2.0.0
    + Things will still build on Gaim 1.x.y, but the Windows DLL shipped
    + with this version only works on Gaim 2.0.0. Likewise, the RPM .spec
    + file is designed for Gaim 2. If you need to build an RPM for Gaim 1,
    + use the .spec file from the Gaim Album 1.1 release.
    +
    +version 1.1
    + * Fixed a fatal bug in caching of icons that exist when the plugin
    + first loads. This feature should actually work now. :)
    + * Fixed a fatal bug in the Windows build. The archive will actually
    + include a .dll now. Oops!
    +
    +version 1.0
    + * Initial Release
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,34 @@
    +HEADER_FILES = \
    + album.h \
    + album-ui.h
    +
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg \
    + $(HEADER_FILES)
    +
    +albumdir = $(PIDGIN_LIBDIR)
    +
    +album_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +album_LTLIBRARIES = album.la
    +
    +album_la_SOURCES = \
    + album.c \
    + album-ui.c
    +
    +album_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,14 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for album plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = album
    +
    +PP_SRC := album.c album-ui.c
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/README Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,15 @@
    +This plugin archives all buddy icons.
    +
    +Bug reports and patches are welcome: http://plugins.guifications.org
    +
    +This plugin is known to not compile against versions of GTK+ < 2.4. If this is
    +important to you, file a support request with a copy of the compiler errors
    +you get when attempting to compile so it can be fixed.
    +
    +Once you have the plugin installed, activate it in the plugins dialog
    +(Tools -> Plugins). The plugin will save all buddy icons as they arrive.
    +To view the icons, right-click on a buddy and choose View Buddy Icons. You
    +can also use Tools -> Album -> View Buddy Icon and then type in a
    +buddy's name. This is useful if the person is offline and you have Show
    +Offline Buddies unchecked or if you don't have the person on your buddy
    +list.
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/TODO Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,3 @@
    +- At some point, the number of stored icons will become unwieldy.
    + - We should find a way to load the icons as scrolling happens.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/album-ui.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,1219 @@
    +/*
    + * Album (Buddy Icon Archiver)
    + *
    + * 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
    + * 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 "album-ui.h"
    +
    +/* We want to use the gstdio functions when possible so that non-ASCII
    + * filenames are handled properly on Windows. */
    +#if GLIB_CHECK_VERSION(2,6,0)
    +#include <glib/gstdio.h>
    +#else
    +#include <sys/stat.h>
    +#define g_fopen fopen
    +#define g_stat stat
    +#endif
    +
    +#include <gtk/gtk.h>
    +#include <gtk/gtkwidget.h>
    +
    +#include <sys/stat.h>
    +#include <string.h>
    +
    +#include <account.h>
    +#include <debug.h>
    +#include <gtkblist.h>
    +#include <pidgin.h>
    +#include <plugin.h>
    +#include <request.h>
    +#include <util.h>
    +
    +/* XXX: For DATADIR... There must be a better way. */
    +#ifdef _WIN32
    +#include <win32/win32dep.h>
    +#endif
    +
    +/* The increment between sizes. */
    +#ifndef ICON_SIZE_MULTIPLIER
    +#define ICON_SIZE_MULTIPLIER 32
    +#endif
    +
    +/* The size of the icon in the icon viewer's label. */
    +#ifndef LABEL_ICON_SIZE
    +#define LABEL_ICON_SIZE 24
    +#endif
    +
    +/* How much to pad between two rows. */
    +#ifndef ROW_PADDING
    +#define ROW_PADDING 0
    +#endif
    +
    +/* Padding around the vbox containing the image and text. */
    +#ifndef VBOX_BORDER
    +#define VBOX_BORDER 10
    +#endif
    +
    +/* Spacing between children in the vbox containing the image and text. */
    +#ifndef VBOX_SPACING
    +#define VBOX_SPACING 5
    +#endif
    +
    +/* Padding around the text label for an icon. */
    +#ifndef LABEL_PADDING
    +#define LABEL_PADDING 3
    +#endif
    +
    +/* Padding around the image. */
    +#ifndef IMAGE_PADDING
    +#define IMAGE_PADDING 3
    +#endif
    +
    +typedef struct _BuddyIcon BuddyIcon;
    +typedef struct _BuddyWindow BuddyWindow;
    +typedef struct _icon_viewer_key icon_viewer_key;
    +
    +struct _BuddyIcon
    +{
    + char *full_path;
    + time_t timestamp;
    +
    + /* For suggesting a filename on image save. */
    + char *buddy_name;
    +};
    +
    +struct _BuddyWindow
    +{
    + GtkWidget *window;
    + GtkWidget *vbox;
    + GtkWidget *iconview;
    + GtkTextBuffer *text_buffer;
    + int text_height;
    + int text_width;
    + GtkRequisition requisition;
    +};
    +
    +/* contact or ((account and screenname) and possibly buddy) will be set.*/
    +struct _icon_viewer_key
    +{
    + PurpleContact *contact;
    +
    + PurpleBuddy *buddy;
    +
    + PurpleAccount *account;
    + char *screenname;
    + GList *list;
    +};
    +
    +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;
    +
    + if (key->contact != NULL)
    + return g_direct_hash(key->contact);
    +
    + return g_str_hash(key->screenname) +
    + g_str_hash(purple_account_get_username(key->account));
    +}
    +
    +gboolean icon_viewer_equal(gconstpointer y, gconstpointer z)
    +{
    + const icon_viewer_key *a, *b;
    +
    + a = y;
    + b = z;
    +
    + if (a->contact != NULL)
    + {
    + if (b->contact != NULL)
    + return (a->contact == b->contact);
    + else
    + return FALSE;
    + }
    + else if (b->contact != NULL)
    + return FALSE;
    +
    + if (a->account == b->account)
    + {
    + char *normal = g_strdup(purple_normalize(a->account, a->screenname));
    + if (!strcmp(normal, purple_normalize(b->account, b->screenname)))
    + {
    + g_free(normal);
    + return TRUE;
    + }
    + g_free(normal);
    + }
    +
    + return FALSE;
    +}
    +
    +static void set_window_geometry(BuddyWindow *bw, int buddy_icon_size)
    +{
    + GdkGeometry geom;
    +
    + g_return_if_fail(bw != NULL);
    +
    + /* Set the window geometry. This controls window resizing. */
    + geom.base_width = bw->requisition.width + 40; /* XXX: Where is the hardcoded value coming from? */
    + geom.base_height = bw->requisition.height + 18; /* XXX: Where is the hardcoded value coming from? */
    + geom.width_inc = MAX(buddy_icon_size, bw->text_width) + 2 * VBOX_BORDER;
    + geom.height_inc = ROW_PADDING + 2 * VBOX_BORDER + buddy_icon_size + 2 * IMAGE_PADDING + VBOX_SPACING + bw->text_height + 2 * LABEL_PADDING;
    + geom.min_width = geom.base_width + 3 * geom.width_inc; /* Minimum size: 3 wide */
    + geom.min_height = geom.base_height + geom.height_inc; /* Minimum size: 1 high */
    + gtk_window_set_geometry_hints(GTK_WINDOW(bw->window), bw->vbox, &geom,
    + GDK_HINT_MIN_SIZE | GDK_HINT_RESIZE_INC | GDK_HINT_BASE_SIZE);
    +}
    +
    +static gboolean resize_icons(GtkWidget *combo, icon_viewer_key *key)
    +{
    + int sel = gtk_combo_box_get_active(GTK_COMBO_BOX(combo));
    + BuddyWindow *bw;
    +
    + switch(sel)
    + {
    + case 0: /* Small */
    + case 1: /* Medium */
    + case 2: /* Large */
    + purple_prefs_set_int(PREF_ICON_SIZE, sel);
    + break;
    + default:
    + g_return_val_if_reached(FALSE);
    + }
    + update_icon_view(key);
    +
    + bw = g_hash_table_lookup(buddy_windows, key);
    + g_return_val_if_fail(bw != NULL, FALSE);
    + set_window_geometry(bw, ICON_SIZE_MULTIPLIER * (sel + 1));
    +
    + return FALSE;
    +}
    +
    +/* Save the size of the window. */
    +static gboolean update_size(GtkWidget *win, GdkEventConfigure *event, gpointer data)
    +{
    + int w;
    + int h;
    +
    + gtk_window_get_size(GTK_WINDOW(win), &w, &h);
    +
    + purple_prefs_set_int(PREF_WINDOW_WIDTH, w);
    + purple_prefs_set_int(PREF_WINDOW_HEIGHT, h);
    +
    + /* We want the normal handling to continue. */
    + return FALSE;
    +}
    +
    +static gboolean window_close(GtkWidget *win, gint resp, icon_viewer_key *key)
    +{
    + g_hash_table_remove(buddy_windows, key);
    + gtk_widget_destroy(win);
    + return TRUE;
    +}
    +
    +/* Returns a scroller window which contains widget. */
    +static GtkWidget *wrap_in_scroller(GtkWidget *widget)
    +{
    + GtkWidget *scroller = gtk_scrolled_window_new(NULL, NULL);
    +
    + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroller),
    + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    +
    + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroller),
    + GTK_SHADOW_IN);
    +
    + gtk_container_add(GTK_CONTAINER(scroller), widget);
    +
    + return scroller;
    +}
    +
    +/* Based on Purple's gtkimhtml.c, image_save_yes_cb(). */
    +static void convert_image(GtkImage *image, const char *filename)
    +{
    + gchar *type = NULL;
    + GError *error = NULL;
    + GSList *formats = gdk_pixbuf_get_formats();
    +
    + while (formats)
    + {
    + GdkPixbufFormat *format = formats->data;
    + gchar **extensions = gdk_pixbuf_format_get_extensions(format);
    + gpointer p = extensions;
    +
    + while (gdk_pixbuf_format_is_writable(format) && extensions && extensions[0])
    + {
    + gchar *fmt_ext = extensions[0];
    + const gchar *file_ext = filename + strlen(filename) - strlen(fmt_ext);
    +
    + if (!strcmp(fmt_ext, file_ext))
    + {
    + type = gdk_pixbuf_format_get_name(format);
    + break;
    + }
    +
    + extensions++;
    + }
    +
    + g_strfreev(p);
    +
    + if (type)
    + break;
    +
    + formats = formats->next;
    + }
    +
    + g_slist_free(formats);
    +
    + if (!type)
    + {
    + GtkWidget *dialog;
    +
    + /* Present an error dialog. */
    + dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
    + _("<span size='larger' weight='bold'>Unrecognized file type</span>\n\nDefaulting to PNG."));
    + g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
    + gtk_widget_show(dialog);
    +
    + /* Assume the user wants a PNG. */
    + type = g_strdup("png");
    + }
    +
    + gdk_pixbuf_save(gtk_image_get_pixbuf(image), filename, type, &error, NULL);
    +
    + if (error)
    + {
    + GtkWidget *dialog;
    +
    + /* Present an error dialog. */
    + dialog = gtk_message_dialog_new_with_markup(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
    + _("<span size='larger' weight='bold'>Error saving image</span>\n\n%s"),
    + error->message);
    + g_signal_connect_swapped(dialog, "response", G_CALLBACK (gtk_widget_destroy), dialog);
    + gtk_widget_show(dialog);
    +
    + g_error_free(error);
    + }
    +
    + g_free(type);
    +}
    +
    +static void
    +image_save_cb(GtkWidget *widget, gint response, GtkImage *image)
    +{
    + char *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(widget));
    + char *image_name = g_object_get_data(G_OBJECT(image), "filename");
    +
    + gtk_widget_destroy(widget);
    +
    + if (response != GTK_RESPONSE_ACCEPT)
    + return;
    +
    + purple_debug_misc(PLUGIN_STATIC_NAME, "Saving image %s as: %s\n", image_name, filename);
    +
    + /* Keeping this crud out of this function is a Good Thing (TM). */
    + convert_image(image, filename);
    +
    + g_free(filename);
    +}
    +
    +static void save_dialog(GtkWidget *widget, GtkImage *image)
    +{
    + GtkWidget *dialog;
    + const char *ext;
    + char *filename;
    +
    + dialog = gtk_file_chooser_dialog_new(_("Save Image"),
    + NULL,
    + GTK_FILE_CHOOSER_ACTION_SAVE,
    + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
    + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
    + NULL);
    + gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
    +
    + /* Determine the extension. */
    + ext = g_object_get_data(G_OBJECT(image), "filename");
    + if (ext)
    + ext = strrchr(ext, '.');
    + if (ext == NULL)
    + ext = "";
    +
    + filename = g_strdup_printf("%s%s", purple_escape_filename(g_object_get_data(G_OBJECT(image), "buddy_name")), ext);
    + gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), filename);
    + g_free(filename);
    +
    + g_signal_connect(G_OBJECT(GTK_FILE_CHOOSER(dialog)), "response",
    + G_CALLBACK(image_save_cb), image);
    +
    + gtk_widget_show(dialog);
    +}
    +
    +static gboolean save_menu(GtkWidget *event_box, GdkEventButton *event, GtkImage *image)
    +{
    + GtkWidget *menu = gtk_menu_new();
    + GtkWidget *item = gtk_image_menu_item_new_with_mnemonic("_Save Icon");
    +
    + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(item),
    + gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU));
    +
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(save_dialog), image);
    +
    + gtk_widget_show_all(menu);
    +
    + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, event_box, 3, event->time);
    +
    + return FALSE;
    +}
    +
    +static gint buddy_icon_compare(BuddyIcon *b1, BuddyIcon *b2)
    +{
    + gint ret;
    +
    + /* NOTE: This sorts by timestamp DESCENDING. */
    + if ((ret = b2->timestamp - b1->timestamp) != 0)
    + return ret;
    +
    + /* This isn't strictly necessary, but it
    + * ensures the icon order is stable. */
    + return strcmp(b1->full_path, b2->full_path);
    +}
    +
    +/* Returns an unsorted list of available icons for buddy (as BuddyIcon *).
    + * The items need to be freed.
    + */
    +static GList *retrieve_icons(PurpleAccount *account, const char *name)
    +{
    + char *path;
    + GDir *dir;
    + const char *filename;
    + GList *list = NULL;
    +
    + path = album_buddy_icon_get_dir(account, name);
    + if (path == NULL)
    + {
    + purple_debug_warning(PLUGIN_STATIC_NAME, "Path for buddy %s not found.\n", name);
    + return NULL;
    + }
    +
    + if (!(dir = g_dir_open(path, 0, NULL)))
    + {
    + purple_debug_warning(PLUGIN_STATIC_NAME, "Could not open path: %s\n", path);
    + g_free(path);
    + return NULL;
    + }
    +
    + while ((filename = g_dir_read_name(dir)))
    + {
    + char *full_path = g_build_filename(path, filename, NULL);
    + struct stat st;
    + BuddyIcon *icon;
    +
    + /* We need the file's timestamp. */
    + if (stat(full_path, &st) != 0)
    + {
    + g_free(full_path);
    + continue;
    + }
    +
    + icon = g_new0(BuddyIcon, 1);
    + icon->full_path = full_path;
    + icon->timestamp = st.st_mtime;
    + icon->buddy_name = g_strdup(name);
    +
    + list = g_list_prepend(list, icon);
    + }
    +
    + g_dir_close(dir);
    +
    + g_free(path);
    + return list;
    +}
    +
    +static gboolean add_icon_from_list_cb(gpointer data)
    +{
    + GtkTextIter text_iter;
    + int buddy_icon_pref = purple_prefs_get_int(PREF_ICON_SIZE);
    + int buddy_icon_size;
    + BuddyWindow *bw;
    + GtkTextBuffer *text_buffer;
    + GtkTextIter start, end;
    + GtkWidget *iconview;
    + icon_viewer_key *key = data;
    + BuddyIcon *icon;
    + GdkPixbuf *pixbuf;
    + int width;
    + int height;
    + int xpad = 0;
    + int ypad = 0;
    + GdkPixbuf *scaled;
    + GtkWidget *image;
    + GtkWidget *event_box;
    + GtkWidget *alignment;
    + GtkWidget *widget;
    + GtkWidget *label;
    + const char *timestamp;
    + GtkTextChildAnchor *anchor;
    + BuddyIcon *prev_icon;
    + char *prev_icon_filename;
    + GList *l;
    +
    +
    + /* If we're out of icons, kill this idle callback. */
    + if (key->list == NULL)
    + return FALSE;
    +
    + bw = g_hash_table_lookup(buddy_windows, key);
    + g_return_val_if_fail(bw != NULL, FALSE);
    +
    + text_buffer = bw->text_buffer;
    + iconview = bw->iconview;
    +
    + /* Clamp the pref value to the allowable range. */
    + buddy_icon_pref = CLAMP(buddy_icon_pref, 0, 2);
    +
    + buddy_icon_size = ICON_SIZE_MULTIPLIER * (buddy_icon_pref + 1);
    +
    + gtk_text_buffer_get_end_iter(text_buffer, &text_iter);
    +
    +
    + /* Duplicate Removal */
    +
    + prev_icon = key->list->data;
    + /* Technically, this will yield "/filename.ext", but the
    + * code below will get the same thing, so it'll compare fine. */
    + prev_icon_filename = strrchr(prev_icon->full_path, '/');
    + if (prev_icon_filename == NULL)
    + {
    + /* This should never happen. */
    + prev_icon_filename = prev_icon->full_path;
    + }
    +
    + for (l = key->list->next ; l != NULL ; l = l->next)
    + {
    + BuddyIcon *this_icon = l->data;
    + char *this_icon_filename = strrchr(this_icon->full_path, '/');
    +
    + if (this_icon_filename == NULL)
    + {
    + /* This should never happen. */
    + this_icon_filename = this_icon->full_path;
    + }
    +
    + /* The files are named by hash, so if they have the
    + * same basename, we can assume they have the same
    + * contents. This happens when someone uses the
    + * same icon on multiple accounts. We only want to
    + * show each icon once.
    + */
    + if (!strcmp(this_icon_filename, prev_icon_filename))
    + {
    + key->list = g_list_delete_link(key->list, l);
    + }
    + }
    +
    +
    + /* Pop off one icon to add. */
    + icon = key->list->data;
    + key->list = g_list_delete_link(key->list, key->list);
    +
    + pixbuf = gdk_pixbuf_new_from_file(icon->full_path, NULL);
    + if (pixbuf == NULL)
    + {
    + purple_debug_warning(PLUGIN_STATIC_NAME, "Invalid image file: %s\n", icon->full_path);
    + g_free(icon->full_path);
    + g_free(icon->buddy_name);
    + g_free(icon);
    + return TRUE;
    + }
    +
    + width = gdk_pixbuf_get_width(pixbuf);
    + height = gdk_pixbuf_get_height(pixbuf);
    +
    + /* Never scale the image up. */
    + if (height > buddy_icon_size || width > buddy_icon_size)
    + {
    + /* Scale the image proportionally to fit with a square of size buddy_icon_size. */
    + if (width > height)
    + {
    + int new_height = (int)(buddy_icon_size / (double)width * height);
    + ypad = buddy_icon_size - new_height;
    + scaled = gdk_pixbuf_scale_simple(pixbuf, buddy_icon_size, new_height, GDK_INTERP_BILINEAR);
    + }
    + else
    + {
    + int new_width = (int)(buddy_icon_size / (double)height * width);
    + xpad = buddy_icon_size - new_width;
    + scaled = gdk_pixbuf_scale_simple(pixbuf, new_width, buddy_icon_size, GDK_INTERP_BILINEAR);
    + }
    + g_object_unref(G_OBJECT(pixbuf));
    + }
    + else
    + {
    + ypad = buddy_icon_size - height;
    + xpad = buddy_icon_size - width;
    + scaled = pixbuf;
    + }
    +
    +
    + /* Now we're ready to add the icon. */
    +
    + /* Create the image and an event box to which to attach the right-click menu. */
    + image = gtk_image_new_from_pixbuf(scaled);
    + g_object_unref(G_OBJECT(scaled));
    + event_box = gtk_event_box_new();
    + gtk_event_box_set_visible_window(GTK_EVENT_BOX(event_box), FALSE);
    + gtk_container_add(GTK_CONTAINER(event_box), image);
    +
    + /* Save the filename and create a right-click handler. */
    + g_object_set_data_full(G_OBJECT(image), "buddy_name", icon->buddy_name, g_free);
    + g_object_set_data_full(G_OBJECT(image), "filename", icon->full_path, g_free);
    + g_signal_connect(G_OBJECT(event_box), "button-press-event",
    + G_CALLBACK(save_menu), image);
    +
    + /* Add padding as required. */
    + alignment = gtk_alignment_new(0.5, 0.5, 0, 0);
    + /* The + 1 is in case the padding is odd. It ensures that one side will get the extra one pixel so that
    + * the padding is always correct. Without it, we'd be one pixel short whenever xpad or ypad was odd. */
    + gtk_alignment_set_padding(GTK_ALIGNMENT(alignment), ypad / 2, (ypad + 1) / 2, xpad / 2, (xpad + 1) / 2);
    + gtk_container_add(GTK_CONTAINER(alignment), event_box);
    +
    + widget = gtk_vbox_new(FALSE, VBOX_SPACING);
    + gtk_container_set_border_width(GTK_CONTAINER(widget), VBOX_BORDER);
    + gtk_box_pack_start(GTK_BOX(widget), alignment, FALSE, FALSE, IMAGE_PADDING);
    +
    + /* Label */
    + timestamp = purple_utf8_strftime(_("%x\n%X"), localtime(&icon->timestamp));
    + label = gtk_label_new(NULL);
    + gtk_label_set_text(GTK_LABEL(label), timestamp);
    + gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
    + gtk_box_pack_start(GTK_BOX(widget), label, TRUE, TRUE, LABEL_PADDING);
    +
    + anchor = gtk_text_buffer_create_child_anchor(text_buffer, &text_iter);
    + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(iconview), widget, anchor);
    +
    + gtk_widget_show_all(widget);
    + gtk_text_buffer_get_bounds(text_buffer, &start, &end);
    + gtk_text_buffer_apply_tag_by_name(text_buffer, "word_wrap", &start, &end);
    +
    + g_free(icon);
    + return TRUE;
    +}
    +
    +static void update_icon_view(icon_viewer_key *key)
    +{
    + GtkTextIter start;
    + GtkTextIter end;
    + GList *list = NULL;
    + BuddyWindow *bw;
    + GtkWidget *iconview;
    + GtkTextBuffer *text_buffer;
    + gboolean success = FALSE;
    +
    + bw = g_hash_table_lookup(buddy_windows, key);
    + g_return_if_fail(bw != NULL);
    + iconview = bw->iconview;
    + text_buffer = bw->text_buffer;
    +
    + gtk_text_buffer_get_bounds(text_buffer, &start, &end);
    + gtk_text_buffer_delete(text_buffer, &start, &end);
    +
    + if (key->contact != NULL)
    + {
    + PurpleBlistNode *bnode;
    +
    + /* Get the list of all the accounts of the contact and sort it. */
    + for (bnode = ((PurpleBlistNode *)key->contact)->child; bnode ; bnode = bnode->next)
    + {
    + list = g_list_concat(retrieve_icons(purple_buddy_get_account((PurpleBuddy *)bnode),
    + purple_buddy_get_name((PurpleBuddy *)bnode)), list);
    + }
    + }
    + else if (key->buddy != NULL)
    + {
    + list = retrieve_icons(purple_buddy_get_account(key->buddy),
    + purple_buddy_get_name(key->buddy));
    + }
    + else
    + {
    + list = retrieve_icons(key->account, key->screenname);
    + }
    +
    + /* Show icons for all the accounts of the contact. */
    + if (list != NULL)
    + {
    + int id;
    +
    + list = g_list_sort(list, (GCompareFunc)buddy_icon_compare);
    + success = (list != NULL);
    + key->list = list;
    +
    + /* It's possible we already have one of these loops running, but
    + * having two won't harm anything, so we don't do anything about it. */
    + id = g_idle_add(add_icon_from_list_cb, key);
    + g_object_set_data_full(G_OBJECT(iconview), "update-idle-callback",
    + GINT_TO_POINTER(id), (GDestroyNotify)g_source_remove);
    + }
    +
    + if (!success)
    + {
    + /* No icons were found. Display an appropriate message. */
    + GtkWidget *hbox;
    + char *filename;
    + GdkPixbuf *pixbuf;
    + GdkPixbuf *scaled;
    + GtkWidget *image;
    + char *str;
    + GtkWidget *label;
    + GtkTextIter text_iter;
    + GtkTextChildAnchor *anchor;
    +
    + /* Hbox */
    + /* Reuse spacing information that's used for the vbox which contains the image and text for each icon. */
    + hbox = gtk_hbox_new(FALSE, VBOX_SPACING);
    + gtk_container_set_border_width(GTK_CONTAINER(hbox), VBOX_BORDER);
    +
    + /* Image */
    +#ifndef _WIN32
    + filename = g_build_filename(PIXMAPSDIR, "dialogs", "purple_info.png", NULL);
    +#else
    + filename = g_build_filename(wpurple_install_dir(), "pixmaps", "pidgin", "dialogs", "purple_info.png", NULL);
    +#endif
    +
    + pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
    + g_free(filename);
    +
    + scaled = gdk_pixbuf_scale_simple(pixbuf, 48, 48, GDK_INTERP_BILINEAR);
    + g_object_unref(G_OBJECT(pixbuf));
    +
    + image = gtk_image_new_from_pixbuf(scaled);
    + g_object_unref(G_OBJECT(scaled));
    + gtk_box_pack_start(GTK_BOX(hbox), image, FALSE, FALSE, 0);
    +
    +
    + /* Label */
    + str = g_strdup_printf("<span size='larger' weight='bold'>%s</span>", _("No icons were found."));
    + label = gtk_label_new(NULL);
    + gtk_label_set_markup(GTK_LABEL(label), str);
    + g_free(str);
    +
    + gtk_misc_set_alignment(GTK_MISC(label), 0, 0.5);
    + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    +
    + /* Add the hbox to the text view. */
    + gtk_text_buffer_get_iter_at_offset (text_buffer, &text_iter, 0);
    + anchor = gtk_text_buffer_create_child_anchor(text_buffer, &text_iter);
    + gtk_text_view_add_child_at_anchor(GTK_TEXT_VIEW(iconview), hbox, anchor);
    + }
    +
    + gtk_widget_show_all(iconview);
    + gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(iconview), FALSE);
    +}
    +
    +static void update_runtime(icon_viewer_key *key, gpointer value, PurpleBuddy *buddy)
    +{
    + PurpleAccount *account = purple_buddy_get_account(buddy);
    +
    + if (key->contact != NULL)
    + {
    + char *name = g_strdup(purple_normalize(account, purple_buddy_get_name(buddy)));
    + PurpleBlistNode *node;
    +
    + for (node = ((PurpleBlistNode *)key->contact)->child; node; node = node->next)
    + {
    + if (account == purple_buddy_get_account((PurpleBuddy *)node) &&
    + !strcmp(name, purple_normalize(account, purple_buddy_get_name((PurpleBuddy *)node))))
    + {
    + g_free(name);
    + update_icon_view(key);
    + return;
    + }
    + }
    + g_free(name);
    + }
    + else if (account == key->account &&
    + !strcmp(key->screenname, purple_normalize(account, purple_buddy_get_name(buddy))))
    + {
    + update_icon_view(key);
    + }
    +}
    +
    +void album_update_runtime(PurpleBuddy *buddy, gpointer data)
    +{
    + g_hash_table_foreach(buddy_windows, (GHFunc)update_runtime, buddy);
    +}
    +
    +static GtkWidget *get_viewer_icon()
    +{
    + char *filename = NULL;
    + GdkPixbuf *pixbuf;
    + int width;
    + int height;
    + GdkPixbuf *scaled;
    + GtkWidget *image;
    +
    + if (filename == NULL)
    +#ifndef _WIN32
    + filename = g_build_filename(PIXMAPSDIR, "icons", "online.png", NULL);
    +#else
    + filename = g_build_filename(wpurple_install_dir(), "pixmaps", "pidgin", "icons", "online.png", NULL);
    +#endif
    +
    + pixbuf = gdk_pixbuf_new_from_file(filename, NULL);
    + g_free(filename);
    +
    + width = gdk_pixbuf_get_width(pixbuf);
    + height = gdk_pixbuf_get_height(pixbuf);
    +
    + /* Never scale the image up. */
    + if (height > LABEL_ICON_SIZE || width > LABEL_ICON_SIZE)
    + {
    + /* Scale the image proportionally to fit with a square of size buddy_icon_size. */
    + if (width > height)
    + {
    + int new_height = (int)(LABEL_ICON_SIZE / (double)width * height);
    + scaled = gdk_pixbuf_scale_simple(pixbuf, LABEL_ICON_SIZE, new_height, GDK_INTERP_BILINEAR);
    + }
    + else
    + {
    + int new_width = (int)(LABEL_ICON_SIZE / (double)height * width);
    + scaled = gdk_pixbuf_scale_simple(pixbuf, new_width, LABEL_ICON_SIZE, GDK_INTERP_BILINEAR);
    + }
    + g_object_unref(G_OBJECT(pixbuf));
    + }
    + else
    + {
    + scaled = pixbuf;
    + }
    +
    + image = gtk_image_new_from_pixbuf(scaled);
    + g_object_unref(G_OBJECT(scaled));
    + return image;
    +}
    +
    +static void view_buddy_icons_cb(PurpleBlistNode *node, gpointer data)
    +{
    + gboolean contact_expanded;
    + icon_viewer_key *key = g_new0(icon_viewer_key, 1);
    + const char *name;
    +
    + g_return_if_fail(node != NULL);
    +
    + contact_expanded = pidgin_blist_node_is_contact_expanded(node);
    +
    + if (PURPLE_BLIST_NODE_IS_BUDDY(node))
    + {
    + if (!contact_expanded)
    + {
    + /* Work on the contact, since it's collapsed. */
    +
    + name = purple_contact_get_alias((PurpleContact*)node->parent);
    + if (name == NULL)
    + name = purple_buddy_get_name(((PurpleContact *)node->parent)->priority);
    +
    + if (node->next != NULL)
    + {
    + /* The contact has at least two buddies. */
    + key->contact = (PurpleContact *)node->parent;
    + }
    + else
    + {
    + /* Treat one-buddy contacts similar to buddies. */
    + key->account = purple_buddy_get_account((PurpleBuddy *)node);
    + key->screenname = g_strdup(purple_normalize(key->account, purple_buddy_get_name((PurpleBuddy *)node)));
    + key->buddy = (PurpleBuddy *)node;
    + }
    + }
    + else
    + {
    + /* The contact is expanded and the user has chosen a buddy. */
    +
    + key->account = purple_buddy_get_account((PurpleBuddy *)node);
    + key->screenname = g_strdup(purple_normalize(key->account, purple_buddy_get_name((PurpleBuddy *)node)));
    + key->buddy = (PurpleBuddy *)node;
    +
    + name = purple_buddy_get_alias_only((PurpleBuddy *)node);
    + if (name == NULL)
    + {
    + name = purple_buddy_get_name((PurpleBuddy *)node);
    + }
    + }
    + }
    + else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
    + {
    + /* The contact is expanded and the user has chosen the contact. */
    +
    + if (node->child != NULL && node->child->next != NULL)
    + {
    + /* The contact has at least two buddies. */
    + key->contact = (PurpleContact *)node;
    + }
    + else
    + {
    + /* Treat one-buddy contacts similar to buddies. */
    + key->account = purple_buddy_get_account((PurpleBuddy *)node->child);
    + key->screenname = g_strdup(purple_normalize(key->account, purple_buddy_get_name((PurpleBuddy *)node->child)));
    + key->buddy = (PurpleBuddy *)node->child;
    + }
    +
    + name = purple_contact_get_alias((PurpleContact*)node);
    + if (name == NULL)
    + name = purple_buddy_get_name(((PurpleContact *)node)->priority);
    + }
    + else
    + g_return_if_reached();
    +
    + show_buddy_icon_window(key, name);
    +}
    +
    +static gboolean compare_buddy_keys(icon_viewer_key *key1, BuddyWindow *bw, icon_viewer_key *key2)
    +{
    + g_return_val_if_fail(key2->contact == NULL, FALSE);
    +
    + if (key1->contact == NULL)
    + {
    + if (key1->account == key2->account)
    + {
    + char *normal = g_strdup(purple_normalize(key1->account, key1->screenname));
    + if (!strcmp(normal, purple_normalize(key2->account, key2->screenname)))
    + {
    + g_free(normal);
    + return TRUE;
    + }
    + g_free(normal);
    + }
    + }
    +
    + return FALSE;
    +}
    +
    +static void show_buddy_icon_window(icon_viewer_key *key, const char *name)
    +{
    + char *title;
    + GtkWidget *win;
    + GtkWidget *vbox;
    + char *str;
    + GtkTextIter start;
    + GtkTextIter end;
    + GtkWidget *title_box;
    + GtkWidget *label;
    + GtkWidget *combo;
    + GtkWidget *iconview;
    + GtkTextBuffer *text_buffer;
    + time_t now;
    + const char *timestamp;
    + PangoLayout *layout;
    + int text_width;
    + int text_height;
    + int buddy_icon_pref = purple_prefs_get_int(PREF_ICON_SIZE);
    + int buddy_icon_size;
    + BuddyWindow *bw;
    +
    + /* 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;
    + }
    +
    + /* If it was a contact, it would've matched above.
    + * Compare screennames...
    + */
    + 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;
    + }
    +
    + /* Clamp the pref value to the allowable range. */
    + buddy_icon_pref = MAX(0, buddy_icon_pref);
    + buddy_icon_pref = MIN(2, buddy_icon_pref);
    +
    + buddy_icon_size = ICON_SIZE_MULTIPLIER * (buddy_icon_pref + 1);
    +
    + title = g_strdup_printf(_("Buddy Icons used by %s"), name);
    +
    + win = gtk_dialog_new_with_buttons(title, NULL, 0,
    + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
    + gtk_window_set_role(GTK_WINDOW(win), "buddy_icon_viewer");
    + gtk_container_set_border_width(GTK_CONTAINER(win), 12);
    +
    + vbox = gtk_vbox_new(FALSE, 5);
    + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(win)->vbox), vbox, TRUE, TRUE, 0);
    +
    + iconview = gtk_text_view_new();
    + text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(iconview));
    + gtk_text_view_set_editable(GTK_TEXT_VIEW(iconview), FALSE);
    +#if ROW_PADDING != 0
    + gtk_text_view_set_pixels_inside_wrap(GTK_TEXT_VIEW(iconview), ROW_PADDING);
    +#endif
    + gtk_text_buffer_create_tag (text_buffer, "word_wrap",
    + "wrap_mode", GTK_WRAP_WORD, NULL);
    + gtk_text_buffer_get_bounds(text_buffer, &start, &end);
    + gtk_text_buffer_apply_tag_by_name(text_buffer, "word_wrap", &start, &end);
    +
    + /* Get the size of the text of a sample timestamp. */
    + now = time(NULL);
    + timestamp = purple_utf8_strftime("%x\n%X", localtime(&now));
    + layout = gtk_widget_create_pango_layout(iconview, timestamp);
    + pango_layout_get_pixel_size(layout, &text_width, &text_height);
    +
    +
    + /* Title Box */
    + title_box = gtk_hbox_new(FALSE, 6);
    + gtk_container_set_border_width(GTK_CONTAINER(title_box), 6);
    + gtk_box_pack_start(GTK_BOX(vbox), title_box, FALSE, FALSE, 0);
    +
    +
    + /* Icon */
    + gtk_box_pack_start(GTK_BOX(title_box), get_viewer_icon(), FALSE, FALSE, 0);
    +
    +
    + /* Label */
    + str = g_strdup_printf("<span size='larger' weight='bold'>%s</span>", title);
    + g_free(title);
    +
    + label = gtk_label_new(NULL);
    + gtk_label_set_markup(GTK_LABEL(label), str);
    + g_free(str);
    +
    + gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
    + gtk_box_pack_start(GTK_BOX(title_box), label, FALSE, FALSE, 0);
    +
    +
    + /* Icon View */
    + gtk_box_pack_start(GTK_BOX(vbox), wrap_in_scroller(iconview), TRUE, TRUE, 0);
    +
    +
    + /* Size Selector */
    + combo = gtk_combo_box_new_text();
    +
    + str = g_strdup_printf(_("Small (%1$ux%1$u)"), ICON_SIZE_MULTIPLIER);
    + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), str);
    + g_free(str);
    +
    + str = g_strdup_printf(_("Medium (%1$ux%1$u)"), ICON_SIZE_MULTIPLIER * 2);
    + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), str);
    + g_free(str);
    +
    + str = g_strdup_printf(_("Large (%1$ux%1$u)"), ICON_SIZE_MULTIPLIER * 3);
    + gtk_combo_box_append_text(GTK_COMBO_BOX(combo), str);
    + g_free(str);
    +
    + gtk_combo_box_set_active(GTK_COMBO_BOX(combo), buddy_icon_pref);
    +
    + gtk_widget_show_all(combo);
    + gtk_signal_connect(GTK_OBJECT(combo), "changed", GTK_SIGNAL_FUNC(resize_icons), key);
    +
    + gtk_box_pack_start(GTK_BOX(GTK_DIALOG(win)->action_area), combo, FALSE, FALSE, 0);
    + gtk_box_reorder_child(GTK_BOX(GTK_DIALOG(win)->action_area), combo, 0);
    +
    + /* Add the stuff in the hashtable. */
    + bw = g_new0(BuddyWindow, 1);
    + bw->window = win;
    + bw->vbox = vbox;
    + bw->iconview = iconview;
    + bw->text_buffer = text_buffer;
    + bw->text_height = text_height;
    + bw->text_width = text_width;
    + g_hash_table_insert(buddy_windows, key, bw);
    +
    + update_icon_view(key);
    +
    + gtk_widget_size_request(bw->iconview, &bw->requisition);
    + set_window_geometry(bw, buddy_icon_size);
    +
    + gtk_window_set_default_size(GTK_WINDOW(win),
    + purple_prefs_get_int(PREF_WINDOW_WIDTH),
    + purple_prefs_get_int(PREF_WINDOW_HEIGHT));
    +
    + gtk_window_set_policy(GTK_WINDOW(win), FALSE, TRUE, FALSE);
    + gtk_widget_show_all(win);
    +
    +#if 0
    + /* TODO: We need a way to disconnect this signal handler when the window is closed.
    + * TODO: Otherwise, it segfaults in update_runtime. */
    +
    + /* Register a signal handler to update the viewer if a new buddy icon is cached. */
    + purple_signal_connect_priority(purple_buddy_icons_get_handle(), "buddy-icon-cached",
    + purple_plugins_find_with_id(PLUGIN_ID), PURPLE_CALLBACK(update_runtime),
    + key, PURPLE_SIGNAL_PRIORITY_DEFAULT + 1);
    +#endif
    +
    + gtk_signal_connect(GTK_OBJECT(win), "configure_event",
    + GTK_SIGNAL_FUNC(update_size), NULL);
    + g_signal_connect(G_OBJECT(win), "response",
    + G_CALLBACK(window_close), key);
    +}
    +
    +static gboolean has_stored_icons(PurpleBuddy *buddy)
    +{
    + char *path = album_buddy_icon_get_dir(purple_buddy_get_account(buddy),
    + purple_buddy_get_name(buddy));
    + GDir *dir = g_dir_open(path, 0, NULL);
    +
    + g_free(path);
    +
    + if (dir)
    + {
    + if (g_dir_read_name(dir))
    + {
    + g_dir_close(dir);
    + return TRUE;
    + }
    + g_dir_close(dir);
    + }
    +
    + return FALSE;
    +}
    +
    +static void
    +album_select_dialog_cb(gpointer data, PurpleRequestFields *fields)
    +{
    + char *username;
    + PurpleAccount *account;
    +
    + account = purple_request_fields_get_account(fields, "account");
    +
    + username = g_strdup(purple_normalize(account,
    + purple_request_fields_get_string(fields, "screenname")));
    +
    + if (username != NULL && *username != '\0' && account != NULL )
    + {
    + icon_viewer_key *key = g_new0(icon_viewer_key, 1);
    + key->account = account;
    + key->screenname = username;
    +
    + show_buddy_icon_window(key, username);
    + }
    +}
    +
    +/* Based on Pidgin's gtkdialogs.c, pidgindialogs_log(). */
    +static void album_select_dialog(PurplePluginAction *action)
    +{
    + 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("screenname", _("_Name"), NULL, FALSE);
    + purple_request_field_set_type_hint(field, "screenname-all");
    + purple_request_field_set_required(field, TRUE);
    + purple_request_field_group_add_field(group, field);
    +
    + field = purple_request_field_account_new("account", _("_Account"), NULL);
    + purple_request_field_set_type_hint(field, "account");
    + purple_request_field_account_set_show_all(field, TRUE);
    + purple_request_field_set_visible(field, (purple_accounts_get_all() != NULL &&
    + purple_accounts_get_all()->next != NULL));
    + purple_request_field_set_required(field, TRUE);
    + purple_request_field_group_add_field(group, field);
    +
    + purple_request_fields(purple_get_blist(), _("View Buddy Icons..."),
    + NULL,
    + _("Please enter the screen name or alias of the person whose icon album you want to view."),
    + fields,
    + _("OK"), G_CALLBACK(album_select_dialog_cb),
    + _("Cancel"), NULL,
    + NULL, NULL, NULL,
    + NULL);
    +}
    +
    +GList *album_get_plugin_actions(PurplePlugin *plugin, gpointer data)
    +{
    + GList *actions = NULL;
    +
    + actions = g_list_append(actions, purple_plugin_action_new(_("View Buddy Icons"), album_select_dialog));
    +
    + return actions;
    +}
    +
    +void album_blist_node_menu_cb(PurpleBlistNode *node, GList **menu)
    +{
    + gboolean contact_expanded;
    + PurpleMenuAction *action;
    + void (*callback)() = view_buddy_icons_cb;
    +
    + if (!(PURPLE_BLIST_NODE_IS_CONTACT(node) || PURPLE_BLIST_NODE_IS_BUDDY(node)))
    + return;
    +
    + contact_expanded = pidgin_blist_node_is_contact_expanded(node);
    +
    + if (PURPLE_BLIST_NODE_IS_BUDDY(node))
    + {
    + if (contact_expanded)
    + {
    + if (!has_stored_icons((PurpleBuddy *)node))
    + {
    + callback = NULL;
    + }
    + }
    + else if (PURPLE_BLIST_NODE_IS_CONTACT(node))
    + {
    + /* We don't want to show this option in buddy submenus. */
    + if ((PurpleBlistNode *)((PurpleContact *)node->parent)->priority != node)
    + return;
    +
    + /* Find the contact and fall through to the contact handling code. */
    + node = node->parent;
    + }
    + }
    +
    + if (PURPLE_BLIST_NODE_IS_CONTACT(node))
    + {
    + PurpleBlistNode *bnode;
    +
    + for (bnode = node->child; bnode; bnode = bnode->next)
    + {
    + if (has_stored_icons((PurpleBuddy *)bnode))
    + break;
    + }
    +
    + /* bnode == NULL when the for loop made it all the way through. */
    + if (bnode == NULL)
    + {
    + /* No icons found. */
    + callback = NULL;
    + }
    + }
    +
    + /* Separator */
    + (*menu) = g_list_append(*menu, NULL);
    +
    + action = purple_menu_action_new(_("_View Buddy Icons"), PURPLE_CALLBACK(callback), NULL, NULL);
    + (*menu) = g_list_append(*menu, action);
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/album-ui.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,47 @@
    +/*
    + * Album (Buddy Icon Archiver)
    + *
    + * 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
    + * 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.
    + */
    +#ifndef _ALBUM_UI_H_
    +#define _ALBUM_UI_H_
    +
    +#include "album.h"
    +
    +#include <blist.h>
    +
    +#define PREF_WINDOW_HEIGHT PREF_PREFIX "/window_height"
    +#define PREF_WINDOW_WIDTH PREF_PREFIX "/window_width"
    +#define PREF_ICON_SIZE PREF_PREFIX "/icon_size"
    +
    +extern GHashTable *buddy_windows;
    +
    +guint icon_viewer_hash(gconstpointer data);
    +
    +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);
    +
    +void album_update_runtime(PurpleBuddy *buddy, gpointer data);
    +
    +#endif /* _ALBUM_UI_H_ */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/album.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,280 @@
    +/*
    + * Album (Buddy Icon Archiver)
    + *
    + * 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
    + * 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 "album.h"
    +
    +#include <errno.h>
    +#include <string.h>
    +
    +/* We want to use the gstdio functions when possible so that non-ASCII
    + * filenames are handled properly on Windows. */
    +#if GLIB_CHECK_VERSION(2,6,0)
    +#include <glib/gstdio.h>
    +#else
    +#include <sys/stat.h>
    +#define g_fopen fopen
    +#define g_stat stat
    +#endif
    +
    +#ifndef _WIN32
    +#include <sys/types.h>
    +#include <utime.h>
    +#include <unistd.h>
    +#endif
    +
    +#include <buddyicon.h>
    +#include <debug.h>
    +#include <plugin.h>
    +#include <util.h>
    +
    +#include <cipher.h>
    +
    +/* GUI */
    +#include <gtkplugin.h>
    +#include "album-ui.h"
    +
    +GHashTable *buddy_windows;
    +
    +#define PLUGIN_AUTHOR "Richard Laager <rlaager@guifications.org>" \
    + "\n\t\t\tSadrul Habib Chowdhury <imadil@gmail.com>"
    +
    +/*****************************************************************************
    + * Prototypes *
    + *****************************************************************************/
    +
    +static gboolean plugin_load(PurplePlugin *plugin);
    +
    +
    +/*****************************************************************************
    + * Plugin Info *
    + *****************************************************************************/
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + 0,
    +
    + PURPLE_PLUGIN_STANDARD, /**< type */
    + PIDGIN_PLUGIN_TYPE, /**< 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 */
    + album_get_plugin_actions, /**< plugin actions */
    + NULL, /**< reserved 1 */
    + NULL, /**< reserved 2 */
    + NULL, /**< reserved 3 */
    + NULL /**< reserved 4 */
    +};
    +
    +
    +/*****************************************************************************
    + * Helpers *
    + *****************************************************************************/
    +
    +char *album_buddy_icon_get_dir(PurpleAccount *account, const char *name)
    +{
    + PurplePlugin *prpl;
    + const char *prpl_name;
    + char *acct_name;
    + char *buddy_name;
    + char *dir;
    +
    + g_return_val_if_fail(account != NULL, NULL);
    + g_return_val_if_fail(name != NULL, NULL);
    +
    + /* BUILD THE DIRECTORY PATH */
    + prpl = purple_find_prpl(purple_account_get_protocol_id(account));
    + if (!prpl)
    + g_return_val_if_reached(NULL);
    +
    + prpl_name = PURPLE_PLUGIN_PROTOCOL_INFO(prpl)->list_icon(account, NULL);
    +
    + acct_name = g_strdup(purple_escape_filename(purple_normalize(account,
    + purple_account_get_username(account))));
    +
    + buddy_name = g_strdup(purple_escape_filename(purple_normalize(account, name)));
    +
    + dir = g_build_filename(purple_buddy_icons_get_cache_dir(), prpl_name, acct_name, buddy_name, NULL);
    + g_free(acct_name);
    + g_free(buddy_name);
    +
    + return dir;
    +}
    +
    +
    +/*****************************************************************************
    + * Callbacks *
    + *****************************************************************************/
    +
    +static void store_buddy_icon(PurpleBuddyIcon *icon, PurpleBuddy *buddy)
    +{
    + char *icon_path;
    + char *filename;
    + char *path;
    + char *dir;
    + gconstpointer icon_data;
    + size_t len;
    + FILE *file;
    +
    +#ifndef _WIN32
    + int status;
    +#endif
    +
    + /* BUILD THE DIRECTORY PATH & CREATE DIRECTORY */
    + dir = album_buddy_icon_get_dir(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy));
    + purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    +
    + icon_path = purple_buddy_icon_get_full_path(icon);
    + filename = g_path_get_basename(icon_path);
    + path = g_build_filename(dir, filename, NULL);
    + g_free(dir);
    + g_free(filename);
    +
    +#ifndef _WIN32
    + /* TRY MAKING A HARD LINK */
    + status = link(icon_path, path);
    + if (status != 0)
    + {
    + if (status != EEXIST)
    + {
    +#endif
    + icon_data = purple_buddy_icon_get_data(icon, &len);
    +
    + /* WRITE THE DATA */
    + if ((file = g_fopen(path, "wb")) != NULL)
    + {
    + if (!fwrite(icon_data, len, 1, file))
    + {
    + purple_debug_error(PLUGIN_STATIC_NAME, "Failed to write to %s: %s\n",
    + path, strerror(errno));
    + fclose(file);
    + g_unlink(path);
    + }
    + else
    + fclose(file);
    + }
    +#ifndef _WIN32
    + }
    + else
    + {
    + utime(path, NULL);
    + }
    + }
    +#endif
    + g_free(icon_path);
    + g_free(path);
    +}
    +
    +static void cache_for_buddy(gpointer key, PurpleBuddy *buddy, gpointer data)
    +{
    + PurpleBuddyIcon *icon;
    +
    + icon = purple_buddy_get_icon(buddy);
    + if (icon == NULL)
    + return;
    +
    + purple_debug_misc(PLUGIN_STATIC_NAME, "Caching icon for buddy: %s\n",
    + purple_buddy_get_name(buddy));
    +
    + store_buddy_icon(icon, buddy);
    +}
    +
    +static
    +void
    +cache_existing_icons(gpointer data)
    +{
    + /* Cache Existing Icons */
    + g_hash_table_foreach(purple_get_blist()->buddies, (GHFunc)cache_for_buddy, data);
    +}
    +
    +static void buddy_icon_changed_cb(PurpleBuddy *buddy)
    +{
    + /* This callback doesn't use either of the arguments besides buddy,
    + * so it's convenient to reuse it here. */
    + cache_for_buddy(NULL, buddy, NULL);
    +}
    +
    +
    +/*****************************************************************************
    + * Plugin Code *
    + *****************************************************************************/
    +
    +static gboolean plugin_load(PurplePlugin *plugin)
    +{
    + purple_signal_connect(purple_blist_get_handle(), "buddy-icon-changed",
    + plugin, PURPLE_CALLBACK(buddy_icon_changed_cb), NULL);
    +
    + /* Some UI stuff here. */
    + purple_signal_connect_priority(
    + purple_blist_get_handle(),
    + "buddy-icon-changed",
    + plugin,
    + PURPLE_CALLBACK(album_update_runtime),
    + NULL,
    + PURPLE_SIGNAL_PRIORITY_DEFAULT + 1);
    +
    + purple_signal_connect(purple_blist_get_handle(),
    + "blist-node-extended-menu",
    + plugin,
    + PURPLE_CALLBACK(album_blist_node_menu_cb),
    + NULL);
    +
    + cache_existing_icons(NULL);
    +
    + buddy_windows = g_hash_table_new_full(icon_viewer_hash, icon_viewer_equal, icon_viewer_key_free, g_free);
    +
    + return TRUE;
    +}
    +
    +static void plugin_init(PurplePlugin *plugin)
    +{
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("Album");
    + info.summary = _("Archives buddy icons.");
    + info.description = _("Enable this plugin to automatically archive all buddy icons.");
    +
    + /* Setup preferences. */
    + purple_prefs_add_none(PREF_PREFIX);
    + purple_prefs_add_int(PREF_WINDOW_HEIGHT, 258);
    + purple_prefs_add_int(PREF_WINDOW_WIDTH, 362);
    + purple_prefs_add_int(PREF_ICON_SIZE, 1);
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, plugin_init, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/album.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,36 @@
    +/*
    + * Album (Buddy Icon Archiver)
    + *
    + * 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
    + * 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.
    + */
    +#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>
    +
    +#define PLUGIN_STATIC_NAME "album"
    +#define PLUGIN_ID "gtk-rlaager-" PLUGIN_STATIC_NAME
    +#define PREF_PREFIX "/plugins/" PLUGIN_ID
    +
    +char *album_buddy_icon_get_dir(PurpleAccount *account, const char *name);
    +
    +#endif /* _ALBUM_H_ */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/album/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Album]
    +type=default
    +depends=pidgin
    +provides=album
    +summary=Archives buddy icons
    +description=%(summary)s
    +authors=Richard Laager,Sadrul Habib Chowdhury
    +introduced=2.0.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autogen.sh Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,159 @@
    +#! /bin/sh
    +# 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.
    +
    +###############################################################################
    +# 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 helpful 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"
    +export CFLAGS
    +export LDFLAGS
    +
    +libtoolize="libtoolize"
    +case $(uname -s) in
    + Darwin*)
    + libtoolize="glibtoolize"
    + ;;
    + *)
    +esac
    +
    +###############################################################################
    +# Some helper functions
    +###############################################################################
    +check () {
    + CMD=$1
    +
    + printf "%s" "checking for ${CMD}... "
    + BIN=`which ${CMD} 2>/dev/null`
    +
    + if [ x"${BIN}" = x"" ] ; then
    + echo "not found."
    + echo "${CMD} is required to build ${PACKAGE}!"
    + exit 1;
    + fi
    +
    + echo "${BIN}"
    +}
    +
    +run_or_die () { # beotch
    + CMD=$1
    + shift
    +
    + OUTPUT=`mktemp autogen-XXXXXX`
    +
    + printf "%s" "running ${CMD} ${@}... "
    + ${CMD} ${@} >${OUTPUT} 2>&1
    +
    + if [ $? != 0 ] ; then
    + echo "failed."
    + cat ${OUTPUT}
    + rm -f ${OUTPUT}
    + exit 1
    + else
    + echo "done."
    + cat ${OUTPUT}
    +
    + rm -f ${OUTPUT}
    + fi
    +}
    +
    +###############################################################################
    +# We really start here, yes, very sneaky!
    +###############################################################################
    +FIGLET=`which figlet 2> /dev/null`
    +if [ x"${FIGLET}" != x"" ] ; then
    + ${FIGLET} -f small ${PACKAGE}
    + echo "build system is being generated"
    +else
    + echo "autogenerating build system for '${PACKAGE}'"
    +fi
    +
    +###############################################################################
    +# Look for our args file
    +###############################################################################
    +printf "%s" "checking for ${ARGS_FILE}: "
    +if [ -f ${ARGS_FILE} ] ; then
    + echo "found."
    + printf "%s" "sourcing ${ARGS_FILE}: "
    + . "`dirname "$0"`"/${ARGS_FILE}
    + echo "done."
    +else
    + echo "not found."
    +fi
    +
    +###############################################################################
    +# 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};
    +check "python"; PYTHON=${BIN};
    +
    +###############################################################################
    +# Build pluginpack.m4
    +###############################################################################
    +CONFIG_FILE="plugin_pack.m4"
    +
    +printf "%s" "creating ${CONFIG_FILE} ..."
    +${PYTHON} plugin_pack.py config_file > ${CONFIG_FILE}
    +echo " done."
    +
    +###############################################################################
    +# Run all of our helpers
    +###############################################################################
    +run_or_die ${LIBTOOLIZE} ${LIBTOOLIZE_FLAGS:-"-c -f --automake"}
    +run_or_die ${INTLTOOLIZE} ${INTLTOOLIZE_FLAGS:-"-c -f --automake"}
    +run_or_die ${ACLOCAL} ${ACLOCAL_FLAGS}
    +run_or_die ${AUTOHEADER} ${AUTOHEADER_FLAGS}
    +run_or_die ${AUTOMAKE} ${AUTOMAKE_FLAGS:-"-a -c --gnu"}
    +run_or_die ${AUTOCONF} ${AUTOCONF_FLAGS}
    +
    +###############################################################################
    +# Run configure
    +###############################################################################
    +echo "running ./configure ${CONFIGURE_FLAGS} $@"
    +./configure ${CONFIGURE_FLAGS} $@
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,60 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 ("/purple/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("/purple/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 ("/purple/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 ("/purple/away/away_when_idle", FALSE);
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Global functions to start it all *
    + *--------------------------------------------------------------------------*/
    +void ap_autoaway_start ()
    +{
    + purple_prefs_set_bool ("/purple/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 (),
    + "/purple/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 ("/purple/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 Thu Aug 27 19:46:46 2009 -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, "/purple/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 ("/purple/away/away_when_idle"));
    +
    + /* Auto-response settings */
    + purple_prefs_add_string ("/plugins/gtk/autoprofile/autorespond/auto_reply",
    + purple_prefs_get_string ("/purple/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 Thu Aug 27 19:46:46 2009 -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 */
    +extern 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 Thu Aug 27 19:46:46 2009 -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 ("/purple/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 ("/purple/away/auto_reply", "never");
    +}
    +
    +/*--------------------------------------------------------------------------*
    + * Global functions *
    + *--------------------------------------------------------------------------*/
    +void ap_autoreply_start ()
    +{
    + purple_prefs_set_string ("/purple/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 (),
    + "/purple/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 ("/purple/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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 *);
    +extern GMarkupParser rss_parser;
    +
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/comp_rss_parser.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,351 @@
    +/*--------------------------------------------------------------------------*
    + * 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 (PurpleUtilFetchUrlData *url_data, gpointer data,
    + const char *text, size_t size, const gchar *error_message)
    +{
    + 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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,88 @@
    +/*--------------------------------------------------------------------------*
    + * 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 */
    +
    + /*
    + XXX BEFORE YOU UNCOMMENT THIS, FIX THE IDENTIFIERS in logstats
    + 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 Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,77 @@
    +/*--------------------------------------------------------------------------*
    + * 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_
    +
    +#ifdef HAVE_CONFIG_H
    +# include "../pp_config.h"
    +#endif
    +
    +#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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 ("/purple/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"),
    + "/purple/sound/while_away",
    + config_vbox);
    + sound_pref_cb = purple_prefs_connect_callback (ap_get_plugin_handle (),
    + "/purple/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 Thu Aug 27 19:46:46 2009 -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, (guchar *)s->str, strlen(s->str)+1);
    +
    + g_string_free (s, TRUE);
    + gtk_tree_path_free (source_row);
    + }
    +}
    +
    +void done_with_widget_list () {
    + if (tree_list) {
    + g_object_unref (tree_list);
    + tree_list = NULL;
    + }
    +
    + widget_dialog = NULL;
    + delete_button = NULL;
    + dialog_box = NULL;
    + dialog_box_contents = NULL;
    + dialog_box_preview = NULL;
    + dialog_box_widget = NULL;
    + if (component_dialog != NULL) {
    + gtk_widget_destroy (component_dialog);
    + component_dialog = NULL;
    + choose_button = NULL;
    + }
    +}
    +
    +GtkWidget *get_widget_list (GtkWidget *box, GtkTreeSelection **sel)
    +{
    + GtkWidget *sw;
    + GtkWidget *event_view;
    + GtkCellRenderer *rend;
    + GtkTreeViewColumn *col;
    + GtkTargetEntry gte [] = {{"STRING", 0, GTK_IMHTML_DRAG_STRING}};
    +
    + if (tree_list == NULL) {
    + tree_list = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
    + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(tree_list),
    + 0, GTK_SORT_ASCENDING);
    + update_widget_list (tree_list);
    + g_object_ref (G_OBJECT(tree_list));
    + }
    +
    + /* List of widgets */
    + sw = gtk_scrolled_window_new (NULL, NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
    + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw),
    + GTK_SHADOW_IN);
    + gtk_box_pack_start (GTK_BOX(box), sw, TRUE, TRUE, 0);
    +
    + event_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_list));
    + *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));
    +
    + rend = gtk_cell_renderer_text_new ();
    + col = gtk_tree_view_column_new_with_attributes (_("Widget"), rend,
    + "markup", 0, NULL);
    +
    + gtk_tree_view_append_column (GTK_TREE_VIEW (event_view), col);
    + gtk_tree_view_column_set_sort_column_id (col, 0);
    + gtk_container_add (GTK_CONTAINER(sw), event_view);
    +
    + /* Drag and Drop */
    + gtk_tree_view_enable_model_drag_source(
    + GTK_TREE_VIEW(event_view), GDK_BUTTON1_MASK, gte,
    + 1, GDK_ACTION_COPY);
    + g_signal_connect(G_OBJECT(event_view), "drag-data-get",
    + G_CALLBACK(drag_data_get_cb), tree_list);
    +
    + return event_view;
    +}
    +
    +/*********************************************************
    + Component selection window
    +**********************************************************/
    +
    +static void add_component (struct component *c) {
    + struct widget *w;
    + GtkTreeIter iter;
    + GString *s;
    +
    + w = ap_widget_create (c);
    +
    + if (w == NULL) return;
    +
    + s = g_string_new ("");
    +
    + gtk_list_store_append (tree_list, &iter);
    + g_string_printf (s, "<b>%s</b>", w->alias);
    +
    + gtk_list_store_set (tree_list, &iter,
    + 0, s->str,
    + 1, w,
    + -1);
    + g_string_free (s, TRUE);
    +}
    +
    +static void component_row_activate_cb (GtkTreeView *view, GtkTreePath *path,
    + GtkTreeViewColumn *column, gpointer null)
    +{
    + GtkTreeSelection *sel;
    + GtkTreeIter iter;
    + struct component *c;
    + GtkTreeModel *model;
    +
    + sel = gtk_tree_view_get_selection (view);
    +
    + if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + gtk_tree_model_get (model, &iter, 1, &c, -1);
    + add_component (c);
    + }
    +
    + gtk_widget_destroy (component_dialog);
    + component_dialog = NULL;
    + choose_button = NULL;
    +}
    +
    +static void component_response_cb(GtkWidget *d, int response,
    + GtkTreeSelection *sel)
    +{
    + GtkTreeModel *model;
    + GtkTreeIter iter;
    + GValue val;
    + struct component *c;
    +
    + switch (response) {
    + case AP_RESPONSE_CHOOSE:
    + gtk_tree_selection_get_selected (sel, &model, &iter);
    + val.g_type = 0;
    + gtk_tree_model_get_value (model, &iter, 1, &val);
    + c = g_value_get_pointer(&val);
    + add_component (c);
    + case GTK_RESPONSE_CLOSE:
    + case GTK_RESPONSE_CANCEL:
    + case GTK_RESPONSE_DELETE_EVENT:
    + gtk_widget_destroy(d);
    + component_dialog = NULL;
    + choose_button = NULL;
    + break;
    + }
    +}
    +
    +static void component_sel_cb (GtkTreeSelection *sel, GtkTreeModel *model) {
    + GtkTreeIter iter;
    +
    + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + gtk_widget_set_sensitive(choose_button, FALSE);
    + } else {
    + gtk_widget_set_sensitive(choose_button, TRUE);
    + }
    +}
    +
    +static void update_component_list (GtkListStore *ls) {
    + GtkTreeIter iter;
    + GList *components;
    + struct component *c;
    + GString *s;
    + gchar *name, *description;
    +
    + gtk_list_store_clear (ls);
    +
    + s = g_string_new ("");
    +
    + for (components = ap_component_get_components ();
    + components != NULL;
    + components = components->next) {
    + gtk_list_store_append (ls, &iter);
    + c = (struct component *) components->data;
    +
    + name = g_markup_escape_text (c->name, -1);
    + description = g_markup_escape_text (c->description, -1);
    +
    + g_string_printf (s, "<b>%s</b>\n%s", name, description);
    +
    + gtk_list_store_set (ls, &iter,
    + 0, s->str,
    + 1, c,
    + -1);
    + free (name);
    + free (description);
    + }
    +
    + g_string_free (s, TRUE);
    +}
    +
    +static void component_dialog_show ()
    +{
    + GtkWidget *sw;
    + GtkWidget *event_view;
    + GtkListStore *ls;
    + GtkCellRenderer *rendt;
    + GtkTreeViewColumn *col;
    + GtkTreeSelection *sel;
    +
    + if (component_dialog != NULL) {
    + gtk_window_present(GTK_WINDOW(component_dialog));
    + return;
    + }
    +
    + component_dialog = gtk_dialog_new_with_buttons (_("Select a widget type"),
    + NULL,
    + GTK_DIALOG_NO_SEPARATOR,
    + NULL);
    +
    + choose_button = gtk_dialog_add_button (GTK_DIALOG(component_dialog),
    + _("Create widget"), AP_RESPONSE_CHOOSE);
    + gtk_dialog_add_button (GTK_DIALOG(component_dialog),
    + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
    + gtk_widget_set_sensitive (choose_button, FALSE);
    +
    + sw = gtk_scrolled_window_new (NULL,NULL);
    + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(sw),
    + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
    + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW(sw),
    + GTK_SHADOW_IN);
    +
    + gtk_box_pack_start (GTK_BOX(GTK_DIALOG(component_dialog)->vbox), sw,
    + TRUE, TRUE, 0);
    +
    + ls = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
    + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(ls),
    + 0, GTK_SORT_ASCENDING);
    +
    + update_component_list (ls);
    +
    + event_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL(ls));
    +
    + g_signal_connect(G_OBJECT(event_view), "row-activated",
    + G_CALLBACK(component_row_activate_cb), event_view);
    +
    + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (event_view));
    +
    + rendt = gtk_cell_renderer_text_new ();
    + col = gtk_tree_view_column_new_with_attributes (_("Widget type"),
    + rendt,
    + "markup", 0,
    + NULL);
    +
    +#if GTK_CHECK_VERSION(2,6,0)
    + gtk_tree_view_column_set_expand (col, TRUE);
    + g_object_set(rendt, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
    +#endif
    + gtk_tree_view_append_column (GTK_TREE_VIEW(event_view), col);
    + gtk_tree_view_column_set_sort_column_id (col, 0);
    + g_object_unref (G_OBJECT(ls));
    + gtk_container_add (GTK_CONTAINER(sw), event_view);
    +
    + g_signal_connect (G_OBJECT (sel), "changed",
    + G_CALLBACK (component_sel_cb), NULL);
    + g_signal_connect(G_OBJECT(component_dialog), "response",
    + G_CALLBACK(component_response_cb), sel);
    + gtk_window_set_default_size(GTK_WINDOW(component_dialog), 550, 430);
    + gtk_widget_show_all(component_dialog);
    +}
    +
    +/* Preferences stuff */
    +static void pref_callback (const char *name, PurplePrefType type,
    + gconstpointer val, gpointer data) {
    + struct widget *w = (struct widget *) data;
    + ap_widget_prefs_updated (w);
    +}
    +
    +static const gchar *get_const_pref (struct widget *w, const char *key) {
    + gchar *pref, *result;
    + // This is here to prevent memory leaks
    +
    + pref = ap_prefs_get_pref_name (w, key);
    + if (pref_names == NULL) {
    + pref_names = g_hash_table_new (g_str_hash, g_str_equal);
    + }
    +
    + result = g_hash_table_lookup (pref_names, pref);
    +
    + if (!result) {
    + g_hash_table_insert (pref_names, pref, pref);
    + return pref;
    + } else {
    + free (pref);
    + return result;
    + }
    +}
    +
    +GtkWidget *ap_prefs_checkbox (struct widget *w, const char *title,
    + const char *key, GtkWidget *page)
    +{
    + GtkWidget *result;
    + const gchar *pref;
    +
    + pref = get_const_pref (w, key);
    + result = pidgin_prefs_checkbox (title, pref, page);
    + purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    + pref_callback, w);
    +
    + return result;
    +}
    +
    +GtkWidget *ap_prefs_dropdown_from_list (struct widget *w, GtkWidget *page,
    + const gchar *title, PurplePrefType type, const char *key, GList *menuitems)
    +{
    + GtkWidget *result;
    + const gchar *pref;
    +
    + pref = get_const_pref (w, key);
    + result = pidgin_prefs_dropdown_from_list (
    + page, title, type, pref, menuitems);
    + purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    + pref_callback, w);
    +
    + return result;
    +}
    +
    +GtkWidget *ap_prefs_labeled_entry (struct widget *w, GtkWidget *page,
    + const gchar *title, const char *key, GtkSizeGroup *sg) {
    + GtkWidget *result;
    + const gchar *pref;
    +
    + pref = get_const_pref (w, key);
    + result = pidgin_prefs_labeled_entry (page, title, pref, sg);
    + purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    + pref_callback, w);
    +
    + return result;
    +}
    +
    +GtkWidget *ap_prefs_labeled_spin_button (struct widget *w,
    + GtkWidget *page, const gchar *title, const char *key, int min,
    + int max, GtkSizeGroup *sg)
    +{
    + GtkWidget *result;
    + const gchar *pref;
    +
    + pref = get_const_pref (w, key);
    + result = pidgin_prefs_labeled_spin_button (page, title, pref,
    + min, max, sg);
    + purple_prefs_connect_callback (ap_get_plugin_handle (), pref,
    + pref_callback, w);
    +
    + return result;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[AutoProfile]
    +type=default
    +depends=pidgin
    +provides=autoprofile
    +summary=User profile and status message content generator
    +description=Allows user to place dynamic text into profiles and status messages, with the text automatically updated whenever content changes
    +authors=Casey Ho
    +introduced=2.4.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoprofile/preferences.c Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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", 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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,189 @@
    +/*--------------------------------------------------------------------------*
    + * 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);
    +
    +#ifndef __BSD_VISIBLE
    + if (rfc_parse_was_gmt ())
    + result -= timezone;
    +#endif
    +
    + 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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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_ */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoreply/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +autoreplydir = $(PURPLE_LIBDIR)
    +
    +autoreply_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +autoreply_LTLIBRARIES = autoreply.la
    +
    +autoreply_la_SOURCES = \
    + autoreply.c
    +
    +autoreply_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/autoreply/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for dice plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = autoreply
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoreply/autoreply.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,506 @@
    +/*
    + * Autoreply - Autoreply feature for all the protocols
    + * 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
    + * 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"
    +
    +#define PLUGIN_ID "core-plugin_pack-autoreply"
    +#define PLUGIN_STATIC_NAME "Autoreply"
    +#define PLUGIN_AUTHOR "Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>"
    +
    +/* Purple headers */
    +#include <account.h>
    +#include <accountopt.h>
    +#include <blist.h>
    +#include <conversation.h>
    +#include <plugin.h>
    +#include <pluginpref.h>
    +#include <request.h>
    +#include <savedstatuses.h>
    +#include <status.h>
    +#include <util.h>
    +
    +#define PREFS_PREFIX "/plugins/core/" PLUGIN_ID
    +#define PREFS_IDLE PREFS_PREFIX "/idle"
    +#define PREFS_AWAY PREFS_PREFIX "/away"
    +#define PREFS_GLOBAL PREFS_PREFIX "/global"
    +#define PREFS_MINTIME PREFS_PREFIX "/mintime"
    +#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;
    +
    +struct _PurpleAutoReply
    +{
    + PurpleBuddy *buddy;
    + char *reply;
    +};
    +
    +struct _AutoReplyProtocolOptions {
    + PurpleAccountOption *message;
    + PurpleAccountOption *off;
    +};
    +
    +typedef enum
    +{
    + STATUS_NEVER,
    + STATUS_ALWAYS,
    + STATUS_FALLBACK
    +} UseStatusMessage;
    +
    +static GHashTable *options = NULL;
    +
    +/**
    + * Returns the auto-reply message for buddy
    + */
    +static const char *
    +get_autoreply_message(PurpleBuddy *buddy, PurpleAccount *account)
    +{
    + const char *reply = NULL;
    + UseStatusMessage use_status;
    +
    + use_status = purple_prefs_get_int(PREFS_USESTATUS);
    + if (use_status == STATUS_ALWAYS)
    + {
    + PurpleStatus *status = purple_account_get_active_status(account);
    + PurpleStatusType *type = purple_status_get_type(status);
    + if (purple_status_type_get_attr(type, "message") != NULL)
    + reply = purple_status_get_attr_string(status, "message");
    + else
    + reply = purple_savedstatus_get_message(purple_savedstatus_get_current());
    + }
    +
    + if ((!reply || !*reply) && buddy)
    + {
    + /* Is there any special auto-reply for this buddy? */
    + reply = purple_blist_node_get_string((PurpleBlistNode*)buddy, "autoreply");
    +
    + 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 || !*reply)
    + {
    + /* Is there any specific auto-reply for this account? */
    + reply = purple_account_get_string(account, "autoreply", NULL);
    + }
    +
    + if (!reply || !*reply)
    + {
    + /* Get the global auto-reply message */
    + reply = purple_prefs_get_string(PREFS_GLOBAL);
    + }
    +
    + if (*reply == ' ' || *reply == '\0')
    + reply = NULL;
    +
    + if (!reply && use_status == STATUS_FALLBACK)
    + reply = purple_status_get_attr_string(purple_account_get_active_status(account), "message");
    +
    + return reply;
    +}
    +
    +static void
    +written_msg(PurpleAccount *account, const char *who, const char *message,
    + PurpleConversation *conv, PurpleMessageFlags flags, gpointer null)
    +{
    + PurpleBuddy *buddy;
    + PurplePresence *presence;
    + const char *reply = NULL;
    + gboolean trigger = FALSE;
    +
    + if (!(flags & PURPLE_MESSAGE_RECV))
    + return;
    +
    + if (!message || !*message)
    + return;
    +
    + /* 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;
    +
    + buddy = purple_find_buddy(account, who);
    + reply = get_autoreply_message(buddy, account);
    +
    + if (reply)
    + {
    + PurpleConnection *gc;
    + PurpleMessageFlags flag = PURPLE_MESSAGE_SEND;
    + time_t last_sent, now;
    + int count_sent, maxsend;
    + char *send = NULL;
    + const char *prefix;
    +
    + last_sent = GPOINTER_TO_INT(purple_conversation_get_data(conv, "autoreply_lastsent"));
    + now = time(NULL);
    +
    + /* Have we spent enough time after our last autoreply? */
    + if (now - last_sent >= (purple_prefs_get_int(PREFS_MINTIME)*60))
    + {
    + count_sent = GPOINTER_TO_INT(purple_conversation_get_data(conv, "autoreply_count"));
    + maxsend = purple_prefs_get_int(PREFS_MAXSEND);
    +
    + /* Have we sent the autoreply enough times? */
    + if (count_sent < maxsend || maxsend == -1)
    + {
    + purple_conversation_set_data(conv, "autoreply_count", GINT_TO_POINTER(++count_sent));
    + purple_conversation_set_data(conv, "autoreply_lastsent", GINT_TO_POINTER(now));
    + gc = purple_account_get_connection(account);
    + prefix = purple_prefs_get_string(PREFS_PREFIX_MSG);
    + if (gc->flags & PURPLE_CONNECTION_AUTO_RESP) {
    + flag |= PURPLE_MESSAGE_AUTO_RESP;
    + prefix = NULL; /* The prpl knows about auto-response. So ignore the prefix string. */
    + }
    + send = g_strdup_printf("%s%s", prefix ? prefix : "", reply);
    + purple_conv_im_send_with_flags(PURPLE_CONV_IM(conv), send, flag);
    + g_free(send);
    + }
    + }
    + }
    +}
    +
    +static void
    +set_auto_reply_cb(PurpleBlistNode *node, char *message)
    +{
    + if (!message || !*message)
    + message = " ";
    + purple_blist_node_set_string(node, "autoreply", message);
    +}
    +
    +static void
    +set_auto_reply(PurpleBlistNode *node, gpointer plugin)
    +{
    + char *message;
    + PurpleBuddy *buddy;
    + PurpleAccount *account;
    + PurpleConnection *gc;
    +
    + if (PURPLE_BLIST_NODE_IS_BUDDY(node))
    + buddy = (PurpleBuddy *)node;
    + else
    + buddy = purple_contact_get_priority_buddy((PurpleContact*)node);
    +
    + account = purple_buddy_get_account(buddy);
    + gc = purple_account_get_connection(account);
    +
    + /* XXX: There should be a way to reset to the default/account-default autoreply */
    +
    + message = g_strdup_printf(_("Set autoreply message for %s"),
    + purple_buddy_get_contact_alias(buddy));
    + purple_request_input(plugin, _("Set Autoreply Message"), message,
    + _("The following message will be sent to the buddy when "
    + "the buddy sends you a message and autoreply is enabled."),
    + get_autoreply_message(buddy, account), TRUE, FALSE,
    + (gc->flags & PURPLE_CONNECTION_HTML) ? "html" : NULL,
    + _("_Save"), G_CALLBACK(set_auto_reply_cb),
    + _("_Cancel"), NULL,
    + account, purple_buddy_get_name(buddy), NULL, node);
    + g_free(message);
    +}
    +
    +static void
    +context_menu(PurpleBlistNode *node, GList **menu, gpointer plugin)
    +{
    + PurpleMenuAction *action;
    +
    + if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
    + return;
    +
    + if (!PURPLE_BLIST_NODE_IS_BUDDY(node) && !PURPLE_BLIST_NODE_IS_CONTACT(node))
    + return;
    +
    + action = purple_menu_action_new(_("Set _Autoreply Message"),
    + PURPLE_CALLBACK(set_auto_reply), plugin, NULL);
    + (*menu) = g_list_prepend(*menu, action);
    +}
    +
    +static void
    +add_options_for_protocol(PurplePlugin *plg)
    +{
    + 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);
    +}
    +
    +static void
    +remove_options_for_protocol(PurplePlugin *plg)
    +{
    + PurplePluginProtocolInfo *info = PURPLE_PLUGIN_PROTOCOL_INFO(plg);
    + AutoReplyProtocolOptions *arpo = g_hash_table_lookup(options, plg);
    + GList *l = NULL;
    +
    + 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
    + */
    + if ((l = g_list_find(info->protocol_options, arpo->message)))
    + {
    + info->protocol_options = g_list_remove_link(info->protocol_options, l);
    + purple_account_option_destroy(arpo->message);
    + }
    + 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);
    +}
    +
    +static void
    +plugin_load_cb(PurplePlugin *plugin, gboolean load)
    +{
    + if (plugin->info && plugin->info->type == PURPLE_PLUGIN_PROTOCOL)
    + {
    + if (load)
    + add_options_for_protocol(plugin);
    + else
    + remove_options_for_protocol(plugin);
    + }
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + GList *list;
    +
    + purple_signal_connect(purple_conversations_get_handle(), "wrote-im-msg", plugin,
    + PURPLE_CALLBACK(written_msg), NULL);
    + purple_signal_connect(purple_blist_get_handle(), "blist-node-extended-menu", plugin,
    + PURPLE_CALLBACK(context_menu), plugin);
    + purple_signal_connect(purple_plugins_get_handle(), "plugin-load", plugin,
    + PURPLE_CALLBACK(plugin_load_cb), GINT_TO_POINTER(TRUE));
    + purple_signal_connect(purple_plugins_get_handle(), "plugin-unload", plugin,
    + PURPLE_CALLBACK(plugin_load_cb), GINT_TO_POINTER(FALSE));
    +
    + /* Perhaps it's necessary to do this after making sure the prpl-s have been loaded? */
    + options = g_hash_table_new(g_direct_hash, g_direct_equal);
    + list = purple_plugins_get_protocols();
    + while (list)
    + {
    + add_options_for_protocol(list->data);
    + list = list->next;
    + }
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + GList *list;
    +
    + if (options == NULL)
    + return TRUE;
    +
    + list = purple_plugins_get_protocols();
    + while (list)
    + {
    + remove_options_for_protocol(list->data);
    + list = list->next;
    + }
    + g_hash_table_destroy(options);
    + options = NULL;
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_label(_("Send autoreply messages when"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_AWAY,
    + _("When my account is _away"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_IDLE,
    + _("When my account is _idle"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_GLOBAL,
    + _("_Default reply"));
    + purple_plugin_pref_set_type(pref, PURPLE_PLUGIN_PREF_STRING_FORMAT);
    + purple_plugin_pref_set_format_type(pref,
    + PURPLE_STRING_FORMAT_TYPE_MULTILINE | PURPLE_STRING_FORMAT_TYPE_HTML);
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_PREFIX_MSG,
    + _("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"),
    + GINT_TO_POINTER(STATUS_NEVER));
    + purple_plugin_pref_add_choice(pref, _("Always when there is a status message"),
    + GINT_TO_POINTER(STATUS_ALWAYS));
    + purple_plugin_pref_add_choice(pref, _("Only when there's no autoreply message"),
    + GINT_TO_POINTER(STATUS_FALLBACK));
    +
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_label(_("Delay between autoreplies"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_MINTIME,
    + _("_Minimum delay (mins)"));
    + purple_plugin_pref_set_bounds(pref, 0, 9999);
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_label(_("Times to send autoreplies"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREFS_MAXSEND,
    + _("Ma_ximum count"));
    + purple_plugin_pref_set_bounds(pref, 0, 9999);
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +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 */
    + &prefs_info, /* 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 = _("Autoreply");
    + info.summary = _("Autoreply for all the protocols");
    + info.description = _("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 specific autoreply message for "
    + "a particular buddy, right click on the buddy in the buddy-list "
    + "window. To set autoreply messages for some accounts, go to the "
    + "`Advanced' tab of the account edit dialog.");
    +
    + purple_prefs_add_none(PREFS_PREFIX);
    + purple_prefs_add_bool(PREFS_IDLE, TRUE);
    + purple_prefs_add_bool(PREFS_AWAY, TRUE);
    + purple_prefs_add_string(PREFS_GLOBAL, _("I am currently not available. Please leave your message, "
    + "and I will get back to you as soon as possible."));
    + purple_prefs_add_int(PREFS_MINTIME, 10);
    + 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)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/autoreply/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Autoreply]
    +type=default
    +depends=purple
    +provides=autoreply
    +summary=Autoreply for all the protocols
    +description=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 specific autoreply message for a particular buddy, right click on the buddy in the buddy-list window. To set autoreply messages for some accounts, go to the `Advanced' tab of the account edit dialog.
    +authors=Sadrul Habib Chowdhury
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/awaynotify/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,25 @@
    +EXTRA_DIST = \
    + plugins.cfg
    +
    +plugindir=$(PURPLE_LIBDIR)
    +
    +awaynotify_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +plugin_LTLIBRARIES = awaynotify.la
    +
    +awaynotify_la_SOURCES = awaynotify.c
    +
    +awaynotify_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + $(GLIB_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/awaynotify/awaynotify.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,268 @@
    +/*
    + * awaynotify - show notices when status changes
    + * 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
    + * 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 <blist.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <signals.h>
    +
    +#include <plugin.h>
    +#include <pluginpref.h>
    +#include <prefs.h>
    +
    +#include <string.h>
    +
    +#define PLUGIN_ID "core-plugin_pack-awaynotify"
    +#define CHECK_AWAY_MESSAGE_TIME_MS 1000
    +
    +typedef struct _Infochecker Infochecker;
    +
    +struct _Infochecker {
    + PurpleAccount *account;
    + char *buddy;
    + guint timeout_id;
    +};
    +
    +GList* infochecker_list = NULL;
    +
    +static gint infocheck_timeout(gpointer data);
    +
    +static Infochecker* infocheck_new(PurpleAccount* account, char* buddy)
    +{
    + Infochecker* checker = g_new0(Infochecker, 1);
    + checker->account = account;
    + checker->buddy = g_strdup(buddy);
    +
    + return checker;
    +}
    +
    +static void infocheck_delete(Infochecker* checker)
    +{
    + g_free(checker->buddy);
    + g_free(checker);
    +}
    +
    +static void infocheck_remove(GList* node)
    +{
    + Infochecker* checker = (Infochecker*)node->data;
    +
    + g_source_remove(checker->timeout_id);
    + infochecker_list = g_list_remove_link(infochecker_list, node);
    +
    + infocheck_delete(checker);
    +}
    +
    +static gint infocheck_compare(gconstpointer pa, gconstpointer pb)
    +{
    + Infochecker* a = (Infochecker*)pa;
    + Infochecker* b = (Infochecker*)pb;
    +
    + return (a->account == b->account) ? strcmp(a->buddy, b->buddy) : 1;
    +}
    +
    +
    +static void write_status(PurpleBuddy *buddy, const char *message, const char* status)
    +{
    + PurpleConversation *conv;
    + const char *who;
    + char buf[256];
    + char *escaped;
    +
    + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, buddy->account);
    +
    + if (conv == NULL)
    + return;
    +
    + who = purple_buddy_get_alias(buddy);
    + escaped = g_markup_escape_text(who, -1);
    +
    + g_snprintf(buf, sizeof(buf), message, escaped, status);
    + g_free(escaped);
    +
    + purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM, time(NULL));
    +}
    +
    +static char* parse_away_message(char* statustext)
    +{
    + char* away_ptr = strstr(statustext, "Away Message:");
    +
    + if (away_ptr == NULL)
    + return g_strdup("");
    +
    + away_ptr += 4 + 1 + 7 + 1;
    + if (*away_ptr == '<') {
    + char* tmp = strchr(away_ptr, '>');
    + if (tmp) away_ptr = tmp + 1;
    + }
    +
    + while (*away_ptr == ' ') away_ptr++;
    +
    + return g_strdup(away_ptr);
    +}
    +
    +static char* get_away_message(PurpleBuddy* buddy)
    +{
    + PurplePlugin *prpl;
    + PurplePluginProtocolInfo *prpl_info = NULL;
    + char* statustext = NULL;
    +
    + if (buddy == NULL)
    + return NULL;
    +
    + prpl = purple_find_prpl(purple_account_get_protocol_id(buddy->account));
    + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
    +
    + if (prpl_info && prpl_info->tooltip_text) {
    + const char *end;
    + char *statustext = NULL;
    + PurpleNotifyUserInfo *info = purple_notify_user_info_new();
    + prpl_info->tooltip_text(buddy, info, TRUE);
    + statustext = purple_notify_user_info_get_text_with_newline(info, "\n");
    + purple_notify_user_info_destroy(info);
    +
    + if (statustext && !g_utf8_validate(statustext, -1, &end)) {
    + char *new = g_strndup(statustext, g_utf8_pointer_to_offset(statustext, end));
    + g_free(statustext);
    + statustext = new;
    + }
    + }
    +
    + if (statustext) {
    + char* away_message = parse_away_message(statustext);
    + g_free(statustext);
    + return away_message;
    + } else
    + return NULL;
    +}
    +
    +static gint infocheck_timeout(gpointer data)
    +{
    + GList* node = (GList*)data;
    + Infochecker* checker = node ? (Infochecker*)node->data : NULL;
    + PurpleBuddy* buddy;
    + char* away_message;
    +
    + if (node == NULL || checker == NULL) {
    + purple_debug_warning("awaynotify", "checker called without being active!\n");
    + return FALSE;
    + }
    +
    + buddy = purple_find_buddy(checker->account, checker->buddy);
    + away_message = get_away_message(buddy);
    +
    + if (away_message == NULL) {
    + /* He must have signed off or there was some other error. Give up. */
    + infocheck_remove(node);
    + return FALSE;
    + }
    +
    + if (away_message[0] == 0) {
    + /* Not away yet. Return true to try again. */
    + g_free(away_message);
    + return TRUE;
    + }
    +
    + write_status(buddy, _("%s is away: %s"), away_message);
    +
    + g_free(away_message);
    + infocheck_remove(node);
    +
    + return FALSE;
    +}
    +
    +static void infocheck_add(Infochecker* checker)
    +{
    + infochecker_list = g_list_prepend(infochecker_list, checker);
    + checker->timeout_id = g_timeout_add(CHECK_AWAY_MESSAGE_TIME_MS,
    + infocheck_timeout, g_list_first(infochecker_list));
    +}
    +
    +static void buddy_away_cb(PurpleBuddy *buddy, void *data)
    +{
    + if (purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, buddy->name, buddy->account) == NULL)
    + return; /* Ignore if there's no conv open. */
    +
    + infocheck_add(infocheck_new(buddy->account, buddy->name));
    +}
    +
    +static void buddy_unaway_cb(PurpleBuddy *buddy, void *data)
    +{
    + GList* node = g_list_find_custom(infochecker_list, buddy->name, infocheck_compare);
    +
    + if (node)
    + infocheck_remove(node);
    +
    + write_status(buddy, _("%s is no longer away."), NULL);
    +}
    +
    +static gboolean plugin_load(PurplePlugin *plugin)
    +{
    + void *blist_handle = purple_blist_get_handle();
    +
    + purple_signal_connect(blist_handle, "buddy-away",
    + plugin, PURPLE_CALLBACK(buddy_away_cb), NULL);
    + purple_signal_connect(blist_handle, "buddy-back",
    + plugin, PURPLE_CALLBACK(buddy_unaway_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + 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 */
    + "Matt Perry <guy@somewhere.fscked.org>", /**< author */
    + PP_WEBSITE, /**< homepage */
    +
    + plugin_load, /**< load */
    + NULL, /**< unload */
    + NULL, /**< destroy */
    +
    + NULL, /**< ui_info */
    + NULL, /**< extra_info */
    + NULL, /**< prefs_info */
    + NULL
    +};
    +
    +static void
    +init_plugin(PurplePlugin *plugin)
    +{
    + info.name = _("Away State Notification");
    + info.summary =
    + _("Notifies in a conversation window when a buddy goes or returns from away");
    + info.description = info.summary;
    +}
    +
    +PURPLE_INIT_PLUGIN(statenotify, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/awaynotify/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Away State Notification]
    +type=incomplete
    +depends=purple
    +provides=awaynotify
    +summary=Shows when someone goes away/back in a conversation
    +description=%(summary)s
    +authors=Matt Perry
    +introduced=1.0beta6
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bash/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +bashdir = $(PURPLE_LIBDIR)
    +
    +bash_la_LDFLAGS = -module -avoid-version
    +
    +bash_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +if HAVE_PURPLE
    +
    +bash_LTLIBRARIES = bash.la
    +
    +bash_la_SOURCES = bash.c
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS) \
    + $(GLIB_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bash/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for bash plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = bash
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bash/bash.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,171 @@
    +/*
    + * Purple Plugin Pack
    + * 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
    + * bash.org quote.
    + *
    + * 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"
    +
    +/* libc */
    +#include <ctype.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <time.h>
    +
    +/* Purple */
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <plugin.h>
    +
    +#define BASH_QUOTES 881844
    +#define QDB_QUOTES 294961
    +
    +static PurpleCmdId bash, qdb;
    +
    +static PurpleCmdRet
    +cmd_func(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar *error, void *data)
    +{
    + GString *msgstr = NULL;
    + guint32 quotes = 0, quoteid = 0;
    +
    + msgstr = g_string_new("");
    +
    + srand(time(NULL));
    +
    + if(!strcmp(cmd, "bash")) {
    + g_string_append(msgstr, "http://www.bash.org/?");
    + quotes = BASH_QUOTES;
    + } else {
    + g_string_append(msgstr, "http://qdb.us/");
    + quotes = QDB_QUOTES;
    + }
    +
    + if(*args == NULL || args[0] == NULL)
    + quoteid = (rand() % quotes) + 1;
    + else
    + quoteid = atoi(args[0]);
    +
    + if(quoteid > quotes)
    + quoteid %= quotes;
    +
    + g_string_append_printf(msgstr, "%i", quoteid);
    +
    + switch(purple_conversation_get_type(conv)) {
    + case PURPLE_CONV_TYPE_IM:
    + purple_conv_im_send(PURPLE_CONV_IM(conv), msgstr->str);
    + break;
    + case PURPLE_CONV_TYPE_CHAT:
    + purple_conv_chat_send(PURPLE_CONV_CHAT(conv), msgstr->str);
    + break;
    + default:
    + g_string_free(msgstr, TRUE);
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + g_string_free(msgstr, TRUE);
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + const gchar *bash_help, *qdb_help;
    + PurpleCmdFlag flags = PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT |
    + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS;
    +
    + bash_help = _("bash [n]: sends a link to a bash.org quote. Specify a"
    + " number for n and it will send a link to the quote with the"
    + " specified number.");
    +
    + qdb_help = _("qdb [n]: sends a link to a qdb.us quote. Specify a number "
    + "for n and it will send a link to the quite with the specified "
    + "number.");
    +
    + bash = purple_cmd_register("bash", "w", PURPLE_CMD_P_PLUGIN, flags, NULL,
    + PURPLE_CMD_FUNC(cmd_func), bash_help, NULL);
    +
    + qdb = purple_cmd_register("qdb", "w", PURPLE_CMD_P_PLUGIN, flags, NULL,
    + PURPLE_CMD_FUNC(cmd_func), qdb_help, NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + purple_cmd_unregister(bash);
    + purple_cmd_unregister(qdb);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo bash_info =
    +{
    + PURPLE_PLUGIN_MAGIC, /* magic, my ass */
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + "core-plugin_pack-bash",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "John Bailey <rekkanoryo@rekkanoryo.org>",
    + PP_WEBSITE,
    + plugin_load,
    + plugin_unload,
    + 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 */
    +
    + bash_info.name = _("bash.org");
    + bash_info.summary =
    + _("Generates links for quotes at bash.org");
    + bash_info.description =
    + _("Generates links for quotes at bash.org or allows the "
    + "user to specify a quote. Provides the /bash command.");
    +
    + return;
    +}
    +
    +PURPLE_INIT_PLUGIN(bash, init_plugin, bash_info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bash/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[bash.org]
    +type=default
    +depends=purple
    +provides=bash
    +summary=Generates links for quotes at bash.org
    +description=Generates links for quotes at bash.org or allows the user to specify a quote. Provides the /bash command.
    +authors=John Bailey
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bit/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +bitdir = $(PIDGIN_LIBDIR)
    +
    +bit_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +bit_LTLIBRARIES = bit.la
    +
    +bit_la_SOURCES = \
    + bit.c
    +
    +bit_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bit/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for bit plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = bit
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bit/bit.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,241 @@
    +/*
    + * 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
    + * 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 <buddyicon.h>
    +#include <debug.h>
    +#include <notify.h>
    +#include <plugin.h>
    +#include <request.h>
    +
    +PurplePlugin *bit = NULL; /* the request api prefers this for a plugin */
    +static PurpleBuddyList *buddies = NULL;
    +
    +/* TODO: Add a function to clear unused icons */
    +/* TODO: Ensure all this stuff I have at the moment is safe for others to use */
    +
    +static void
    +blist_iterate_action(gboolean remove)
    +{
    + PurpleBlistNode *node = NULL;
    + PurpleConversation *conv = NULL;
    + gint n;
    +
    + /* this grabs the purple buddy list, which will be walked through */
    + buddies = purple_get_blist();
    +
    + /* Use the utility function to loop over the nodes of the tree */
    + for (node = buddies->root; node && PURPLE_BLIST_NODE_IS_BUDDY(node);
    + node = purple_blist_node_next(node, TRUE)) {
    + PurpleBuddy *buddy = (PurpleBuddy *)node;
    + const char *tmpname = purple_buddy_get_name(buddy);
    + PurpleBuddyIcon *icon = purple_buddy_get_icon(buddy);
    + if (icon != NULL) {
    +#if 0
    + purple_debug_info("bit", "Processing %s (%p)\n", tmpname, icon);
    + if (!icon->ref_count > 0 && remove == TRUE) {
    + for ( n = icon->ref_count; n !=0; n-- ) {
    + purple_debug_info("bit", "ref_count: %d\n", n);
    + purple_buddy_icon_unref(icon);
    + }
    + }
    +#endif
    + /* XXX: This *may* cause a segfault. - Sadrul */
    + if (remove == TRUE) {
    + purple_debug_info("bit", "Uncaching icon for %s\n", tmpname);
    +#if 0
    + /* XXX: The new buddy icon API doesn't have purple_buddy_icon_uncache() */
    + purple_buddy_icon_uncache(buddy);
    +#endif
    + /* XXX: This *definately* causes a segfault. From reading the
    + * source, I may not need to unref but just straight destroy it
    + * haven't played/investigated enough to decide if I want to
    + * keep/move/delete this - Bleeter
    +
    + purple_debug_info("bit", "Destroying icon for %s\n", tmpname);
    + purple_buddy_icon_destroy(icon);*/
    + }
    + } else {
    + if (remove == TRUE)
    + purple_debug_info("bit", "No icon to flush for %s\n", tmpname);
    + }
    +
    + if (purple_account_is_connected(purple_buddy_get_account(buddy))) {
    + purple_debug_info("bit", "Updating icon for %s\n",
    + tmpname);
    + purple_blist_update_buddy_icon(buddy);
    + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
    + tmpname,purple_buddy_get_account(buddy));
    + if (conv != NULL)
    + purple_conversation_update(conv, PURPLE_CONV_UPDATE_ICON);
    + }
    + }
    +}
    +
    +static void
    +flush_buddy_icon_action(PurplePluginAction *action)
    +{
    + blist_iterate_action(TRUE);
    +}
    +
    +static void
    +refresh_buddy_icon_action(PurplePluginAction *action)
    +{
    + blist_iterate_action(FALSE);
    +}
    +
    +#if 0
    +static void
    +destroy_unused_icons_action(PurplePluginAction *action)
    +{
    + GDir *dir;
    + GList *l;
    + const char *path, *filename;
    + const char *type;
    +
    + path = purple_buddy_icons_get_cache_dir();
    + if (path == NULL) {
    + gchar *str;
    + str = g_strdup_printf(_("Unable to locate the buddy icon cache directory %s"), path);
    + purple_debug_error("bit", str);
    + purple_notify_error(bit, _("Destroy Unused Icons"), _("Unable to locate"),
    + str);
    + return;
    + }
    +
    + if (!(dir = g_dir_open(path, 0, NULL))) {
    + gchar *str;
    + str = g_strdup_printf(_("Unable to read the buddy icon cache directory %s"), path);
    + purple_debug_error("bit", str);
    + purple_notify_error(bit, _("Destroy Unused Icons"), _("Unable to read"),
    + str);
    + return;
    + }
    +
    + while ((filename = g_dir_read_name(dir))) {
    +
    + PurpleBlistNode *cur_node = NULL;
    + PurpleConversation *conv = NULL;
    + gint n;
    +
    + buddies = purple_get_blist();
    + for (cur_node = buddies->root; cur_node;
    + cur_node = purple_blist_node_next(cur_node, TRUE)) {
    + if(PURPLE_BLIST_NODE_IS_BUDDY(cur_node)) {
    + PurpleBuddy *buddy = (PurpleBuddy *)cur_node;
    + const char *tmpname = purple_buddy_get_name(buddy);
    + PurpleBuddyIcon *icon = purple_buddy_get_icon(buddy);
    + if (icon != NULL) {
    + /* store each found icon FILENAME into *l */
    + /* checksums are done in prpl, so don't bother trying */
    + }
    + }
    + }
    + /* remove files not in SOMWHERE*/
    + purple_debug_info("bit", "Filename %s\n", filename);
    + type = purple_buddy_icon_get_type(filename);
    + purple_debug_info("bit", "Type %s\n", type);
    + }
    +}
    +#endif
    +
    +static GList *
    +bit_actions(PurplePlugin *plugin, gpointer context)
    +{
    + GList *list = NULL;
    + PurplePluginAction *act = NULL;
    +
    +#if 0
    +/* buddy icon structs currently suck, I think
    + it's impossible to tell from a filename which buddy it's associated with
    + without going through every file, and the blist...
    + ... a huge hash type table *may help*, but I'd consider it highly inefficient
    + then again, some of the stuff in here ain't exactly a TGV either */
    +
    + act = purple_plugin_action_new(_("Destroy Unused Icons"),
    + destroy_unused_icons_action);
    + list = g_list_append(list, act);
    +#endif
    + act = purple_plugin_action_new(_("Flush Buddy Icons"),
    + flush_buddy_icon_action);
    + list = g_list_append(list, act);
    +
    + act = purple_plugin_action_new(_("Refresh Buddy Icons"),
    + refresh_buddy_icon_action);
    + list = g_list_append(list, act);
    +
    + purple_debug_info("bit", "Action list created\n");
    +
    + return list;
    +}
    +
    +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 */
    +
    + "core-plugin_pack-bit", /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /** summary */
    + NULL, /** description */
    + "Peter Lawler <bleeter from users.sf.net>",
    + /**< authors */
    + PP_WEBSITE, /**< homepage */
    +
    + NULL, /**< load */
    + NULL, /**< unload */
    + NULL, /**< destroy */
    +
    + NULL, /**< ui_info */
    + NULL, /**< extra_info */
    + NULL, /**< prefs_info */
    + bit_actions, /**< 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
    + info.name = _("Buddy Icon Tools");
    + info.summary = _("Tools to manipulate buddy icons. *DANGEROUS*");
    + info.description = _("Whilst working on Purple 2.0.0, I found a need to "
    + "destroy all my buddies' buddy icons. There's nothing to do "
    + "these functions in Purple, so here they are. Completely, "
    + "thoroughly untested.");
    +
    + bit = plugin; /* handle needed for request API file selector */
    +}
    +
    +PURPLE_INIT_PLUGIN(bit, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/bit/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Buddy Icon Tools]
    +type=incomplete
    +depends=pidgin
    +provides=bit
    +summary=Tools to manipulate buddy icons *DANGEROUS*
    +description=Whilst working on Purple 2.0.0, I found a need to destroy all my buddies' buddy icons. There's nothing to do these functions in Purple, so here they are. Completely, thoroughly untested.
    +authors=Peter Lawler
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/blistops/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +blistopsdir = $(PIDGIN_LIBDIR)
    +
    +blistops_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +blistops_LTLIBRARIES = blistops.la
    +
    +blistops_la_SOURCES = \
    + blistops.c
    +
    +blistops_la_LIBADD = \
    + $(GTK_LIBS) \
    + $(PIDGIN_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS) \
    + $(GTK_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/blistops/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for blistops plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = blistops
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/blistops/blistops.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,311 @@
    +/*
    + * Hides the blist on signon (or when it's created)
    + * 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
    + * 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 <gdk/gdk.h>
    +#include <gtk/gtk.h>
    +
    +#include <gtkplugin.h>
    +#include <gtkblist.h>
    +#include <pluginpref.h>
    +#include <prefs.h>
    +
    +#include <string.h>
    +
    +#define PREF_MY "/plugins/gtk/amc_grim"
    +#define PREF_ROOT "/plugins/gtk/amc_grim/blistops"
    +#define PREF_LIST "/plugins/gtk/amc_grim/blistops/hidelist"
    +#define PREF_MENU "/plugins/gtk/amc_grim/blistops/hidemenu"
    +#define PREF_STRETCH "/plugins/gtk/amc_grim/blistops/stretch"
    +#define PREF_EMAIL "/plugins/gtk/amc_grim/blistops/email"
    +
    +static GtkWidget *w_blist = NULL;
    +static GtkWidget *w_menubar = NULL;
    +
    +static void
    +abracadabra(GtkWidget *w, gboolean v)
    +{
    + if(v)
    + gtk_widget_hide(w);
    + else
    + gtk_widget_show(w);
    +}
    +
    +static gboolean
    +motion_notify_cb(GtkTreeView *view, GdkEventMotion *event, GtkRequisition *req)
    +{
    + if (event->y < req->height) {
    + gtk_widget_show(w_menubar);
    + } else {
    + gtk_widget_hide(w_menubar);
    + }
    +
    + return FALSE;
    +}
    +
    +static void
    +pref_cb(const char *name, PurplePrefType type,
    + gconstpointer val, gpointer data)
    +{
    + static GtkRequisition req;
    + gboolean value = GPOINTER_TO_INT(val);
    +
    + if(!g_ascii_strcasecmp(name, PREF_LIST))
    + abracadabra(w_blist, value);
    + else if(!g_ascii_strcasecmp(name, PREF_MENU)) {
    + PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
    + if (!value)
    + g_signal_handlers_disconnect_matched(G_OBJECT(gtkblist->treeview), G_SIGNAL_MATCH_FUNC,
    + 0, 0, NULL, G_CALLBACK(motion_notify_cb), NULL);
    + else {
    + gtk_widget_show(w_menubar);
    + gtk_widget_size_request(w_menubar, &req);
    + g_signal_connect(gtkblist->treeview, "motion-notify-event", G_CALLBACK(motion_notify_cb), &req);
    + }
    + abracadabra(w_menubar, value);
    + }
    +}
    +
    +static void
    +reset_row_heights(const char *name, PurplePrefType type,
    + gconstpointer val, gpointer data)
    +{
    + PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
    + GtkTreeViewColumn *col = gtk_tree_view_get_column(GTK_TREE_VIEW(gtkblist->treeview), 1);
    + GList *iter = gtk_tree_view_column_get_cell_renderers(col);
    +
    + for (; iter; iter = g_list_delete_link(iter, iter)) {
    + GtkCellRenderer *rend = iter->data;
    + if (GTK_IS_CELL_RENDERER_PIXBUF(rend)) {
    + g_object_set(rend, "height", val ? 32 : 16, NULL);
    + break;
    + }
    + }
    + if (iter)
    + g_list_free(iter);
    +}
    +
    +static void
    +redraw_blist(const char *name, PurplePrefType type,
    + gconstpointer val, gpointer data)
    +{
    + pidgin_blist_refresh(data);
    +}
    +
    +static void
    +row_changed_cb(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, PidginBuddyList *gtkblist)
    +{
    + PurpleBlistNode *node;
    + gboolean stretch, email;
    + static gboolean inuse = FALSE, vis;
    + const char *name;
    + GdkPixbuf *pix = NULL;
    + char *html;
    +
    + if (inuse)
    + return;
    +
    + stretch = purple_prefs_get_bool(PREF_STRETCH);
    + email = purple_prefs_get_bool(PREF_EMAIL);
    + if (!stretch && !email)
    + return;
    +
    + inuse = TRUE;
    +
    + gtk_tree_model_get(model, iter, NODE_COLUMN, &node,
    + BUDDY_ICON_COLUMN, &pix, NAME_COLUMN, &html,
    + BUDDY_ICON_VISIBLE_COLUMN, &vis, -1);
    + if (PURPLE_BLIST_NODE_IS_CONTACT(node))
    + node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)node);
    + if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
    + goto end;
    +
    + if (email) {
    + char *alias = g_markup_escape_text(purple_buddy_get_alias((PurpleBuddy*)node), -1);
    + name = purple_buddy_get_name((PurpleBuddy*)node);
    + if (g_utf8_collate(alias, name)) {
    + char *new;
    + char *find = g_strstr_len(html, -1, alias);
    + if (find) {
    + *find = '\0';
    + new = g_strdup_printf("%s%s%s", html, name, find + strlen(alias));
    + gtk_tree_store_set(GTK_TREE_STORE(model), iter, NAME_COLUMN, new, -1);
    + g_free(new);
    + }
    + }
    + g_free(alias);
    + }
    + if (stretch && vis && (!pix || pix == gtkblist->empty_avatar)) {
    + /* Hiding the empty avatar makes the rows shorter than rows with buddy icons.
    + * We need to find a way to make sure that doesn't happen.
    + */
    + gtk_tree_store_set(GTK_TREE_STORE(model), iter, BUDDY_ICON_VISIBLE_COLUMN, FALSE, -1);
    + }
    +
    +end:
    + if (pix)
    + g_object_unref(pix);
    + g_free(html);
    + inuse = FALSE;
    +}
    +
    +static void
    +gtkblist_created_cb(PurpleBuddyList *blist)
    +{
    + PidginBuddyList *gtkblist = PIDGIN_BLIST(blist);
    +
    + w_blist = gtkblist->window;
    + w_menubar = gtk_item_factory_get_widget(gtkblist->ift, "<PurpleMain>");
    +
    + g_signal_connect(gtkblist->treemodel, "row_changed", G_CALLBACK(row_changed_cb), gtkblist);
    +
    + purple_prefs_trigger_callback(PREF_LIST);
    + purple_prefs_trigger_callback(PREF_MENU);
    +
    + purple_prefs_trigger_callback(PIDGIN_PREFS_ROOT "/blist/show_buddy_icons");
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-created", plugin,
    + PURPLE_CALLBACK(gtkblist_created_cb), NULL);
    +
    + if (pidgin_blist_get_default_gtk_blist())
    + gtkblist_created_cb(purple_get_blist());
    + purple_prefs_connect_callback(plugin, PREF_LIST, pref_cb, NULL);
    + purple_prefs_connect_callback(plugin, PREF_MENU, pref_cb, NULL);
    +
    + purple_prefs_connect_callback(plugin, PREF_STRETCH, redraw_blist, purple_get_blist());
    + purple_prefs_connect_callback(plugin, PREF_EMAIL, redraw_blist, purple_get_blist());
    +
    + purple_prefs_connect_callback(plugin, PIDGIN_PREFS_ROOT "/blist/show_buddy_icons",
    + reset_row_heights, NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + if(w_blist) {
    + gtk_widget_show(w_blist);
    +
    + if(w_menubar)
    + gtk_widget_show(w_menubar);
    + }
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *ppref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + ppref = purple_plugin_pref_new_with_name_and_label(PREF_LIST,
    + _("Hide the buddy list when it is created"));
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + ppref = purple_plugin_pref_new_with_name_and_label(PREF_MENU,
    + _("Hide the menu in the buddy list window"));
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + ppref = purple_plugin_pref_new_with_name_and_label(PREF_STRETCH,
    + _("Stretch the buddyname if the buddy has no buddyicon."));
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + ppref = purple_plugin_pref_new_with_name_and_label(PREF_EMAIL,
    + _("Show email addresses for all the buddies."));
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    + 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,
    +
    + "gtk-plugin_pack-blistops",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + NULL,
    +
    + NULL,
    + NULL,
    + &prefs_info,
    + 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 = _("Buddy List Options");
    + info.summary = _("Gives extended options to the buddy list");
    + info.description = _("Gives extended options to the buddy list");
    +
    + purple_prefs_add_none(PREF_MY);
    + purple_prefs_add_none(PREF_ROOT);
    + purple_prefs_add_bool(PREF_LIST, FALSE);
    + purple_prefs_add_bool(PREF_MENU, FALSE);
    + purple_prefs_add_bool(PREF_STRETCH, TRUE);
    + purple_prefs_add_bool(PREF_EMAIL, FALSE);
    +}
    +
    +PURPLE_INIT_PLUGIN(blistops, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/blistops/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Buddy List Options]
    +type=default
    +depends=pidgin
    +provides=blistops
    +summary=Gives extended options to the buddy list
    +description=%(summary)s
    +authors=Gary Kramlich
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,50 @@
    +EXTRA_DIST = \
    + plugins.cfg
    +
    +noinst_PROGRAMS = recursetest
    +
    +recursetest_SOURCES = \
    + recurse.c \
    + recursetest.c
    +
    +if HAVE_PIDGIN
    +
    +noinst_PROGRAMS += gtktimezonetest
    +
    +gtktimezonetest_SOURCES = \
    + gtktimezone.c \
    + gtktimezonetest.c \
    + recurse.c
    +
    +gtktimezonetest_LDADD = \
    + $(GTK_LIBS)
    +
    +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)\" \
    + $(GTK_CFLAGS) \
    + $(GLIB_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/buddyedit.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,412 @@
    +/*************************************************************************
    + * Buddy Edit Module
    + *
    + * A Purple 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 PURPLE_PLUGINS
    +#define PLUGIN "core-kleptog-buddyedit"
    +
    +#include <glib.h>
    +#include <string.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 PurplePlugin *plugin_self;
    +
    +static void
    +buddyedit_editcomplete_cb(PurpleBlistNode * data, PurpleRequestFields * fields)
    +{
    + gboolean blist_destroy = FALSE;
    +
    + PurpleBlistNode *olddata = data; /* Keep pointer in case we need to destroy it */
    +
    + /* Account detail stuff */
    + switch (data->type)
    + {
    + case PURPLE_BLIST_BUDDY_NODE:
    + {
    + PurpleBuddy *buddy = (PurpleBuddy *) data;
    + PurpleAccount *account = purple_request_fields_get_account(fields, "account");
    + const char *name = purple_request_fields_get_string(fields, "name");
    + const char *alias = purple_request_fields_get_string(fields, "alias");
    +
    + /* If any details changes, create the buddy */
    + if((account != buddy->account) || strcmp(name, buddy->name))
    + {
    + GHashTable *tmp;
    + PurpleBuddy *newbuddy = purple_buddy_new(account, name, alias);
    + purple_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 = ((PurpleBlistNode *) buddy)->settings;
    + ((PurpleBlistNode *) buddy)->settings = ((PurpleBlistNode *) newbuddy)->settings;
    + ((PurpleBlistNode *) newbuddy)->settings = tmp;
    +
    + blist_destroy = TRUE;
    + data = (PurpleBlistNode *) newbuddy;
    + }
    + else
    + purple_blist_alias_buddy(buddy, alias);
    + break;
    + }
    + case PURPLE_BLIST_CONTACT_NODE:
    + {
    + PurpleContact *contact = (PurpleContact *) data;
    + const char *alias = purple_request_fields_get_string(fields, "alias");
    + purple_contact_set_alias(contact, alias);
    + break;
    + }
    + case PURPLE_BLIST_GROUP_NODE:
    + {
    + PurpleGroup *group = (PurpleGroup *) data;
    + const char *alias = purple_request_fields_get_string(fields, "alias");
    + purple_blist_rename_group(group, alias);
    + break;
    + }
    + case PURPLE_BLIST_CHAT_NODE:
    + {
    + PurpleChat *chat = (PurpleChat *) data;
    + gboolean new_chat = FALSE;
    +
    + PurpleConnection *gc;
    + GList *list = NULL, *tmp;
    + gc = purple_account_get_connection(chat->account);
    + if(PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
    + list = PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
    +
    + PurpleAccount *newaccount = purple_request_fields_get_account(fields, "account");
    +
    + /* In Purple2 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 just change
    + * in-situ. */
    + 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 = purple_request_fields_get_string(fields, pce->identifier);
    +
    + if(oldvalue == NULL)
    + oldvalue = "";
    + if(newvalue == NULL)
    + newvalue = "";
    + if(strcmp(oldvalue, newvalue) != 0)
    + new_chat = TRUE;
    + }
    + }
    +
    + 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 = purple_request_fields_get_string(fields, pce->identifier);
    + g_hash_table_replace(components, g_strdup(pce->identifier),
    + g_strdup(newvalue));
    + }
    + }
    + PurpleChat *newchat = purple_chat_new(newaccount, NULL, components);
    + purple_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */
    + data = (PurpleBlistNode *) 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(pce->required)
    + continue;
    + if(pce->is_int)
    + {
    + /* Do nothing, yet */
    + }
    + else
    + {
    + newvalue = purple_request_fields_get_string(fields, pce->identifier);
    + g_hash_table_replace(chat->components, g_strdup(pce->identifier),
    + g_strdup(newvalue));
    + }
    + }
    + }
    +
    + const char *alias = purple_request_fields_get_string(fields, "alias");
    + purple_blist_alias_chat(chat, alias);
    + break;
    + }
    + case PURPLE_BLIST_OTHER_NODE:
    + break;
    + }
    +
    + purple_signal_emit(purple_blist_get_handle(), PLUGIN "-submit-fields", fields, data);
    +
    + if(blist_destroy)
    + {
    + if(olddata->type == PURPLE_BLIST_BUDDY_NODE)
    + purple_blist_remove_buddy((PurpleBuddy *) olddata);
    + else if(olddata->type == PURPLE_BLIST_CHAT_NODE)
    + purple_blist_remove_chat((PurpleChat *) olddata);
    + }
    +
    + /* Save any changes */
    + purple_blist_schedule_save();
    +}
    +
    +static PurpleAccount *buddyedit_account_filter_func_data;
    +
    +static gboolean
    +buddyedit_account_filter_func(PurpleAccount * account)
    +{
    + PurplePluginProtocolInfo *gppi1 =
    + PURPLE_PLUGIN_PROTOCOL_INFO(purple_account_get_connection(account)->prpl);
    + PurplePluginProtocolInfo *gppi2 =
    + PURPLE_PLUGIN_PROTOCOL_INFO(purple_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(PurpleBlistNode * node, gpointer data)
    +{
    + purple_debug(PURPLE_DEBUG_INFO, PLUGIN, "buddy_edit_cb(%p)\n", node);
    + PurpleRequestFields *fields;
    + PurpleRequestField *field;
    + PurpleRequestFieldGroup *group;
    + char *request_title = NULL;
    +
    + fields = purple_request_fields_new();
    +
    + switch (node->type)
    + {
    + case PURPLE_BLIST_BUDDY_NODE:
    + {
    + PurpleBuddy *buddy = (PurpleBuddy *) node;
    + group = purple_request_field_group_new("Buddy Details");
    + purple_request_fields_add_group(fields, group);
    +
    + field = purple_request_field_account_new("account", "Account", buddy->account);
    + purple_request_field_account_set_show_all(field, TRUE);
    + purple_request_field_group_add_field(group, field);
    +
    + field = purple_request_field_string_new("name", "Name", buddy->name, FALSE);
    + purple_request_field_group_add_field(group, field);
    +
    + field = purple_request_field_string_new("alias", "Alias", buddy->alias, FALSE);
    + purple_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Buddy";
    + break;
    + }
    + case PURPLE_BLIST_CONTACT_NODE:
    + {
    + PurpleContact *contact = (PurpleContact *) node;
    + group = purple_request_field_group_new("Contact Details");
    + purple_request_fields_add_group(fields, group);
    +
    + field = purple_request_field_string_new("alias", "Alias", contact->alias, FALSE);
    + purple_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Contact";
    + break;
    + }
    + case PURPLE_BLIST_GROUP_NODE:
    + {
    + PurpleGroup *grp = (PurpleGroup *) node;
    + group = purple_request_field_group_new("Group Details");
    + purple_request_fields_add_group(fields, group);
    +
    + field = purple_request_field_string_new("alias", "Name", grp->name, FALSE);
    + purple_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Group";
    + break;
    + }
    + case PURPLE_BLIST_CHAT_NODE:
    + {
    + PurpleChat *chat = (PurpleChat *) node;
    + PurpleConnection *gc;
    + GList *list = NULL, *tmp;
    + group = purple_request_field_group_new("Chat Details");
    + purple_request_fields_add_group(fields, group);
    +
    + field = purple_request_field_account_new("account", "Account", chat->account);
    + purple_request_field_account_set_filter(field, buddyedit_account_filter_func);
    + buddyedit_account_filter_func_data = chat->account;
    + purple_request_field_group_add_field(group, field);
    + gc = purple_account_get_connection(chat->account);
    + if(PURPLE_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
    + list = PURPLE_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;
    +
    + purple_debug(PURPLE_DEBUG_INFO, PLUGIN,
    + "identifier=%s, label=%s, is_int=%d, required=%d\n", pce->identifier,
    + pce->label, pce->is_int, pce->required);
    +
    + if(pce->is_int)
    + continue; /* Not yet */
    + value = g_hash_table_lookup(chat->components, pce->identifier);
    + field = purple_request_field_string_new(pce->identifier, pce->label, value, FALSE);
    + purple_request_field_set_required(field, pce->required);
    + purple_request_field_group_add_field(group, field);
    + }
    + field = purple_request_field_string_new("alias", "Alias", chat->alias, FALSE);
    + purple_request_field_group_add_field(group, field);
    +
    + request_title = "Edit Chat";
    + break;
    + }
    + default:
    + request_title = "Edit";
    + break;
    + }
    +
    + purple_signal_emit(purple_blist_get_handle(), PLUGIN "-create-fields", fields, node);
    +
    + purple_request_fields(plugin_self, request_title, NULL, NULL, fields,
    + "OK", G_CALLBACK(buddyedit_editcomplete_cb),
    + "Cancel", NULL,
    + NULL, NULL, NULL, /* XXX: These should be set. */
    + node);
    +}
    +
    +static void
    +buddy_menu_cb(PurpleBlistNode * node, GList ** menu, void *data)
    +{
    + PurpleMenuAction *action;
    +
    + switch (node->type)
    + {
    + /* These are the types we handle */
    + case PURPLE_BLIST_BUDDY_NODE:
    + case PURPLE_BLIST_CONTACT_NODE:
    + case PURPLE_BLIST_GROUP_NODE:
    + case PURPLE_BLIST_CHAT_NODE:
    + break;
    +
    + case PURPLE_BLIST_OTHER_NODE:
    + default:
    + return;
    + }
    +
    + action = purple_menu_action_new("Edit...", PURPLE_CALLBACK(buddy_edit_cb), NULL, NULL);
    + *menu = g_list_append(*menu, action);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin * plugin)
    +{
    +
    + plugin_self = plugin;
    +
    + void *blist_handle = purple_blist_get_handle();
    +
    + purple_signal_register(blist_handle, PLUGIN "-create-fields", /* Called when about to create dialog */
    + purple_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
    + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_TYPE_POINTER), /* FieldList */
    + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_BLIST));
    +
    + purple_signal_register(blist_handle, PLUGIN "-submit-fields", /* Called when dialog submitted */
    + purple_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
    + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_TYPE_POINTER), /* FieldList */
    + purple_value_new(PURPLE_TYPE_SUBTYPE, PURPLE_SUBTYPE_BLIST));
    +
    + purple_signal_connect(blist_handle, "blist-node-extended-menu", plugin,
    + PURPLE_CALLBACK(buddy_menu_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +
    +static PurplePluginInfo info = {
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_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://buddytools.sf.net",
    +
    + plugin_load,
    + NULL,
    + NULL,
    +
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void
    +init_plugin(PurplePlugin * plugin)
    +{
    +}
    +
    +PURPLE_INIT_PLUGIN(buddyedit, init_plugin, info);
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/buddytime.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,490 @@
    +/*
    + * 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"
    +
    +#include "buddytime.h"
    +#define PLUGIN_STATIC_NAME CORE_PLUGIN_STATIC_NAME
    +#define PLUGIN_ID CORE_PLUGIN_ID
    +
    +#define SETTING_NAME "timezone"
    +#define CONTROL_NAME PLUGIN_ID "-" SETTING_NAME
    +
    +#include <glib.h>
    +#include <errno.h>
    +#include <ctype.h>
    +#include <string.h>
    +
    +#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"
    +
    +#include "gtkblist.h"
    +
    +#include "recurse.h"
    +
    +#define TIMEZONE_FLAG ((void*)1)
    +#define DISABLED_FLAG ((void*)2)
    +
    +BuddyTimeUiOps *ui_ops = NULL;
    +
    +static PurplePlugin *plugin_self;
    +
    +/* 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;
    +
    + 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);
    +
    + if (!resolve)
    + return timezone;
    +
    + /* The effect of "none" is to stop recursion */
    + if (timezone && strcmp(timezone, "none") == 0)
    + return NULL;
    +
    + if (timezone)
    + return timezone;
    +
    + 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);
    + }
    +
    + if (timezone && strcmp(timezone, "none") == 0)
    + return NULL;
    +
    + return timezone;
    +}
    +
    +/* 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;
    +
    + /* 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;
    +}
    +
    +/* 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");
    +
    + 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
    +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;
    +
    + name = purple_conversation_get_name(conv);
    + buddy = purple_find_buddy(purple_conversation_get_account(conv), name);
    + if (!buddy)
    + return;
    +
    + timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE, NULL);
    +
    + if (!timezone)
    + return;
    +
    + ret = timezone_get_time(timezone, &tm, &diff, NULL);
    +
    + if (ret == 0)
    + {
    + const char *text = purple_time_format(&tm);
    +
    + 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);
    + }
    +
    + purple_conversation_write(conv, PLUGIN_STATIC_NAME, str, PURPLE_MESSAGE_SYSTEM, time(NULL));
    +
    + g_free(str);
    + }
    +}
    +
    +#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;
    + }
    +
    + 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);
    +
    + /* 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
    +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
    +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 *);
    +
    + ret_val = ((gpointer (*)(void *, void *, void *, void *))cb)(arg1, arg2, arg3, data);
    +
    + if (return_val != NULL)
    + *return_val = ret_val;
    +}
    +
    +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;
    +}
    +
    +static gboolean
    +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 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)
    +{
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("Buddy Time");
    + 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);
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/buddytime.c.old Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,18 @@
    +[Buddy Time]
    +type=incomplete
    +depends=purple
    +provides=buddytime
    +summary=Quickly see the local time of a buddy
    +description=%(summary)s
    +authors=Gary Kramlich,Richard Laager
    +introduced=2.2.0
    +
    +[Buddy Time (Pidgin UI)]
    +type=incomplete
    +depends=pidgin buddytime
    +provides=gtkbuddytime
    +summary=Pidgin user interface for the Buddy Time plugin
    +description=%(summary)s
    +authors=Gary Kramlich,Richard Laager
    +introduced=2.2.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/buddytime/private.h Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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 */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/chronic/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,26 @@
    +EXTRA_DIST = \
    + plugins.cfg
    +
    +chronicdir=$(PURPLE_LIBDIR)
    +
    +chronic_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +chronic_LTLIBRARIES = chronic.la
    +
    +chronic_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +chronic_la_SOURCES = chronic.c
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + $(GLIB_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/chronic/chronic.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,112 @@
    +/*
    + * Chronic - Remote sound play triggering
    + * 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
    + * 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"
    +
    +/* libc */
    +#include <string.h>
    +
    +/* Purple */
    +#include <conversation.h>
    +#include <debug.h>
    +#include <plugin.h>
    +
    +static void
    +chronic_received_cb(PurpleAccount *account, char *sender, char *message,
    + PurpleConversation *conv, PurpleMessageFlags flags)
    +{
    + char *sound = NULL, *path = NULL;
    +
    + if(strlen(message) > 3) {
    + if(!strncmp(message, "{S ", 3)) {
    + sound = (message + 4);
    + /* add code to find a matching sound */
    + /* purple_sound_play_file(); */
    + }
    + }
    +
    + return;
    +}
    +
    +static gboolean
    +chronic_load(PurplePlugin *plugin)
    +{
    + void *convhandle;
    +
    + convhandle = purple_conversations_get_handle();
    +
    + purple_signal_connect(convhandle, "received-im-msg", plugin,
    + PURPLE_CALLBACK(chronic_received_cb), NULL);
    + purple_signal_connect(convhandle, "received-chat-msg", plugin,
    + PURPLE_CALLBACK(chronic_received_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo chronic_info =
    +{
    + PURPLE_PLUGIN_MAGIC, /* magic? do you think i'm gullible enough to
    + * believe in magic? */
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + "core-plugin_pack-chronic",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "John Bailey <rekkanoryo@rekkanoryo.org>",
    + PP_WEBSITE,
    + chronic_load,
    + /* comment below is temporary until i decide if i need the function */
    + NULL, /*chronic_unload,*/
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void
    +chronic_init(PurplePlugin *plugin)
    +{
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + chronic_info.name = _("Chronic");
    + chronic_info.summary = _("Sound playing triggers");
    + chronic_info.description = _("Allows buddies to remotely trigger sound"
    + " playing in your instance of Purple with {S &lt;sound&gt;. Inspired"
    + " by #guifications channel resident EvilDennisR and ancient"
    + " versions of AOL. THIS PLUGIN IS NOT YET FUNCTIONAL!"
    + " IT IS USELESS!");
    +}
    +
    +PURPLE_INIT_PLUGIN(chronic, chronic_init, chronic_info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/chronic/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Chronic]
    +type=incomplete
    +depends=purple
    +provides=chronic
    +summary=Sound playing triggers
    +description=Allows buddies to remotely trigger sound playing in your running libpurple client with {S <sound>. Inspired by IRC channel resident EvilDennisR and ancient versions of AOL. THIS PLUGIN IS NOT YET FUNCTIONAL! IT IS USELESS!
    +authors=John Bailey
    +introduced=1.0beta3.1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/colorize/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +colorizedir = $(PURPLE_LIBDIR)
    +
    +colorize_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +colorize_LTLIBRARIES = colorize.la
    +
    +colorize_la_SOURCES = \
    + colorize.c
    +
    +colorize_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/colorize/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for dice plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = colorize
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/colorize/colorize.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,298 @@
    +/* Gaim Colorize Plug-in v0.2
    + *
    + * Colorizes outgoing text to a gradient of specified starting and
    + * and ending values.
    + *
    + * TODO:
    + * - echo color formatting to local color log
    + * - fix HTML-mixed messages (currently strips all HTML)
    + *
    + * Copyright (C) 2005, Ike Gingerich <ike_@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 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 <version.h>
    +#include <plugin.h>
    +#include <util.h>
    +#include <debug.h>
    +
    +#define PLUGIN_ID "core-plugin_pack-colorize"
    +#define PLUGIN_AUTHOR "Ike Gingerich <ike_@users.sourceforge.net>"
    +
    +#define PREFS_PREFIX "/plugins/core/" PLUGIN_ID
    +#define PREFS_I_RED PREFS_PREFIX "/initial_r"
    +#define PREFS_I_GREEN PREFS_PREFIX "/initial_g"
    +#define PREFS_I_BLUE PREFS_PREFIX "/initial_b"
    +#define PREFS_T_RED PREFS_PREFIX "/target_r"
    +#define PREFS_T_GREEN PREFS_PREFIX "/target_g"
    +#define PREFS_T_BLUE PREFS_PREFIX "/target_b"
    +
    +static const guint8 default_initial_rgb[3] = { 0xFF, 0x00, 0x00 };
    +static const guint8 default_target_rgb[3] = { 0x00, 0x00, 0x00 };
    +
    +/* set up preferences dialog */
    +static PurplePluginPrefFrame *
    +init_pref_frame(PurplePlugin *plugin) {
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *ppref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + /* initial color */
    + ppref = purple_plugin_pref_new_with_label("Initial Color");
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* initial red intensity */
    + ppref = purple_plugin_pref_new_with_name_and_label(PREFS_I_RED,
    + "Red intensity (0-255): ");
    + purple_plugin_pref_set_bounds(ppref, 0, 255);
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* initial green intensity */
    + ppref = purple_plugin_pref_new_with_name_and_label(PREFS_I_GREEN,
    + "Green intensity (0-255): ");
    + purple_plugin_pref_set_bounds(ppref, 0, 255);
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* initial blue intensity */
    + ppref = purple_plugin_pref_new_with_name_and_label(PREFS_I_BLUE,
    + "Blue intensity (0-255): ");
    + purple_plugin_pref_set_bounds(ppref, 0, 255);
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* target color */
    + ppref = purple_plugin_pref_new_with_label("Target Color");
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* target red intensity */
    + ppref = purple_plugin_pref_new_with_name_and_label(PREFS_T_RED,
    + "Red intensity (0-255): ");
    + purple_plugin_pref_set_bounds(ppref, 0, 255);
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* target green intensity */
    + ppref = purple_plugin_pref_new_with_name_and_label(PREFS_T_GREEN,
    + "Green intensity (0-255): ");
    + purple_plugin_pref_set_bounds(ppref, 0, 255);
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + /* target blue intensity */
    + ppref = purple_plugin_pref_new_with_name_and_label(PREFS_T_BLUE,
    + "Blue intensity (0-255): ");
    + purple_plugin_pref_set_bounds(ppref, 0, 255);
    + purple_plugin_pref_frame_add(frame, ppref);
    +
    + return frame;
    +}
    +
    +inline static guint8
    +round_gfloat_to_guint8(gfloat f) {
    + return ((guchar)(f + 0.5f));
    +}
    +
    +inline static gint32
    +rgb_equals(guint8 a[3], gfloat b[3]) {
    + return ( a[0] == round_gfloat_to_guint8(b[0]) &&
    + a[1] == round_gfloat_to_guint8(b[1]) &&
    + a[2] == round_gfloat_to_guint8(b[2]) );
    +}
    +
    +static void
    +colorize_message(char **message) {
    + guint i, len;
    + gfloat d_grad[3], grad[3];
    + guint8 initial_rgb[3], target_rgb[3], last_rgb[3];
    + gchar *formatted_char, *tmp, *new_msg;
    +
    + g_return_if_fail(message != NULL);
    + g_return_if_fail(*message != NULL);
    + g_return_if_fail(**message != '\0');
    +
    + new_msg = g_strdup("");
    + len = strlen( *message );
    +
    + /* get colors from preferences */
    + initial_rgb[0] = (guint8)purple_prefs_get_int(PREFS_I_RED);
    + initial_rgb[1] = (guint8)purple_prefs_get_int(PREFS_I_GREEN);
    + initial_rgb[2] = (guint8)purple_prefs_get_int(PREFS_I_BLUE);
    +
    + target_rgb[0] = (guint8)purple_prefs_get_int(PREFS_T_RED);
    + target_rgb[1] = (guint8)purple_prefs_get_int(PREFS_T_GREEN);
    + target_rgb[2] = (guint8)purple_prefs_get_int(PREFS_T_BLUE);
    +
    + /* initialize current gradient value */
    + grad[0] = (gfloat)initial_rgb[0];
    + grad[1] = (gfloat)initial_rgb[1];
    + grad[2] = (gfloat)initial_rgb[2];
    +
    + /* determine the delta gradient value */
    + d_grad[0] = (gfloat)(target_rgb[0] - initial_rgb[0]) / (gfloat)len;
    + d_grad[1] = (gfloat)(target_rgb[1] - initial_rgb[1]) / (gfloat)len;
    + d_grad[2] = (gfloat)(target_rgb[2] - initial_rgb[2]) / (gfloat)len;
    +
    + /* open initial font tag and format first character */
    + formatted_char = g_strdup_printf("<font color=\"#%02x%02x%02x\">%c",
    + round_gfloat_to_guint8(grad[0]),
    + round_gfloat_to_guint8(grad[1]),
    + round_gfloat_to_guint8(grad[2]),
    + *(*message));
    +
    + /* create a new string with the newly formatted char and free the old one */
    + tmp = g_strconcat(new_msg, formatted_char, NULL);
    + g_free(formatted_char);
    + g_free(new_msg);
    +
    + new_msg = tmp;
    +
    + /* format each character one by one:
    + * (if it is not a space) AND
    + * (if it is not the same color as the last character)
    + */
    + for(i=1; i<len; i++)
    + {
    + /* store last color */
    + last_rgb[0] = round_gfloat_to_guint8(grad[0]);
    + last_rgb[1] = round_gfloat_to_guint8(grad[1]);
    + last_rgb[2] = round_gfloat_to_guint8(grad[2]);
    +
    + /* increment the gradient */
    + grad[0] += d_grad[0];
    + grad[1] += d_grad[1];
    + grad[2] += d_grad[2];
    +
    + /* format next character appropriately */
    + if( g_ascii_isspace ( *(*message+i) ) ||
    + rgb_equals(last_rgb, grad) )
    + formatted_char = g_strdup_printf("%c", *(*message+i));
    + else
    + formatted_char = g_strdup_printf("</font><font color=\"#%02x%02x%02x\">%c",
    + round_gfloat_to_guint8(grad[0]),
    + round_gfloat_to_guint8(grad[1]),
    + round_gfloat_to_guint8(grad[2]),
    + *(*message+i));
    +
    +
    + /* create a new string with the newly formatted char and free the old one */
    + tmp = g_strconcat(new_msg, formatted_char, NULL);
    + g_free(formatted_char);
    + g_free(new_msg);
    +
    + new_msg = tmp;
    + }
    +
    + /* close final font tag */
    + new_msg = g_strconcat(new_msg, "</font>", NULL);
    +
    + /* return result */
    + g_free(*message);
    + *message = new_msg;
    +}
    +
    +/* respond to a sending-im signal by replacing outgoing text
    + * with colorized version
    + */
    +static void
    +sending_im_msg(PurpleAccount *account, gchar *receiver, gchar **message) {
    + gchar *stripped_message;
    +
    + /* strip any existing HTML */
    + stripped_message = purple_markup_strip_html(*message);
    + g_free(*message);
    +
    + /* colorize the message with HTML font tags */
    + *message = stripped_message;
    + colorize_message(message);
    +
    + /* todo: additional conversation manipulation is going to be required to
    + display the colorized version of the message locally */
    +}
    +
    +/* register sendin-im signal */
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + purple_signal_connect(purple_conversations_get_handle(), "sending-im-msg",
    + plugin, PURPLE_CALLBACK(sending_im_msg), NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + return TRUE;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + init_pref_frame
    +};
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + PLUGIN_ID,
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + PLUGIN_AUTHOR,
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + NULL,
    +
    + NULL,
    + NULL,
    + &prefs_info,
    + NULL
    +};
    +
    +/* initialize default preferences */
    +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 = _("Colorize");
    + info.summary = _("Colorizes outgoing message text.");
    + info.description = _("Colorizes outgoing message text to a gradient of "
    + "specified starting and ending RGB values.");
    +
    + /* prefs */
    + purple_prefs_add_none(PREFS_PREFIX);
    +
    + purple_prefs_add_int(PREFS_I_RED, default_initial_rgb[0]);
    + purple_prefs_add_int(PREFS_I_GREEN, default_initial_rgb[1]);
    + purple_prefs_add_int(PREFS_I_BLUE, default_initial_rgb[2]);
    +
    + purple_prefs_add_int(PREFS_T_RED, default_target_rgb[0]);
    + purple_prefs_add_int(PREFS_T_GREEN, default_target_rgb[1]);
    + purple_prefs_add_int(PREFS_T_BLUE, default_target_rgb[2]);
    +}
    +
    +PURPLE_INIT_PLUGIN(colorize, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/colorize/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Colorize]
    +type=default
    +depends=purple
    +provides=colorize
    +summary=Colorizes outgoing message text.
    +description=Colorizes outgoing message text to a gradient of specified starting and ending RGB values.
    +authors=Ike Gingerich
    +introduced=2.4.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/common/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,5 @@
    +EXTRA_DIST = \
    + glib_compat.h \
    + gtk_template.c \
    + purple_template.c \
    + pp_internal.h
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/common/glib_compat.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,154 @@
    +/*
    + * Purple Plugin Pack - GLib Compatibility Header
    + *
    + * This code borrowed from GLib (http://www.gtk.org/) for backward
    + * compatibility with ancient versions of GLib.
    + *
    + * 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 <string.h>
    +
    +#if !GLIB_CHECK_VERSION(2,2,0)
    +gboolean
    +g_str_has_suffix (const gchar *str, const gchar *suffix)
    +{
    + int str_len;
    + int suffix_len;
    +
    + g_return_val_if_fail (str != NULL, FALSE);
    + g_return_val_if_fail (suffix != NULL, FALSE);
    +
    + str_len = strlen (str);
    + suffix_len = strlen (suffix);
    +
    + if (str_len < suffix_len)
    + return FALSE;
    +
    + return strcmp (str + str_len - suffix_len, suffix) == 0;
    +}
    +
    +gboolean
    +g_str_has_prefix (const gchar *str, const gchar *prefix)
    +{
    + int str_len;
    + int prefix_len;
    +
    + g_return_val_if_fail (str != NULL, FALSE);
    + g_return_val_if_fail (prefix != NULL, FALSE);
    +
    + str_len = strlen (str);
    + prefix_len = strlen (prefix);
    +
    + if (str_len < prefix_len)
    + return FALSE;
    +
    + return strncmp (str, prefix, prefix_len) == 0;
    +}
    +#endif
    +
    +/* this check is backwards now because we need to do stuff both if the builder
    + * has 2.4.0 and if the builder doesn't have 2.4.0 */
    +#if GLIB_CHECK_VERSION(2,4,0)
    +# include <glib/gi18n-lib.h>
    +#else
    +# include <locale.h>
    +# include <libintl.h>
    +# define _(String) dgettext (GETTEXT_PACKAGE, String)
    +# define Q_(String) g_strip_context ((String), dgettext (GETTEXT_PACKAGE, String))
    +# ifdef gettext_noop
    +# define N_(String) gettext_noop (String)
    +# else
    +# define N_(String) (String)
    +# endif
    +static gchar **
    +g_strsplit_set (const gchar *string, const gchar *delimiters, gint max_tokens)
    +{
    + gboolean delim_table[256];
    + GSList *tokens, *list;
    + gint n_tokens;
    + const gchar *s;
    + const gchar *current;
    + gchar *token;
    + gchar **result;
    +
    + g_return_val_if_fail (string != NULL, NULL);
    + g_return_val_if_fail (delimiters != NULL, NULL);
    +
    + if (max_tokens < 1)
    + max_tokens = G_MAXINT;
    +
    + if (*string == '\0')
    + {
    + result = g_new (char *, 1);
    + result[0] = NULL;
    + return result;
    + }
    +
    + memset (delim_table, FALSE, sizeof (delim_table));
    + for (s = delimiters; *s != '\0'; ++s)
    + delim_table[*(guchar *)s] = TRUE;
    +
    + tokens = NULL;
    + n_tokens = 0;
    +
    + s = current = string;
    + while (*s != '\0')
    + {
    + if (delim_table[*(guchar *)s] && n_tokens + 1 < max_tokens)
    + {
    + gchar *token;
    +
    + token = g_strndup (current, s - current);
    + tokens = g_slist_prepend (tokens, token);
    + ++n_tokens;
    +
    + current = s + 1;
    + }
    +
    + ++s;
    + }
    +
    + token = g_strndup (current, s - current);
    + tokens = g_slist_prepend (tokens, token);
    + ++n_tokens;
    +
    + result = g_new (gchar *, n_tokens + 1);
    +
    + result[n_tokens] = NULL;
    + for (list = tokens; list != NULL; list = list->next)
    + result[--n_tokens] = list->data;
    +
    + g_slist_free (tokens);
    +
    + return result;
    +}
    +#endif
    +
    +#if !GLIB_CHECK_VERSION(2,6,0)
    +static guint
    +g_strv_length (gchar **str_array)
    +{
    + guint i = 0;
    +
    + g_return_val_if_fail (str_array != NULL, 0);
    +
    + while (str_array[i])
    + ++i;
    +
    + return i;
    +}
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/common/gtk_template.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,89 @@
    +/*
    + * 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>"
    +
    +/* System headers */
    +#include <gdk/gdk.h>
    +#include <gtk/gtk.h>
    +
    +/* Purple headers */
    +#include <gtkplugin.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 */
    + 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 = _("unnamed");
    + 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/common/pp_internal.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,53 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See AUTHORS for a list of all authors
    + *
    + * 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.
    + */
    +#ifndef PP_INTERNAL_H
    +#define PP_INTERNAL_H
    +
    +#ifdef HAVE_CONFIG_H
    +# include "../pp_config.h"
    +#endif
    +
    +#include <glib.h>
    +
    +#include "../common/glib_compat.h"
    +
    +#ifdef _WIN32
    +# include <win32dep.h>
    +#endif
    +
    +/* This works around the lack of G_GNUC_NULL_TERMINATED in old glib and the
    + * lack of the NULL sentinel in GCC older than 4.0.0 and non-GCC compilers */
    +#ifndef G_GNUC_NULL_TERMINATED
    +# if __GNUC__ >= 4
    +# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__))
    +# else
    +# define G_GNUC_NULL_TERMINATED
    +# endif
    +#endif
    +
    +/* Every plugin for libpurple, Pidgin, or Finch needs to define PURPLE_PLUGINS
    + * in order to compile and run correctly. Define it here for simplicity */
    +#define PURPLE_PLUGINS
    +
    +/* Every plugin for libpurple, Pidgin, or Finch needs to include version.h
    + * to ensure it has the version macros, so include it here for simplicity */
    +#include <version.h>
    +
    +#endif /* PP_INTERNAL_H */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/common/purple_template.c Thu Aug 27 19:46:46 2009 -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)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/configure.ac Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,465 @@
    +AC_INIT([purple-plugin_pack], [2.6.0dev], [plugins-devel@lists.guifications.org])
    +AC_CANONICAL_SYSTEM
    +AM_CONFIG_HEADER(pre_config.h)
    +
    +AM_INIT_AUTOMAKE([foreign dist-bzip2])
    +
    +AC_PREREQ([2.50])
    +
    +AC_DEFINE_UNQUOTED(PP_VERSION, "AC_PACKAGE_VERSION", [Plugin Pack Version])
    +
    +AC_PATH_PROG(sedpath, sed)
    +AC_DISABLE_STATIC
    +
    +dnl #######################################################################
    +dnl # Initialize some variables that get passed to plugin_pack.py
    +dnl #######################################################################
    +DEPENDENCIES=""
    +
    +dnl #######################################################################
    +dnl # Setup libtool
    +dnl #######################################################################
    +m4_defun([_LT_AC_LANG_CXX_CONFIG], [:])
    +m4_defun([_LT_AC_LANG_F77_CONFIG], [:])
    +m4_defun([_LT_AC_LANG_GCJ_CONFIG], [:])
    +m4_defun([_LT_AC_LANG_RC_CONFIG], [:])
    +AM_PROG_LIBTOOL
    +LIBTOOL="$LIBTOOL --silent"
    +
    +dnl #######################################################################
    +dnl # I'm lazy and figured config.h is the best place for this ;)
    +dnl #######################################################################
    +AC_DEFINE_UNQUOTED(PP_WEBSITE, "http://plugins.guifications.org/trac", [Plugin Pack Website])
    +
    +dnl #######################################################################
    +dnl # Our header
    +dnl #######################################################################
    +AH_TOP([ /* our header */
    +#ifndef PP_CONFIG_H
    +#define PP_CONFIG_H
    +])
    +AH_BOTTOM([
    +#endif /* PP_CONFIG_H */
    +])
    +
    +dnl #######################################################################
    +dnl # Good ol' gettext
    +dnl #######################################################################
    +AC_PROG_INTLTOOL
    +
    +GETTEXT_PACKAGE=plugin_pack
    +AC_SUBST(GETTEXT_PACKAGE)
    +AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE, ["$GETTEXT_PACKAGE"], [Define the gettext package to be used.])
    +ALL_LINGUAS="en_AU es_ES fr vi"
    +AM_GLIB_GNU_GETTEXT
    +
    +dnl #######################################################################
    +dnl # Look for the C compiler
    +dnl #######################################################################
    +CFLAGS_save="$CFLAGS"
    +AC_PROG_CC
    +CFLAGS="$CFLAGS_save"
    +
    +AC_ARG_ENABLE(debug, [ --enable-debug compile with debugging support],,enable_debug=no)
    +
    +if test "x$enable_debug" = "xyes" ; then
    + AC_DEFINE(DEBUG, 1, [Define if debugging is enabled.])
    +fi
    +
    +if test "x$GCC" = "xyes"; then
    + CFLAGS="$CFLAGS -Wall -g3"
    +fi
    +AC_SUBST(CFLAGS)
    +
    +dnl #######################################################################
    +dnl # Check for purple
    +dnl #######################################################################
    +PKG_CHECK_MODULES(PURPLE, purple,
    +[
    + AC_DEFINE(HAVE_PURPLE, 1, [Define if we've found libpurple.])
    +])
    +
    +HAVE_PURPLE="yes"
    +AM_CONDITIONAL(HAVE_PURPLE, true)
    +DEPENDENCIES="$DEPENDENCIES,purple"
    +
    +if test x"$prefix" = x"NONE" ; then
    + PURPLE_LIBDIR=`pkg-config --variable=libdir purple`
    + PURPLE_DATADIR=`pkg-config --variable=datadir purple`
    +else
    + PURPLE_LIBDIR="$libdir"
    + PURPLE_DATADIR="$datadir"
    +fi
    +
    +PURPLE_PIXMAPSDIR=""
    +
    +# this is a hack but should work fine.
    +# we use the libpurple datadir for PP_LOCALEDIR since we are not breaking up
    +# the pot's yet, and need to make sure they goto the same place
    +PP_LOCALEDIR="PURPLE_DATADIR/locale"
    +AC_DEFINE_UNQUOTED(PP_LOCALEDIR, ["$PP_LOCALEDIR"], [The localedir to use])
    +
    +if test x"$PURPLE_LIBDIR" != x"" ; then
    + PURPLE_LIBDIR="$PURPLE_LIBDIR/purple-2"
    +fi
    +
    +if test x"$PURPLE_DATADIR" != x"" ; then
    + PURPLE_PIXMAPSDIR="$PURPLE_DATADIR/pixmaps/libpurple"
    + AC_DEFINE_UNQUOTED(PURPLE_PIXMAPSDIR, "$PURPLE_PIXMAPSDIR", [Libpurple pixmaps directory])
    +fi
    +
    +AC_SUBST(PURPLE_CFLAGS)
    +AC_SUBST(PURPLE_LIBS)
    +AC_SUBST(PURPLE_LIBDIR)
    +AC_SUBST(PURPLE_DATADIR)
    +AC_SUBST(PURPLE_PIXMAPSDIR)
    +
    +
    +dnl #######################################################################
    +dnl # Check for pidgin
    +dnl #######################################################################
    +PKG_CHECK_MODULES(PIDGIN, pidgin,
    +[
    + AC_DEFINE(HAVE_PIDGIN, 1, [Define if we've found pidgin.])
    + HAVE_PIDGIN="yes"
    + AM_CONDITIONAL(HAVE_PIDGIN, true)
    + DEPENDENCIES="$DEPENDENCIES,pidgin"
    +], [
    + AC_MSG_RESULT([no])
    + HAVE_PIDGIN="no"
    + AM_CONDITIONAL(HAVE_PIDGIN, false)
    +])
    +
    +if test x"$prefix" = x"NONE" ; then
    + PIDGIN_LIBDIR=`pkg-config --variable=libdir pidgin`
    + PIDGIN_DATADIR=`pkg-config --variable=datadir pidgin`
    +else
    + PIDGIN_LIBDIR="$libdir"
    + PIDGIN_DATADIR="$datadir"
    +fi
    +
    +PIDGIN_PIXMAPSDIR=""
    +
    +if test x"$PIDGIN_LIBDIR" != x"" ; then
    + PIDGIN_LIBDIR="$PIDGIN_LIBDIR/pidgin"
    +fi
    +
    +if test x"$PIDGIN_DATADIR" != x"" ; then
    + PIDGIN_PIXMAPSDIR="$PIDGIN_DATADIR/pixmaps/pidgin"
    + AC_DEFINE_UNQUOTED(PIDGIN_PIXMAPSDIR, "$PIDGIN_PIXMAPSDIR", [Pidgin pixmaps directory])
    +fi
    +
    +AC_SUBST(PIDGIN_CFLAGS)
    +AC_SUBST(PIDGIN_LIBS)
    +AC_SUBST(PIDGIN_LIBDIR)
    +AC_SUBST(PIDGIN_DATADIR)
    +AC_SUBST(PIDGIN_PIXMAPSDIR)
    +
    +dnl #######################################################################
    +dnl # Check for finch
    +dnl #######################################################################
    +PKG_CHECK_MODULES(FINCH, finch,
    +[
    + AC_DEFINE(HAVE_FINCH, 1, [Define if we've found finch.])
    + HAVE_FINCH="yes"
    + AM_CONDITIONAL(HAVE_FINCH, true)
    + DEPENDENCIES="$DEPENDENCIES,finch"
    +], [
    + AC_MSG_RESULT([no])
    + HAVE_FINCH="no"
    + AM_CONDITIONAL(HAVE_FINCH, false)
    +])
    +
    +if test x"$prefix" = x"NONE" ; then
    + FINCH_LIBDIR=`pkg-config --variable=libdir finch`
    + FINCH_DATADIR=`pkg-config --variable=datadir finch`
    +else
    + FINCH_LIBDIR="$libdir"
    + FINCH_DATADIR="$datadir"
    +fi
    +
    +FINCH_PIXMAPSDIR=""
    +
    +if test x"$FINCH_LIBDIR" != x"" ; then
    + FINCH_LIBDIR="$FINCH_LIBDIR/finch"
    +fi
    +
    +if test x"$FINCH_DATADIR" != x"" ; then
    + FINCH_PIXMAPSDIR="$FINCH_DATADIR/pixmaps/finch"
    + AC_DEFINE_UNQUOTED(FINCH_PIXMAPSDIR, "$FINCH_PIXMAPSDIR", [Finch pixmaps directory])
    +fi
    +
    +AC_SUBST(FINCH_CFLAGS)
    +AC_SUBST(FINCH_LIBS)
    +AC_SUBST(FINCH_LIBDIR)
    +AC_SUBST(FINCH_DATADIR)
    +AC_SUBST(FINCH_PIXMAPSDIR)
    +
    +dnl #######################################################################
    +dnl # check for gtk
    +dnl #######################################################################
    +HAVE_GLIB="no"
    +PKG_CHECK_MODULES(GLIB, [glib-2.0], HAVE_GLIB="yes", HAVE_GLIB="no")
    +
    +if test x"$HAVE_GLIB" = x"no" ; then
    + echo "glib development headers were not found. glib development headers"
    + echo "are required to build $PACKAGE."
    + exit 1
    +fi
    +AC_SUBST(GLIB_CFLAGS)
    +AC_SUBST(GLIB_LIBS)
    +
    +HAVE_GTK="no"
    +GTK_CFLAGS=""
    +GTK_LIBS=""
    +PKG_CHECK_MODULES(GTK, [gtk+-2.0], HAVE_GTK="yes", HAVE_GTK="no")
    +AC_SUBST(GTK_CFLAGS)
    +AC_SUBST(GTK_LIBS)
    +
    +dnl #######################################################################
    +dnl # check for gnt
    +dnl #######################################################################
    +HAVE_GNT="no"
    +GNT_CFLAGS=""
    +GNT_LIBS=""
    +PKG_CHECK_MODULES(GNT, [gnt], HAVE_GNT="yes", HAVE_GNT="no")
    +AC_SUBST(GNT_CFLAGS)
    +AC_SUBST(GNT_CFLAGS)
    +
    +dnl #######################################################################
    +dnl # check for pango
    +dnl #######################################################################
    +HAVE_PANGO="no"
    +PANGO_CFLAGS=""
    +PANGO_LIBS=""
    +PKG_CHECK_MODULES(PANGO, [pango], HAVE_PANGO="yes", HAVE_PANGO="no")
    +AC_SUBST(PANGO_CFLAGS)
    +AC_SUBST(PANGO_CFLAGS)
    +
    +if test x"$HAVE_PANGO" = x"yes" ; then
    + DEPENDENCIES="$DEPENDENCIES,pango"
    +fi
    +
    +dnl #######################################################################
    +dnl # check for cairo
    +dnl #######################################################################
    +HAVE_CAIRO="no"
    +CAIRO_CFLAGS=""
    +CAIRO_LIBS=""
    +PKG_CHECK_MODULES(CAIRO, [cairo], HAVE_CAIRO="yes", HAVE_CAIRO="no")
    +AC_SUBST(CAIRO_CFLAGS)
    +AC_SUBST(CAIRO_CFLAGS)
    +
    +if test x"$HAVE_CAIRO" = x"yes" ; then
    + DEPENDENCIES="$DEPENDENCIES,cairo"
    +fi
    +
    +dnl #######################################################################
    +dnl # Check for talkfilters
    +dnl #######################################################################
    +AC_CHECK_HEADER(talkfilters.h, HAVE_TALKFILTERS=yes, AC_MSG_WARN([
    +*** GNU Talk Filters is required to build the talkfilters plugin;
    +*** please make sure you have the GNU Talk Filters development headers installed.
    +*** The latest version of GNU Talk Filters is available at
    +*** http://www.hyperrealm.com/talkfilters/talkfilters.html.])
    +HAVE_TALKFILTERS=no
    +)
    +AM_CONDITIONAL(USE_TALKFILTERS, test x"$HAVE_TALKFILTERS" = x"yes")
    +if test x"$HAVE_TALKFILTERS" = x"yes"; then
    + dnl work out that the library exists
    + AC_CHECK_LIB(talkfilters, gtf_filter_count, TALKFILTERS_LIBS="-ltalkfilters")
    + AC_SUBST(TALKFILTERS_LIBS)
    +
    + DEPENDENCIES="$DEPENDENCIES,talkfilters"
    +fi
    +
    +dnl #######################################################################
    +dnl # Check for switchspell
    +dnl #######################################################################
    +gtkspell=yes
    +PKG_CHECK_MODULES(GTKSPELL, gtkspell-2.0 >= 2.0.2, [], [gtkspell=no])
    +AC_SUBST(GTKSPELL_CFLAGS)
    +AC_SUBST(GTKSPELL_LIBS)
    +
    +DEPENDENCIES="$DEPENDENCIES,gtkspell"
    +
    +BUILD_SWITCH_SPELL=no
    +
    +ASPELL_CFLAGS=""
    +APSELL_LIBS=""
    +
    +ENCHANT_CFLAGS=""
    +ENCHANT_LIBS=""
    +
    +if test x"$gtkspell" = x"yes" ; then
    + AC_MSG_CHECKING([which backend gtkspell is compiled with])
    +
    + $PKG_CONFIG --static --libs gtkspell-2.0 | grep -q enchant
    + if test $? -eq 0 ; then
    + AC_MSG_RESULT([enchant])
    +
    + PKG_CHECK_MODULES(ENCHANT,
    + [enchant],
    + [BUILD_SWITCH_SPELL=yes],
    + [BUILD_SWITCH_SPELL=no])
    +
    + AC_DEFINE(HAVE_ENCHANT, 1, [define if we've found enchant])
    +
    + DEPENDENCIES="$DEPENDENCIES,enchant"
    + else
    + AC_MSG_RESULT([aspell])
    +
    + 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],
    + [ASPELL_LIBS="-laspell"
    + BUILD_SWITCH_SPELL=yes],
    + [BUILD_SWITCH_SPELL=no])
    +
    + DEPENDENCIES="$DEPENDENCIES,aspell"
    + else
    + BUILD_SWITCH_SPELL=no
    + fi
    + fi
    +fi
    +
    +if test x"$BUILD_SWITCH_SPELL" = x"no" ; then
    + ASPELL_CFLAGS=""
    + ASPELL_LIBS=""
    +
    + ENCHANT_CFLAGS=""
    + ENCHANT_LIBS=""
    +
    + AM_CONDITIONAL(BUILD_SWITCH_SPELL, false)
    +else
    + AM_CONDITIONAL(BUILD_SWITCH_SPELL, true)
    +fi
    +
    +AC_SUBST(ASPELL_CFLAGS)
    +AC_SUBST(ASPELL_LIBS)
    +
    +AC_SUBST(ENCHANT_CFLAGS)
    +AC_SUBST(ENCHANT_LIBS)
    +
    +dnl #######################################################################
    +dnl # Check for xmms
    +dnl #######################################################################
    +XMMS_LIBS=""
    +XMMS_CFLAGS=""
    +HAVE_XMMS="no"
    +
    +AC_PATH_PROG(XMMS_CONFIG, xmms-config, no)
    +if test x"$XMMS_CONFIG" != x"no" ; then
    + AC_MSG_CHECKING([for xmms >= 1.0.0])
    + # check the version of xmms config we found
    + XMMS_VERSION=`$XMMS_CONFIG --version`
    + if test x"$XMMS_VERSION" != x"" ; then
    + XMMS_MAJOR=`echo $XMMS_VERSION | cut -d. -f1`
    + XMMS_MINOR=`echo $XMMS_VERSION | cut -d. -f2`
    +
    + if test $XMMS_MAJOR -ge 1 -a $XMMS_MINOR -ge 0 ; then
    + XMMS_LIBS=`$XMMS_CONFIG --libs`
    + XMMS_CFLAGS=`$XMMS_CONFIG --cflags`
    +
    + HAVE_XMMS="yes"
    + fi
    + fi
    +
    + if test x"$HAVE_XMMS" = x"yes" ; then
    + AC_MSG_RESULT([yes])
    +
    + DEPENDENCIES="$DEPENDENCIES,xmms"
    + else
    + AC_MSG_RESULT([no])
    + fi
    +fi
    +AM_CONDITIONAL(USE_XMMS, test x"$HAVE_XMMS" = x"yes")
    +AC_SUBST(XMMS_LIBS)
    +AC_SUBST(XMMS_CFLAGS)
    +
    +dnl #######################################################################
    +dnl # Check for some basic headers
    +dnl #######################################################################
    +AC_CHECK_HEADERS(regex.h)
    +
    +dnl #######################################################################
    +dnl # Disable installation of translation files
    +dnl #######################################################################
    +AC_ARG_ENABLE(nls, AC_HELP_STRING([--enable-nls], [enable installation of translation files]), enable_i18n="$enableval", enable_i18n=yes)
    +
    +AM_CONDITIONAL(INSTALL_I18N, test "x$enable_i18n" = "xyes")
    +
    +dnl #######################################################################
    +dnl # Version stuff
    +dnl #######################################################################
    +AC_CONFIG_COMMANDS_PRE([
    + if test -e VERSION; then
    + cp -p VERSION VERSION.ac-save
    + fi
    +])
    +
    +AC_CONFIG_COMMANDS_POST([
    + cmp VERSION VERSION.ac-save || touch -r VERSION.ac-save VERSION
    + rm -f VERSION.ac-save
    +])
    +
    +dnl #######################################################################
    +dnl # plugin_pack.py has already done our heavy lifting from the boot
    +dnl # strap. So we'll include our config file it created and call it to
    +dnl # determine our build directories
    +dnl #######################################################################
    +AC_PATH_PROG([PYTHON], [python], [no])
    +
    +dnl # include the config file we created during bootstrapping
    +m4_include([plugin_pack.m4])
    +
    +dnl #######################################################################
    +dnl # Finish up
    +dnl #######################################################################
    +AC_OUTPUT([Makefile
    + common/Makefile
    + doc/Makefile
    + po/Makefile.in
    + VERSION
    + plugin_pack.spec
    +])
    +
    +dnl #######################################################################
    +dnl # Ouput!!
    +dnl #######################################################################
    +echo;
    +echo $PACKAGE $VERSION Configuration complete
    +echo;
    +echo Debugging enabled................: $enable_debug
    +echo;
    +
    +echo Build purple plugins.............: $HAVE_PURPLE
    +if test x"$HAVE_PURPLE" = x"yes" ; then
    + echo Installing purple plugins to.....: `eval eval echo $PURPLE_LIBDIR`
    + echo Installing purple plugin data to.: `eval eval echo $PURPLE_DATADIR`
    + echo Purple plugins to be built.......:
    + eval $PP_PURPLE_BUILD
    +fi
    +echo;
    +
    +echo Build pidgin plugins.............: $HAVE_PIDGIN
    +if test x"$HAVE_PIDGIN" = x"yes" ; then
    + echo Installing pidgin plugins to.....: `eval eval echo $PIDGIN_LIBDIR`
    + echo Installing pidgin plugin data to.: `eval eval echo $PIDGIN_DATADIR`
    + echo Pidgin plugins to be built.......:
    + eval $PP_PIDGIN_BUILD
    +fi
    +echo;
    +
    +echo Build finch plugins..............: $HAVE_FINCH
    +if test x"$HAVE_FINCH" = x"yes" ; then
    + echo Installing finch plugins to......: `eval eval echo $FINCH_LIBDIR`
    + echo Installing finch plugin data to..: `eval eval echo $FINCH_DATADIR`
    + echo Finch plugins to be built........: none - THIS IS NORMAL
    + # uncomment this when we have finch plugins
    + # eval $PP_FINCH_BUILD
    +fi
    +echo;
    +
    +echo Type make to compile
    +echo;
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/convbadger/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +convbadgerdir = $(PIDGIN_LIBDIR)
    +
    +convbadger_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +convbadger_LTLIBRARIES = convbadger.la
    +
    +convbadger_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GLIB_LIBS)
    +
    +convbadger_la_SOURCES = convbadger.c
    +
    +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/convbadger/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for convbadger plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = convbadger
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/convbadger/convbadger.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,206 @@
    +/*
    + * ConvBadger - Adds the protocol icon to the menu tray of a conversation
    + * 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
    + * 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 <gtk/gtk.h>
    +
    +#include <conversation.h>
    +#include <signals.h>
    +
    +#include <gtkmenutray.h>
    +#include <gtkplugin.h>
    +#include <gtkutils.h>
    +
    +/******************************************************************************
    + * Structs
    + *****************************************************************************/
    +typedef struct {
    + PidginWindow *win;
    + PurpleConversation *conv;
    + GtkWidget *icon;
    +} ConvBadgerData;
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static GHashTable *data = NULL;
    +
    +/******************************************************************************
    + * helpers
    + *****************************************************************************/
    +static void
    +conv_badger_data_free(ConvBadgerData *cbd) {
    + cbd->win = NULL;
    + cbd->conv = NULL;
    +
    + if(cbd->icon && GTK_IS_IMAGE(cbd->icon))
    + gtk_widget_destroy(cbd->icon);
    +
    + g_free(cbd);
    +
    + cbd = NULL;
    +}
    +
    +static void
    +conv_badger_data_free_helper(gpointer k, gpointer v, gpointer d) {
    + ConvBadgerData *cbd = (ConvBadgerData *)v;
    +
    + conv_badger_data_free(cbd);
    +}
    +
    +static void
    +conv_badger_update(PidginWindow *win, PurpleConversation *conv) {
    + ConvBadgerData *cbd = NULL;
    + GdkPixbuf *pixbuf = NULL;
    + PurpleAccount *account = NULL;
    +
    + g_return_if_fail(win);
    + g_return_if_fail(conv);
    +
    + cbd = g_hash_table_lookup(data, win);
    +
    + if(cbd == NULL) {
    + cbd = g_new0(ConvBadgerData, 1);
    +
    + cbd->win = win;
    +
    + cbd->icon = gtk_image_new();
    + 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;
    +
    + account = purple_conversation_get_account(conv);
    + pixbuf = pidgin_create_prpl_icon(account, PIDGIN_PRPL_ICON_SMALL);
    + gtk_image_set_from_pixbuf(GTK_IMAGE(cbd->icon), pixbuf);
    + g_object_unref(G_OBJECT(pixbuf));
    +
    + g_hash_table_insert(data, win, cbd);
    +}
    +
    +/******************************************************************************
    + * Callbacks
    + *****************************************************************************/
    +static void
    +conv_badger_conv_created_cb(PurpleConversation *conv, gpointer data) {
    + PidginConversation *pconv = PIDGIN_CONVERSATION(conv);
    + PidginWindow *win = pidgin_conv_get_window(pconv);
    +
    + conv_badger_update(win, conv);
    +}
    +
    +static void
    +conv_badger_conv_destroyed_cb(PurpleConversation *conv, gpointer data) {
    +}
    +
    +static void
    +conv_badger_conv_switched_cb(PurpleConversation *conv, gpointer data) {
    + PidginConversation *pconv = PIDGIN_CONVERSATION(conv);
    + PidginWindow *win = pidgin_conv_get_window(pconv);
    +
    + conv_badger_update(win, conv);
    +}
    +
    +/******************************************************************************
    + * Plugin Stuff
    + *****************************************************************************/
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + void *conv_handle = purple_conversations_get_handle();
    +
    + data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
    + NULL, NULL);
    +
    + purple_signal_connect(conv_handle, "conversation-created", plugin,
    + PURPLE_CALLBACK(conv_badger_conv_created_cb), NULL);
    + purple_signal_connect(conv_handle, "deleting-conversation", plugin,
    + PURPLE_CALLBACK(conv_badger_conv_destroyed_cb), NULL);
    +
    + purple_signal_connect(pidgin_conversations_get_handle(),
    + "conversation-switched", plugin,
    + PURPLE_CALLBACK(conv_badger_conv_switched_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + g_hash_table_foreach(data, conv_badger_data_free_helper, NULL);
    +
    + g_hash_table_destroy(data);
    +
    + data = NULL;
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info = {
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + PIDGIN_PLUGIN_TYPE,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + "gtk-plugin_pack-convbadger",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + 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 = _("Conversation Badger");
    + info.summary = _("Badges conversations with the protocol icon.");
    + info.description = _("Badges conversations with the protocol icon.");
    +}
    +
    +PURPLE_INIT_PLUGIN(convbadger, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/convbadger/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Conversation Badger]
    +type=default
    +depends=pidgin
    +provides=convbadger
    +summary=Badges conversations with the protocol icon.
    +description=%(summary)s
    +authors=Gary Kramlich
    +introduced=2.0.0
    +notes=Completed for 2.1.0, buildsystem issues fixed in 2.1.1.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dewysiwygification/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -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\n", 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\n", 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)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dewysiwygification/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[DeWYSIWYGification]
    +type=default
    +depends=purple
    +provides=dewysiwygification
    +summary=Lets you type in HTML without it being escaped
    +description=%(summary)s This will not work well for some protocols. Use "&lt;" for a literal "<".
    +authors=Tim Ringenbach
    +introduced=2.2.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dice/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +dicedir = $(PURPLE_LIBDIR)
    +
    +dice_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +dice_LTLIBRARIES = dice.la
    +
    +dice_la_SOURCES = \
    + dice.c
    +
    +dice_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/dice/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for dice plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = dice
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dice/dice.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,387 @@
    +/*
    + * Adds a command to roll an arbitrary number of dice with an arbitrary
    + * number of sides
    + * 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
    + * 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 <time.h>
    +#include <stdlib.h>
    +
    +#include <cmds.h>
    +#include <conversation.h>
    +#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)
    +{
    + PurpleCmdStatus ret;
    + gchar *str = NULL, *newcmd = 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;
    +
    + str = old_school_roll(dice, sides);
    + }
    + }
    +
    +#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(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("");
    + 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: */
    + }
    +
    + 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
    +
    + newcmd = g_strdup_printf("me rolls %s", str);
    +
    + ret = purple_cmd_do_command(conv, newcmd, newcmd, &error);
    +
    + g_free(str);
    + g_free(newcmd);
    +
    + return ret;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + const gchar *help;
    +
    + 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 |
    + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
    + 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)
    +{
    + purple_cmd_unregister(dice_cmd_id);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + "core-plugin_pack-dice",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + 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 = _("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. "
    + "Now supports dice notation! /help dice for details");
    +
    +}
    +
    +PURPLE_INIT_PLUGIN(dice, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/dice/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Dice]
    +type=default
    +depends=purple
    +provides=dice
    +summary=Rolls dice in a chat or im
    +description=Adds a command (/dice) to roll an arbitrary number of dice with an arbitrary number of sides. Now supports dice notation! /help dice for details
    +authors=Gary Kramlich
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/difftopic/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +difftopicdir = $(PIDGIN_LIBDIR)
    +
    +difftopic_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +difftopic_LTLIBRARIES = difftopic.la
    +
    +difftopic_la_SOURCES = \
    + difftopic.c
    +
    +difftopic_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)/pidgin/\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/difftopic/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for difftopic plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = difftopic
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/difftopic/difftopic.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,224 @@
    +/*
    + * DiffTopic - Show the old topic when the topic in a chat room changes.
    + * 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
    + * 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"
    +
    +#define PLUGIN_ID "gtk-plugin_pack-difftopic"
    +#define PLUGIN_STATIC_NAME "difftopic"
    +#define PLUGIN_AUTHOR "Sadrul H Chowdhury <sadrul@users.sourceforge.net>"
    +
    +/* System headers */
    +#include <ctype.h>
    +#include <string.h>
    +
    +/* Pidgin Headers */
    +#include <gtkconv.h>
    +#include <gtkimhtml.h>
    +#include <gtkplugin.h>
    +
    +#define SAME(a,b) ((isalnum((a)) && isalnum((b))) || (!isalnum((a)) && !isalnum((b))))
    +
    +#define SIZE 1000 /* XXX: Let's assume this always holds */
    +static int lcs[SIZE][SIZE];
    +
    +static GList *
    +split_string(const char *string)
    +{
    + GList *ret = NULL;
    + const char *start, *end;
    +
    + start = end = string;
    + while (*start) {
    + while (*end && SAME(*start, *end)) {
    + end++;
    + }
    + ret = g_list_append(ret, g_strndup(start, end - start));
    + start = end;
    + }
    +
    + return ret;
    +}
    +
    +static GString *
    +g_string_prepend_printf(GString *string, const char *format, ...)
    +{
    + va_list args;
    + char *str;
    +
    + va_start(args, format);
    + str = g_strdup_vprintf(format, args);
    + va_end(args);
    + string = g_string_prepend(string, str);
    + g_free(str);
    + return string;
    +}
    +
    +/* Let's LCS */
    +static void
    +have_fun(GtkIMHtml *imhtml, const char *old, const char *new)
    +{
    + GList *lold, *lnew;
    + int i, j, m, n;
    + GString *from, *to;
    + char *text;
    +
    + lold = split_string(old);
    + lnew = split_string(new);
    +
    + memset(lcs, 0, sizeof(lcs));
    + n = g_list_length(lold);
    + m = g_list_length(lnew);
    +
    + for (i = 1; i <= n; i++)
    + lcs[i][0] = 0;
    +
    + for (j = 1; j <= m; j++)
    + lcs[0][j] = 0;
    +
    + for (i = 1; i <= n; i++) {
    + for (j = 1; j <= m; j++) {
    + if (strcmp(g_list_nth_data(lold, i-1), g_list_nth_data(lnew, j-1)) == 0)
    + lcs[i][j] = lcs[i-1][j-1] + 1;
    + else
    + lcs[i][j] = (lcs[i-1][j] >= lcs[i][j-1] ? lcs[i-1][j] : lcs[i][j-1]);
    + }
    + }
    +
    + from = g_string_new(NULL);
    + to = g_string_new(NULL);
    + i = n;
    + j = m;
    + while (i && j) {
    + if (lcs[i][j] == lcs[i-1][j]) {
    + from = g_string_prepend_printf(from, "<font color='red'><B>%s</B></font>",
    + g_list_nth_data(lold, i - 1));
    + i--;
    + } else if (lcs[i][j] == lcs[i][j-1]) {
    + to = g_string_prepend_printf(to, "<font color='green'><B>%s</B></font>",
    + g_list_nth_data(lnew, j - 1));
    + j--;
    + } else if (lcs[i][j] == lcs[i-1][j-1] + 1) {
    + from = g_string_prepend_printf(from, "<B>%s</B>", g_list_nth_data(lold, i-1));
    + to = g_string_prepend_printf(to, "<B>%s</B>", g_list_nth_data(lnew, j-1));
    + i--;
    + j--;
    + }
    + }
    +
    + while (j) {
    + to = g_string_prepend_printf(to, "<font color='green'><B>%s</B></font>",
    + g_list_nth_data(lnew, j - 1));
    + j--;
    + }
    +
    + while (i) {
    + from = g_string_prepend_printf(from, "<font color='red'><B>%s</B></font>",
    + g_list_nth_data(lold, i - 1));
    + i--;
    + }
    +
    + text = g_strdup_printf(_("<BR>Topic changed from: <BR>%s<BR>To:<BR>%s"), from->str, to->str);
    + gtk_imhtml_append_text(GTK_IMHTML(imhtml), text, 0);
    + g_free(text);
    +
    + g_string_free(from, TRUE);
    + g_string_free(to, TRUE);
    + g_list_foreach(lold, (GFunc)g_free, NULL);
    + g_list_foreach(lnew, (GFunc)g_free, NULL);
    + g_list_free(lold);
    + g_list_free(lnew);
    +}
    +
    +static void
    +topic_changed(PurpleConversation *conv, const char *who, const char *what)
    +{
    + PidginConversation *gtkconv = conv->ui_data;
    + char *old;
    +
    + old = g_object_get_data(G_OBJECT(gtkconv->imhtml), "difftopic");
    + if (old && what) {
    + have_fun(GTK_IMHTML(gtkconv->imhtml), old, what);
    + }
    + g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "difftopic", g_strdup(what), (GDestroyNotify)g_free);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + purple_signal_connect(purple_conversations_get_handle(), "chat-topic-changed",
    + plugin, PURPLE_CALLBACK(topic_changed), NULL);
    + /* Set the topic to the opened chat windows */
    + 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,
    + 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 = _("DiffTopic");
    + info.summary = _("Show the old topic when the topic in a chat room changes.");
    + info.description = _("Show the old topic when the topic in a chat room changes.");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/difftopic/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[DiffTopic]
    +type=default
    +depends=pidgin
    +provides=difftopic
    +summary=Show the old topic when the topic in a chat room changes
    +description=%(summary)s
    +authors=Sadrul Habib Chowdhury
    +introduced=1.0beta4
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/doc/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,2 @@
    +EXTRA_DIST = \
    + quickhack.txt
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/doc/quickhack.txt Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +Adding a plugin to the plugin pack is actually quite simple.
    +
    +Each plugin needs to reside in its own directory. For it to be autodected
    +this directory must contain a file named .plugin. The contents of this file
    +do not matter; the existance of the file just lets the configure script know
    +that this directory holds a plugin.
    +
    +However, there is still one minor thing to do in configure.ac -- that is to
    +add <directory>/Makefile to the AC_OUTPUT line.
    +
    +If you want the plugin to build by default, create a file named .build in the
    +directory for the plugin.
    +
    +For the i18n support, plugins need to be added to po/POTFILES.in.
    +
    +All plugins should have both a .plugin file and be added AC_OUTPUT in
    +configure.ac. This way when working on new plugins, the plugin will not build
    +by default but can still be compiled by 'cd'ing into its directory and typing
    +'make'.
    +
    +If you are building your own distribution tarballs, ensure that you have at
    +very least 'EXTRA_DIST = .build .plugin' in your plugin's Makefile.am.
    +Otherwise things no workies and you end up writing garbage in quickhack.txt to
    +remind yourself.
    +
    +There are templates for Makefile.am, core plugins, and gtk plugins in the
    +directory common/.
    +
    +Happy Hacking
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/eight_ball/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +eight_balldir = $(PURPLE_LIBDIR)
    +
    +eight_ball_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +eight_ball_LTLIBRARIES = eight_ball.la
    +
    +eight_ball_la_SOURCES = eight_ball.c
    +
    +eight_ball_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS) \
    + $(GLIB_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/eight_ball/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for eight_ball plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = eight_ball
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/eight_ball/eight_ball.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,426 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * eight_ball: Provides Magic 8-ball-like functionality
    + *
    + * 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"
    +
    +/* libc */
    +#include <ctype.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <time.h>
    +
    +/* Purple */
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <plugin.h>
    +
    +/* TODO: add to these, but be careful NOT to use real 8-ball messages!!! */
    +static const gchar *eight_ball_strings[] = {
    + "Unclear",
    + "Certainly",
    + "Ask again later",
    + "Not likely",
    + "It's possible",
    +};
    +
    +/* feel free to add */
    +static const gchar *sg_ball_strings[] = {
    + "Indeed",
    + "I got nothin\'",
    + "Ask me tomorrow", /* from "Window of Opportunity" */
    + "According to quantum physics, it's theoretically possible...",
    + "That's what you get for dickin' around", /* from "Fallout" */
    + "Jaffa! Cree!",
    + "Tec ma te",
    + "Kneel before your god!",
    + "I believe a medical attack could be successful."
    +};
    +
    +static const gchar *fullcrap_strings[] = {
    + "you are only fullcrap",
    + "this is only fooling blabber",
    + "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.",
    + "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,
    + 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,
    + gchar *error, void *data)
    +{
    + GString *msgstr = NULL;
    + gchar *msgprefix = NULL;
    + const gchar **msgs = NULL;
    + gint numstrings = 0, index = 0;
    +
    + msgstr = g_string_new("");
    +
    + srand(time(NULL));
    +
    + if(!strcmp(cmd, "sgball")) {
    + numstrings = sizeof(sg_ball_strings) / sizeof(sg_ball_strings[0]);
    + msgprefix = "The Purple Stargate Ball says";
    + msgs = sg_ball_strings;
    + } else if(!strcmp(cmd, "fullcrap")) {
    + 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";
    + msgs = eight_ball_strings;
    + }
    +
    + if(*args == NULL || args[0] == NULL)
    + index = rand() % numstrings;
    + else
    + index = atoi(args[0]);
    +
    + if(index < 0)
    + index *= -1;
    +
    + if(index > numstrings)
    + index %= numstrings;
    +
    + 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:
    + purple_conv_im_send(PURPLE_CONV_IM(conv), msgstr->str);
    + break;
    + case PURPLE_CONV_TYPE_CHAT:
    + purple_conv_chat_send(PURPLE_CONV_CHAT(conv), msgstr->str);
    + break;
    + default:
    + g_string_free(msgstr, TRUE);
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + g_string_free(msgstr, TRUE);
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + 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 |
    + PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL,
    + PURPLE_CMD_FUNC(eight_ball_cmd_func),
    + eight_ball_help, NULL);
    +
    + sg_ball_cmd_id = purple_cmd_register("sgball", "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),
    + sg_ball_help, NULL);
    +
    + fullcrap_cmd_id = purple_cmd_register("fullcrap", "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),
    + 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;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + 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;
    +}
    +
    +static PurplePluginInfo eight_ball_info =
    +{
    + PURPLE_PLUGIN_MAGIC, /* Do you believe in magic? */
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + "core-plugin_pack-eight_ball",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "John Bailey <rekkanoryo@rekkanoryo.org>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + 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 */
    +
    + eight_ball_info.name = _("Magic 8 Ball");
    + eight_ball_info.summary = _("Provides Magic 8-ball like functionality");
    + eight_ball_info.description = _("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.");
    +
    + return;
    +}
    +
    +PURPLE_INIT_PLUGIN(eight_ball, init_plugin, eight_ball_info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/eight_ball/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Magic 8 Ball]
    +type=default
    +depends=purple
    +provides=eight_ball
    +summary=Provides Magic 8-ball like functionality
    +description=%(summary)s with the /8ball command, as well as similar functionality for common Stargate words or phrases with the /sg-ball command.
    +authors=John Bailey
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/enhancedhist/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +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 Thu Aug 27 19:46:46 2009 -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 Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,414 @@
    +/*
    + * 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) {
    + GSList *cur;
    +
    + for(cur = buddies; cur; cur = cur->next) {
    + PurpleBlistNode *node = cur->data;
    +
    + if(node && (node->prev || node->next)) {
    + PurpleBlistNode *node2;
    +
    + for(node2 = node->parent->child; node2; node2 = node2->next) {
    + logs = g_list_concat(purple_log_get_logs(PURPLE_LOG_IM,
    + purple_buddy_get_name((PurpleBuddy *)node2),
    + purple_buddy_get_account((PurpleBuddy *)node2)), logs);
    + }
    +
    + break;
    + }
    + }
    +
    + if(logs)
    + logs = g_list_sort(logs, purple_log_compare);
    + else
    + logs = purple_log_get_logs(PURPLE_LOG_IM, name, account);
    +
    + } 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)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/enhancedhist/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Enhanced History]
    +type=default
    +depends=pidgin
    +provides=enhancedhist
    +summary=An enhanced version of the history plugin
    +description=%(summary)s Grants ability to select the number of previous conversations to show instead of just one.
    +authors=Andrew Pangborn
    +introduced=2.3.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/findip/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +findipdir = $(PURPLE_LIBDIR)
    +
    +findip_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +findip_LTLIBRARIES = findip.la
    +
    +findip_la_SOURCES = \
    + findip.c
    +
    +findip_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/findip/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for dice plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = findip
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/findip/findip.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,178 @@
    +/*
    + * Find IP - Find the IP of a person in the buddylist
    + * 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
    + * 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"
    +
    +#define PLUGIN_ID "core-plugin_pack-findip"
    +#define PLUGIN_STATIC_NAME "findip"
    +#define PLUGIN_AUTHOR "someone <someone@somewhere.tld>"
    +
    +/* System headers */
    +#include <glib.h>
    +
    +/* Purple headers */
    +#include <plugin.h>
    +#include <blist.h>
    +#include <eventloop.h>
    +#include <server.h>
    +#include <version.h>
    +
    +/* 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_pack/" PLUGIN_STATIC_NAME
    +#define PREF_NOTIFY PREF_ROOT "/notify"
    +
    +static gboolean
    +show_ip(gpointer node)
    +{
    + PurpleBuddy *buddy;
    + PurpleConversation *conv;
    +
    + buddy = (PurpleBuddy*)node;
    + if (buddy->account == NULL || buddy->account->gc == NULL)
    + return FALSE;
    +
    + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, buddy->account, buddy->name);
    + purple_conversation_write(conv, NULL, _("Looked up IP: 127.0.0.1\n"),
    + PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM,
    + time(NULL));
    + if (purple_prefs_get_bool(PREF_NOTIFY))
    + serv_send_im(buddy->account->gc, buddy->name, _("Yo! What's your IP?"), 0);
    + return FALSE;
    +}
    +
    +static void
    +find_ip(PurpleBlistNode *node, gpointer plugin)
    +{
    + PurpleConversation *conv;
    + PurpleBuddy *buddy;
    +
    + if (PURPLE_BLIST_NODE_IS_CONTACT(node))
    + node = (PurpleBlistNode*)purple_contact_get_priority_buddy((PurpleContact*)node);
    + if (!PURPLE_BLIST_NODE_IS_BUDDY(node))
    + return;
    +
    + purple_timeout_add_seconds(5, show_ip, node);
    +
    + buddy = (PurpleBuddy*)node;
    + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, buddy->account, buddy->name);
    + purple_conversation_write(conv, NULL, _("Looking up the IP ...\n"),
    + PURPLE_MESSAGE_NOTIFY | PURPLE_MESSAGE_SYSTEM,
    + time(NULL));
    +}
    +
    +static void
    +context_menu(PurpleBlistNode *node, GList **menu, gpointer plugin)
    +{
    + PurpleMenuAction *action;
    +
    + if (!PURPLE_BLIST_NODE_IS_BUDDY(node) && !PURPLE_BLIST_NODE_IS_CONTACT(node))
    + return;
    +
    + action = purple_menu_action_new(_("Find IP"),
    + PURPLE_CALLBACK(find_ip), plugin, NULL);
    + (*menu) = g_list_prepend(*menu, action);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + purple_signal_connect(purple_blist_get_handle(), "blist-node-extended-menu", plugin,
    + PURPLE_CALLBACK(context_menu), plugin);
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_NOTIFY,
    + _("Notify the user that you are trying to get the IP"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo pref_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +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 */
    + &pref_info, /* prefs_info */
    + NULL /* actions */
    +};
    +
    +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 = _("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);
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/findip/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Find IP]
    +type=default
    +depends=purple
    +provides=findip
    +summary=Find the IP of a person in the buddylist
    +description=%(summary)s This doesn't really work.
    +authors=Sadrul Habib Chowdhury
    +introduced=2.2.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/flip/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +flipdir = $(PURPLE_LIBDIR)
    +
    +flip_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +flip_LTLIBRARIES = flip.la
    +
    +flip_la_SOURCES = \
    + flip.c
    +
    +flip_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS) \
    + $(GLIB_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/flip/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for flip plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = flip
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/flip/flip.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,119 @@
    +/*
    + * Adds a command to flip a coin in a conversation and outputs the result
    + * Copyright (C) 2005 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 02111-1301, USA.
    + */
    +
    +#include "../common/pp_internal.h"
    +
    +#include <time.h>
    +#include <stdlib.h>
    +
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <plugin.h>
    +
    +static PurpleCmdId flip_cmd_id = 0;
    +
    +static PurpleCmdRet
    +flip_it(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar *error, void *data)
    +{
    + gboolean heads = FALSE;
    + gchar *msg;
    +
    + srand(time(NULL));
    +
    + heads = rand() % 2;
    +
    + msg = g_strdup_printf("Flips a coin: %s", (heads) ? "HEADS" : "TAILS");
    +
    + if(conv->type == PURPLE_CONV_TYPE_IM)
    + purple_conv_im_send(PURPLE_CONV_IM(conv), msg);
    + else if(conv->type == PURPLE_CONV_TYPE_CHAT)
    + purple_conv_chat_send(PURPLE_CONV_CHAT(conv), msg);
    +
    + g_free(msg);
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + flip_cmd_id = purple_cmd_register("flip", "", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT,
    + NULL, PURPLE_CMD_FUNC(flip_it),
    + _("Outputs the results of flipping a coin"),
    + NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + purple_cmd_unregister(flip_cmd_id);
    +
    + return TRUE;
    +}
    +
    +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 */
    +
    + "core-plugin_pack-flip", /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /** summary */
    + NULL, /** description */
    + "Gary Kramlich <grim@reaperworld.com>", /**< author */
    + PP_WEBSITE, /**< homepage */
    +
    + 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 = _("Coin Flip");
    + info.summary = _("Flips a coin and outputs the result");
    + info.description = _("Adds a command (/flip) to flip a coin and "
    + "outputs the result in the active conversation");
    +}
    +
    +PURPLE_INIT_PLUGIN(flip, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/flip/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Coin Flip]
    +name=Coin Flip
    +type=default
    +depends=purple
    +provides=flip
    +summary=Flips a coin and outputs the result
    +description=Adds a command (/flip) to flip a coin and outputs the result in the active conversation
    +authors=Gary Kramlich
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gRIM/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +gRIMdir = $(PIDGIN_LIBDIR)
    +
    +gRIM_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +gRIM_LTLIBRARIES = gRIM.la
    +
    +gRIM_la_SOURCES = \
    + gRIM.c
    +
    +gRIM_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gRIM/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for gRIM plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = gRIM
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gRIM/gRIM.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,380 @@
    +/*
    + * 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-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
    + * 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.
    + */
    +
    +/*
    + * 08:14 < OgreBoy> well, gaim isn't a gnome app, so it shouldn't be in /opt/gnome anyway
    + * 08:17 < flurble> not a gnome app? but it begins with g!
    + * 08:18 < Bleeter> Google is a gnome app!
    + * 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>
    +#include <string.h>
    +#include <stdlib.h>
    +
    +#include <cmds.h>
    +#include <debug.h>
    +
    +#include <gtkconv.h>
    +#include <gtkimhtml.h>
    +#include <gtkplugin.h>
    +
    +
    +#if GLIB_CHECK_VERSION(2,6,0)
    +#include <glib/gstdio.h>
    +#else
    +#include <sys/stat.h>
    +#define g_fopen fopen
    +#define g_rename rename
    +#define g_stat stat
    +#define g_unlink unlink
    +#endif
    +
    +#define MAX_LENGTH 1024
    +
    +#define COMPOSER "Howard Goodall"
    +#define LYRICISTS "Kim Fuller and Doug Naylor"
    +
    +const char *LYRICS[] = {
    +"If you're in trouble he will save the day",
    +"He's brave and he's fearless come what may",
    +"Without him the mission would go astray",
    +"",
    +"He's Arnold, Arnold, Arnold Rimmer",
    +"Without him life would be much grimmer",
    +"He's handsome, trim and no-one slimmer",
    +"He will never need a Zimmer",
    +"",
    +"Ask Arnold, Arnold, Arnold Rimmer",
    +"More reliable than a garden trimmer",
    +"He's never been mistaken for Yul Brynner",
    +"He's not bald and his head doesn't glimmer",
    +"",
    +"Master of the wit and the repartee",
    +"His command of space directives is uncanny",
    +"How come he's such a genius? Don't ask me",
    +"",
    +"Ask Arnold, Arnold, Arnold Rimmer",
    +"He's also a fantastic swimmer",
    +"And if you play your cards right",
    +"Then he just might come around for dinner",
    +"",
    +"He's Arnold, Arnold, Arnold Rimmer",
    +"No rhymes left now apart from quimmer",
    +"I hope they fade us out before we get to shlimmer",
    +"Fade out you stupid brimmer",
    +NULL
    +};
    +
    +struct lyrics_and_info {
    + GList *lyric; /* The line of lyric */
    + gboolean verse; /* TRUE for Verse, FALSE for Chorus */
    + guint time; /* Time period the lyric lasts for, in milliseconds */
    + guint gap; /* Gap between last lyric and the next, in milliseconds */
    +};
    +
    +struct timeout_data
    +{
    + struct lyrics_and_info *info;
    + PurpleConversation *conv;
    +};
    +
    +static PurpleCmdId rim_cmd_id = 0, base_cmd_id = 0;
    +
    +static gboolean
    +timeout_func_cb(struct timeout_data *data)
    +{
    + char *msg;
    + const char *color;
    + PidginConversation *gtkconv = PIDGIN_CONVERSATION(data->conv);
    + GtkIMHtml *imhtml = GTK_IMHTML(gtkconv->entry);
    + GList *list;
    +
    + if (data->info->lyric == NULL) {
    + /* XXX: free the lyric if it was dyn-allocated */
    + g_free(data->info);
    + g_free(data);
    + return FALSE;
    + }
    +
    + color = imhtml->edit.forecolor;
    +
    + list = data->info->lyric;
    +
    + if (list->next == NULL) {
    + /* Is this Ugly or is this UGLY? */
    + int len = strlen(list->data);
    + GdkColor gdkcolor;
    + int inc_r, inc_g, inc_b;
    + char *s = list->data;
    +
    + if (!gdk_color_parse(color, &gdkcolor))
    + gdkcolor.red = gdkcolor.green = gdkcolor.blue = 0;
    +
    + inc_r = (255 - (gdkcolor.red >> 8))/len;
    + inc_g = (255 - (gdkcolor.green >> 8))/len;
    + inc_b = (255 - (gdkcolor.blue >> 8))/len;
    +
    + msg = g_strdup("");
    + while(*s) {
    + char *temp = msg;
    + char t[2] = {*s++, 0};
    + char tag[16];
    +
    + snprintf(tag, sizeof(tag), "#%02x%02x%02x",
    + gdkcolor.red >> 8, gdkcolor.green >> 8, gdkcolor.blue >> 8);
    +
    + msg = g_strconcat(msg, "<font color=\"", tag, "\">", t, "</font>", NULL);
    + g_free(temp);
    +
    + gdkcolor.red += inc_r << 8;
    + gdkcolor.green += inc_g << 8;
    + gdkcolor.blue += inc_b << 8;
    + }
    + } else if (color)
    + msg = g_strdup_printf("<font color=\"%s\">%s</font>", color, (char *)list->data);
    + else
    + msg = g_strdup(*(char*)list->data ? (char*)list->data : "&nbsp;");
    +
    + if (purple_conversation_get_type(data->conv) == PURPLE_CONV_TYPE_IM)
    + purple_conv_im_send(PURPLE_CONV_IM(data->conv), msg);
    + else if (purple_conversation_get_type(data->conv) == PURPLE_CONV_TYPE_CHAT)
    + purple_conv_chat_send(PURPLE_CONV_CHAT(data->conv), msg);
    + g_free(msg);
    +
    + g_free(list->data);
    + data->info->lyric = list->next;
    + list->next = NULL;
    + g_list_free(list); /* XXX: perhaps just a g_free? */
    +
    + /* what stuff do you do about the verse? */
    +
    + return TRUE;
    +}
    +
    +static GList *
    +rim_get_file_lines(const char *filename)
    +{
    + GList *list = NULL;
    + FILE *file = NULL;
    + char str[MAX_LENGTH];
    +
    + file = g_fopen(filename, "r");
    +
    + if (!file) /* XXX: Show an error message that the file doesn't exist */
    + return NULL;
    +
    + while (fgets(str, MAX_LENGTH, file)) {
    + char *s = str + strlen(str) - 1;
    + if (*s == '\r' || *s == '\n')
    + *s = 0;
    + list = g_list_append(list, g_strdup(str));
    + }
    +
    + fclose(file);
    +
    + return list;
    +}
    +
    +static PurpleCmdRet
    +rim(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar *error, void *null)
    +{
    + struct timeout_data *data = g_new0(struct timeout_data, 1);
    + struct lyrics_and_info *info = g_new0(struct lyrics_and_info, 1);
    + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
    + gint source;
    +
    + /* XXX: Need to manually parse the arguments :-/ */
    + if (*args && *(args+1)) {
    + /* two parameters: filename duration (in seconds) */
    + info->lyric = rim_get_file_lines(*(args + 1));
    + sscanf(*args, "%d", &info->time);
    + info->time *= 1000;
    + } else if(*args) {
    + if(!g_ascii_strcasecmp(*args, "quit")) {
    + GList *list = NULL;
    + list = g_list_append(list, "Fine, I'll stop");
    + g_list_foreach(info->lyric, (GFunc)g_free, NULL);
    + g_list_free(info->lyric);
    + info->lyric = list;
    + info->verse = FALSE;
    + info->time = 5000;
    + }
    + else {
    + g_list_free(info->lyric);
    + info->lyric = NULL;
    + }
    + } else {
    + int i = 0;
    + GList *list = NULL;
    +
    + while (LYRICS[i]) {
    + list = g_list_append(list, g_strdup(LYRICS[i]));
    + i++;
    + }
    +
    + info->lyric = list;
    + info->verse = TRUE;
    + info->time = 60000;
    + }
    +
    + purple_debug_info("gRIM", "HINT: quit with quit\n");
    +
    + if (info->lyric == NULL) {
    + g_free(info);
    + g_free(data);
    + return PURPLE_CMD_STATUS_FAILED;
    + }
    +
    + info->gap = info->time / g_list_length(info->lyric);
    + if (info->gap < 5000)
    + info->gap = 5000;
    +
    + data->info = info;
    + data->conv = conv;
    +
    + /* XXX: make sure we stop when conv-is destroyed. */
    +
    + source = g_timeout_add(info->gap, (GSourceFunc)timeout_func_cb, data);
    + g_object_set_data_full(G_OBJECT(gtkconv->imhtml), "gRim:timer",
    + GINT_TO_POINTER(source), (GDestroyNotify)g_source_remove);
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + const gchar *help;
    +
    + /* should be completely mad and see if user has only one buddy (not a chat)
    + * on the blist and pluralise if appropriate */
    + help = _("gRIM: rim your pals\n"
    + "/rim &lt;duration-in-secs&gt; &lt;filename&gt;");
    +
    + rim_cmd_id = purple_cmd_register("rim", "ws", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS,
    + NULL, PURPLE_CMD_FUNC(rim),
    + help, NULL);
    +
    + /* THIS LINE IS NOT TRANSLATABLE. Patches to make it NLS capable will be
    + * rejected without response */
    + help = "gRIM: Take off every 'Zig'!!";
    + 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);
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + purple_cmd_unregister(rim_cmd_id);
    + purple_cmd_unregister(base_cmd_id);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC, /**< magic */
    + PURPLE_MAJOR_VERSION, /**< major version */
    + PURPLE_MINOR_VERSION, /**< minor version */
    + PURPLE_PLUGIN_STANDARD, /**< type */
    + PIDGIN_PLUGIN_TYPE, /**< ui_requirement */
    + 0, /**< flags */
    + NULL, /**< dependencies */
    + PURPLE_PRIORITY_DEFAULT, /**< priority */
    +
    + "gtk-plugin_pack-gRIM", /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /** summary */
    + NULL, /** description */
    + "Peter Lawler <bleeter from users.sf.net> and"
    + " Sadrul Habib Chowdhury <sadrul from users.sf.net>",
    + /**< authors */
    + PP_WEBSITE, /**< homepage */
    +
    + 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
    + info.name = _("gRIM");
    + info.summary = _("A completely stupid and pointless plugin");
    + info.description = _("Adds commands to annoy buddies with. Inspired by a dumb IRC convo and Red Dwarf.");
    +}
    +
    +/*
    +grim suggested I should /base this plugin. By adding this in, I should get the
    +guts of this thing able to handle different text inputs, so I thought it was
    +a good thing to do.
    +
    +For the moment, I've thrown in the skeleton command registration, and the text
    +here which I got from wikipedia.
    +
    + Narrator: In A.D. 2101, war was beginning.
    +
    + Captain: What happen ?
    + Mechanic: Somebody set up us the bomb.
    +
    + Operator: We get signal.
    + Captain: What !
    + Operator: Main screen turn on.
    + Captain: It's you !!
    + CATS: How are you gentlemen !!
    + CATS: All your base are belong to us.
    + CATS: You are on the way to destruction.
    + Captain: What you say !!
    + CATS: You have no chance to survive make your time.
    + CATS: Ha Ha Ha Ha ....
    +
    + Operator: Captain !!
    + Captain: Take off every 'Zig'!!
    + Captain: You know what you doing.
    + Captain: Move 'Zig'.
    + Captain: For great justice.
    +*/
    +
    +PURPLE_INIT_PLUGIN(flip, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/gRIM/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[gRIM]
    +type=default
    +depends=pidgin
    +provides=gRIM
    +summary=A completely stupid and pointless plugin
    +description=Adds commands to annoy buddies with. Inspired by a dumb IRC convo and Red Dwarf.
    +authors=Peter Lawler,Sadrul Habib Chowdhury
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/google/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +googledir = $(PURPLE_LIBDIR)
    +
    +google_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +google_LTLIBRARIES = google.la
    +
    +google_la_SOURCES = \
    + google.c
    +
    +google_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/google/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for the google plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = google
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/google/google.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,325 @@
    +/*
    + * Adds a command to return the first url for a google I'm feeling lucky search
    + * Copyright (C) 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 02111-1301, USA.
    + */
    +
    +#include "../common/pp_internal.h"
    +
    +#include <errno.h>
    +#include <string.h>
    +
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <plugin.h>
    +#include <proxy.h>
    +#include <util.h>
    +
    +static PurpleCmdId google_cmd_id = 0;
    +
    +#define GOOGLE_URL_FORMAT "http://%s/search?q=%s&btnI=I%%27m+Feeling+Lucky"
    +
    +/******************************************************************************
    + * Structs
    + *****************************************************************************/
    +typedef struct {
    + PurpleConversation *conv;
    + gchar *host;
    + gint port;
    + gchar *path;
    +
    + gchar *request;
    + gsize request_written;
    +
    + gint fd;
    + gint inpa;
    +
    + GString *response;
    +
    + PurpleProxyConnectData *conn_data;
    +} GoogleFetchUrlData;
    +
    +/******************************************************************************
    + * GoogleFetchUrlData API
    + *****************************************************************************/
    +static GoogleFetchUrlData *
    +google_fetch_url_data_new(const gchar *url) {
    + GoogleFetchUrlData *gfud = g_new0(GoogleFetchUrlData, 1);
    +
    + if(!purple_url_parse(url, &gfud->host, &gfud->port, &gfud->path, NULL,
    + NULL))
    + {
    + g_free(gfud);
    + return NULL;
    + }
    +
    + gfud->response = g_string_new("");
    +
    + return gfud;
    +}
    +
    +static void
    +google_fetch_url_data_free(GoogleFetchUrlData *gfud) {
    + g_free(gfud->host);
    + g_free(gfud->path);
    +
    + g_free(gfud->request);
    +
    + g_string_free(gfud->response, TRUE);
    +
    + if(gfud->inpa > 0)
    + purple_input_remove(gfud->inpa);
    +
    + if(gfud->fd >= 0)
    + close(gfud->fd);
    +
    + if(gfud->conn_data)
    + purple_proxy_connect_cancel(gfud->conn_data);
    +
    + g_free(gfud);
    +}
    +
    +/******************************************************************************
    + * The final result (hiding in the middle, very sneaky)
    + *****************************************************************************/
    +static void
    +google_output_url(GoogleFetchUrlData *gfud) {
    + gchar *str = NULL, *url_s = NULL, *url_e = NULL;
    + gsize len = 0;
    + const gchar *needle = "Location: ";
    +
    + /* if our conv has disappeared from under us, drop out */
    + if(!gfud->conv)
    + return;
    +
    + str = gfud->response->str;
    + len = gfud->response->len;
    +
    + url_s = g_strstr_len(str, len, needle);
    + if(!url_s)
    + return;
    +
    + len = strlen(url_s);
    + url_s += strlen(needle);
    +
    + url_e = g_strstr_len(url_s, len, "\r\n");
    + if(!url_e)
    + return;
    +
    + *url_e = '\0';
    +
    + if(gfud->conv->type == PURPLE_CONV_TYPE_IM)
    + purple_conv_im_send(PURPLE_CONV_IM(gfud->conv), url_s);
    + else if(gfud->conv->type == PURPLE_CONV_TYPE_CHAT)
    + purple_conv_chat_send(PURPLE_CONV_CHAT(gfud->conv), url_s);
    +}
    +
    +/******************************************************************************
    + * URL Stuff
    + *****************************************************************************/
    +static void
    +im_feeling_lucky_recv_cb(gpointer data, gint source, PurpleInputCondition c) {
    + GoogleFetchUrlData *gfud = (GoogleFetchUrlData *)data;
    + gint len;
    + gchar buff[4096];
    +
    + while((len = read(source, buff, sizeof(buff))) > 0)
    + gfud->response = g_string_append_len(gfud->response, buff, len);
    +
    + if(len < 0) {
    + if(errno == EAGAIN)
    + return;
    +
    + /* need to die here */
    +
    + return;
    + }
    +
    + if(len == 0) {
    + google_output_url(gfud);
    + google_fetch_url_data_free(gfud);
    + }
    +}
    +
    +static void
    +im_feeling_lucky_send_cb(gpointer data, gint source, PurpleInputCondition c) {
    + GoogleFetchUrlData *gfud = (GoogleFetchUrlData *)data;
    + gint len, total_len;
    +
    + total_len = strlen(gfud->request);
    +
    + len = write(gfud->fd, gfud->request + gfud->request_written,
    + total_len - gfud->request_written);
    +
    + if(len < 0) {
    + if(errno == EAGAIN)
    + return;
    +
    + /* need to die here */
    +
    + return;
    + }
    +
    + gfud->request_written += len;
    +
    + if(gfud->request_written < total_len)
    + return;
    +
    + /* done writing the request, now read the response */
    + purple_input_remove(gfud->inpa);
    + gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_READ,
    + im_feeling_lucky_recv_cb, gfud);
    +}
    +
    +static void
    +im_feeling_lucky_cb(gpointer data, gint source, const gchar *e) {
    + GoogleFetchUrlData *gfud = (GoogleFetchUrlData *)data;
    +
    + gfud->conn_data = NULL;
    +
    + if(source == -1) {
    + purple_debug_error("google", "unable to connect to %s: %s\n",
    + gfud->host, gfud->path);
    +
    + google_fetch_url_data_free(gfud);
    +
    + return;
    + }
    +
    + gfud->fd = source;
    +
    + gfud->request = g_strdup_printf(
    + "GET /%s HTTP/1.1\r\n"
    + "Host: %s\r\n"
    + "User-Agent: Purple/%u.%u.%u\r\n"
    + "Accept: */*\r\n"
    + "Connection: close\r\n"
    + "Referer: %s\r\n"
    + "\r\n",
    + gfud->path,
    + gfud->host,
    + PURPLE_MAJOR_VERSION, PURPLE_MINOR_VERSION, PURPLE_MICRO_VERSION,
    + gfud->host);
    +
    + gfud->inpa = purple_input_add(gfud->fd, PURPLE_INPUT_WRITE,
    + im_feeling_lucky_send_cb, gfud);
    + im_feeling_lucky_send_cb(gfud, gfud->fd, PURPLE_INPUT_WRITE);
    +}
    +
    +/******************************************************************************
    + * Command stuff
    + *****************************************************************************/
    +static PurpleCmdRet
    +im_feeling_lucky(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar *error, void *data)
    +{
    + GoogleFetchUrlData *gfud = NULL;
    + PurplePlugin *plugin = (PurplePlugin *)data;
    + gchar *url = NULL;
    +
    + url = g_strdup_printf(GOOGLE_URL_FORMAT, "www.google.com",
    + purple_url_encode(args[0]));
    + gfud = google_fetch_url_data_new(url);
    + g_free(url);
    +
    + if(!gfud)
    + return PURPLE_CMD_RET_FAILED;
    +
    + gfud->conv = conv;
    +
    + /* now make the connection */
    + gfud->conn_data =
    + purple_proxy_connect(plugin, NULL, gfud->host, gfud->port,
    + im_feeling_lucky_cb, gfud);
    +
    + if(!gfud->conn_data) {
    + google_fetch_url_data_free(gfud);
    +
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +/******************************************************************************
    + * Plugin Stuff
    + *****************************************************************************/
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + google_cmd_id =
    + purple_cmd_register("google", "s", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT,
    + NULL, PURPLE_CMD_FUNC(im_feeling_lucky),
    + _("Returns the url for a Google I'm feeling lucky "
    + "search"),
    + plugin);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + purple_cmd_unregister(google_cmd_id);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info = {
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + "core-plugin_pack-google",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + 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 = _("Google");
    + info.summary = _("Returns the url for a Google \"I'm feeling lucky\" search");
    + info.description = info.summary;
    +}
    +
    +PURPLE_INIT_PLUGIN(google, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/google/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,8 @@
    +[Google]
    +type=default
    +depends=purple
    +provides=google
    +summary=Writes the results of an "I'm feeling lucky" search to a conversation
    +description=%(summary)s
    +authors=Gary Kramlich
    +introduced=2.4.0
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/groupmsg/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +groupmsgdir = $(PURPLE_LIBDIR)
    +
    +groupmsg_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +groupmsg_LTLIBRARIES = groupmsg.la
    +
    +groupmsg_la_SOURCES = \
    + groupmsg.c
    +
    +groupmsg_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/groupmsg/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,11 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for groupmsg plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = groupmsg
    +
    +include $(PP_TOP)/win_pp.mak
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/groupmsg/groupmsg.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,189 @@
    +/*
    + * GroupMsg - Send an IM to a group of buddies
    + * 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
    + * 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 <notify.h>
    +#include <prpl.h>
    +#include <request.h>
    +#include <signals.h>
    +#include <util.h>
    +
    +static void
    +dont_do_it_cb(GList *list, const char *text)
    +{
    + g_list_free(list);
    +}
    +
    +static void
    +do_it_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
    +groupmsg_sendto_group(PurpleBlistNode *node, gpointer data)
    +{
    + PurpleBlistNode *n;
    + PurpleBuddy *b;
    + PurpleGroup *group;
    + GList *list = NULL;
    + gchar *tmp = NULL, *tmp2 = NULL;
    +
    + group = (PurpleGroup *)node;
    +
    + for (n = node->child; n; n = n->next) {
    +
    + if (PURPLE_BLIST_NODE_IS_CHAT(n))
    + continue;
    +
    + if (PURPLE_BLIST_NODE_IS_CONTACT(n)) {
    + b = purple_contact_get_priority_buddy((PurpleContact*)n);
    + } else
    + b = (PurpleBuddy *)n;
    +
    + if(b == NULL || !PURPLE_BUDDY_IS_ONLINE(b))
    + continue;
    +
    + tmp2 = tmp;
    + if(tmp != NULL)
    + tmp = g_strdup_printf(" %s (%s)\n%s", purple_buddy_get_alias(b), b->name, tmp2);
    + else
    + tmp = g_strdup_printf(" %s (%s)", purple_buddy_get_alias(b), b->name);
    + g_free(tmp2);
    + list = g_list_append(list, b);
    +
    + }
    +
    + if (tmp == NULL) {
    + tmp = g_strdup_printf(_("There are no buddies online in group %s"), group->name);
    + purple_notify_error(NULL, NULL, "No buddies", tmp);
    + g_free(tmp);
    + g_list_free(list);
    + return;
    + }
    +
    + tmp2 = tmp;
    + tmp = g_strdup_printf(_("Your message will be sent to these buddies:\n%s"), tmp2);
    + g_free(tmp2);
    +
    + purple_request_input(group, _("Spam"),
    + _("Please enter the message to send"),
    + tmp,
    + "", TRUE, FALSE, "html",
    + _("Send"), G_CALLBACK(do_it_cb),
    + _("Cancel"), G_CALLBACK(dont_do_it_cb),
    + NULL, NULL, NULL, list);
    +
    + g_free(tmp);
    +}
    +
    +static void
    +groupmsg_extended_menu_cb(PurpleBlistNode *node, GList **m)
    +{
    + PurpleMenuAction *bna = NULL;
    +
    + if(!PURPLE_BLIST_NODE_IS_GROUP(node))
    + return;
    +
    + *m = g_list_append(*m, bna);
    + bna = purple_menu_action_new("Group IM", PURPLE_CALLBACK(groupmsg_sendto_group), NULL, NULL);
    + *m = g_list_append(*m, bna);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    +
    + purple_signal_connect(purple_blist_get_handle(), "blist-node-extended-menu",
    + plugin, PURPLE_CALLBACK(groupmsg_extended_menu_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +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 */
    +
    + "core-plugin_pack-groupmsg", /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /**< summary */
    + NULL, /** description */
    + "Stu Tomlinson <stu@nosnilmot.com>", /**< 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) {
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("Group IM");
    + info.summary = _("Send an IM to a group of buddies.");
    + info.description = _("Adds the option to send an IM to every online "
    + "buddy in a group.");
    +}
    +
    +PURPLE_INIT_PLUGIN(groupmsg, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/groupmsg/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Group IM]
    +type=default
    +depends=purple
    +provides=groupmsg
    +summary=Send an IM to a group of buddies
    +description=Adds the option to send an IM to every online buddy in a group.
    +authors=Stu Tomlinson
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/hideconv/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +hideconvdir = $(PIDGIN_LIBDIR)
    +
    +hideconv_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +hideconv_LTLIBRARIES = hideconv.la
    +
    +hideconv_la_SOURCES = \
    + hideconv.c
    +
    +hideconv_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)/pidgin/\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DLOCALEDIR=\"$(PIDGIN_DATADIR)/locale\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/hideconv/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for hideconv plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = hideconv
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/hideconv/hideconv.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,291 @@
    +/*
    + * Hide Conversations - You can hide conversations without having to close them.
    + * 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 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"
    +
    +#define PLUGIN_ID "gtk-plugin_pack-hideconv"
    +#define PLUGIN_STATIC_NAME "hideconv"
    +#define PLUGIN_AUTHOR "Sadrul H Chowdhury <sadrul@users.sourceforge.net>"
    +
    +/* System headers */
    +#include <gdk/gdk.h>
    +#include <gtk/gtk.h>
    +
    +/* Purple headers */
    +#include <gtkconv.h>
    +#include <gtkconvwin.h>
    +#include <gtkplugin.h>
    +#include <gtkutils.h>
    +
    +#define MENUSET "hideconv::menuset"
    +
    +static PidginWindow *window = NULL;
    +static void (*orig_conv_present)(PurpleConversation *conv);
    +
    +static void conv_created(PurpleConversation *conv, gpointer null);
    +
    +static void
    +create_hidden_convwin()
    +{
    + /* This is a 'wee bit' hacky. Create two conv windows, remove the second
    + * one from the list, and then destroy the first one. This is because we
    + * want to hide this entire conversation window from pidgin itself.
    + */
    + GList *null;
    + PidginWindow *tmp = pidgin_conv_window_new();
    + window = pidgin_conv_window_new();
    + null = pidgin_conv_windows_get_list();
    + null = g_list_remove(null, window);
    + pidgin_conv_window_hide(window);
    + pidgin_conv_window_destroy(tmp);
    +}
    +
    +static void
    +gtkconv_redisplaying(PidginConversation *gtkconv)
    +{
    + conv_created(gtkconv->active_conv, NULL);
    + g_signal_handlers_disconnect_by_func(G_OBJECT(gtkconv->imhtml),
    + G_CALLBACK(gtkconv_redisplaying), gtkconv);
    +}
    +
    +static void
    +hide_gtkconv(PidginConversation *gtkconv)
    +{
    + pidgin_conv_window_add_gtkconv(window, gtkconv);
    + g_signal_connect_swapped(G_OBJECT(gtkconv->imhtml), "visibility_notify_event",
    + G_CALLBACK(gtkconv_redisplaying), gtkconv);
    +}
    +
    +static void
    +hide_conv_cb(GtkWidget *wid, PidginWindow *win)
    +{
    + PidginConversation *gtkconv = pidgin_conv_window_get_active_gtkconv(win);
    + pidgin_conv_window_remove_gtkconv(win, gtkconv);
    + hide_gtkconv(gtkconv);
    +}
    +
    +static void
    +show_convs_cb(PurplePluginAction *dontcare)
    +{
    + GList *list = g_list_copy(pidgin_conv_window_get_gtkconvs(window)), *iter;
    +
    + for (iter = list; iter; iter = iter->next) {
    + PidginConversation *gtkconv = iter->data;
    + pidgin_conv_window_remove_gtkconv(window, gtkconv);
    + pidgin_conv_placement_place(gtkconv);
    + purple_conversation_present(gtkconv->active_conv);
    + conv_created(gtkconv->active_conv, NULL);
    + }
    + g_list_free(list);
    +
    + create_hidden_convwin();
    +}
    +
    +static void
    +attach_menu_to_window(PidginWindow *win)
    +{
    + GtkWidget *widget, *item;
    + if (g_object_get_data(G_OBJECT(win->window), MENUSET))
    + return;
    + g_object_set_data(G_OBJECT(win->window), MENUSET, GINT_TO_POINTER(TRUE));
    +
    + widget = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Options"));
    +
    + /* We cannot use pidgin_separator, unfortunately. */
    + item = gtk_separator_menu_item_new();
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(widget), item);
    + g_object_set_data(G_OBJECT(item), MENUSET, GINT_TO_POINTER(TRUE));
    +
    + item = gtk_menu_item_new_with_mnemonic(_("_Hide Conversation"));
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(hide_conv_cb), win);
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(widget), item);
    + g_object_set_data(G_OBJECT(item), MENUSET, GINT_TO_POINTER(TRUE));
    +
    + item = gtk_menu_item_new_with_mnemonic(_("Show Hidden Conversations"));
    + g_signal_connect_swapped(G_OBJECT(item), "activate", G_CALLBACK(show_convs_cb), NULL);
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(widget), item);
    + g_object_set_data(G_OBJECT(item), MENUSET, GINT_TO_POINTER(TRUE));
    +}
    +
    +static void
    +detach_menu_from_window(PidginWindow *win)
    +{
    + GtkWidget *widget;
    + GList *children;
    +
    + widget = gtk_item_factory_get_widget(win->menu.item_factory, N_("/Options"));
    + children = gtk_container_get_children(GTK_CONTAINER(widget));
    + while (children) {
    + GtkWidget *item = children->data;
    + children = children->next;
    + if (g_object_get_data(G_OBJECT(item), MENUSET))
    + gtk_widget_destroy(item);
    + }
    + g_object_set_data(G_OBJECT(win->window), MENUSET, GINT_TO_POINTER(FALSE));
    +}
    +
    +static gboolean
    +conv_created_to(PurpleConversation *conv)
    +{
    + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
    +
    + if (!gtkconv || !gtkconv->win || !gtkconv->win->window)
    + return TRUE;
    + if (!GTK_WIDGET_VISIBLE(gtkconv->win->window))
    + return TRUE;
    +
    + attach_menu_to_window(gtkconv->win);
    + return FALSE;
    +}
    +
    +static void
    +conv_created(PurpleConversation *conv, gpointer null)
    +{
    + g_timeout_add(1000, (GSourceFunc)conv_created_to, conv);
    +}
    +
    +static void
    +twisted_present(PurpleConversation *conv)
    +{
    + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
    + if (gtkconv && gtkconv->win == window) {
    + gboolean last = (g_list_length(window->gtkconvs) == 1);
    + pidgin_conv_window_remove_gtkconv(window, gtkconv);
    + pidgin_conv_placement_place(gtkconv);
    + if (last)
    + create_hidden_convwin();
    + }
    + orig_conv_present(conv);
    + conv_created(conv, NULL);
    +}
    +
    +static void
    +hide_all_conv(PurplePluginAction *dontcare)
    +{
    + GList *iter = pidgin_conv_windows_get_list();
    + while (iter) {
    + GList *it = pidgin_conv_window_get_gtkconvs(iter->data);
    + iter = iter->next;
    + while (it) {
    + PidginConversation *gtkconv = it->data;
    + it = it->next;
    + pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv);
    + hide_gtkconv(gtkconv);
    + }
    + }
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + PurpleConversationUiOps *ops = pidgin_conversations_get_conv_ui_ops();
    + orig_conv_present = ops->present;
    + ops->present = twisted_present;
    +
    + create_hidden_convwin();
    +
    + purple_signal_connect(purple_conversations_get_handle(), "conversation-created",
    + plugin, PURPLE_CALLBACK(conv_created), NULL);
    +
    + g_list_foreach(pidgin_conv_windows_get_list(), (GFunc)attach_menu_to_window, NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + PurpleConversationUiOps *ops = pidgin_conversations_get_conv_ui_ops();
    + ops->present = orig_conv_present;
    +
    + show_convs_cb(NULL);
    + pidgin_conv_window_destroy(window);
    + window = NULL;
    +
    + g_list_foreach(pidgin_conv_windows_get_list(), (GFunc)detach_menu_from_window, NULL);
    +
    + return TRUE;
    +}
    +
    +static GList *
    +actions(PurplePlugin *plugin, gpointer context)
    +{
    + PurplePluginAction *act;
    + GList *list = NULL;
    +
    + act = purple_plugin_action_new(_("Show All Hidden Conversations"), show_convs_cb);
    + list = g_list_append(list, act);
    +
    + act = purple_plugin_action_new(_("Hide All Conversations"), hide_all_conv);
    + list = g_list_append(list, act);
    +
    + return list;
    +}
    +
    +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 */
    + actions, /* 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, LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("Hide Conversation");
    + info.summary = _("Hide conversations without closing them.");
    + info.description = _("Hide conversations without closing them.");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/hideconv/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Hide Conversation]
    +type=incomplete
    +depends=pidgin
    +provides=hideconv
    +summary=Hide conversations without closing them
    +description=%(summary)s
    +authors=Sadrul Habib Chowdhury
    +introduced=1.0
    +notes=Superseded by functionality present in Pidgin 2.3.0 and newer
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/highlight/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +highlightdir = $(PURPLE_LIBDIR)
    +
    +highlight_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +highlight_LTLIBRARIES = highlight.la
    +
    +highlight_la_SOURCES = \
    + highlight.c
    +
    +highlight_la_LIBADD = \
    + $(PURPLE_LIBS) \
    + $(GLIB_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(GLIB_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/highlight/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for highlight plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = highlight
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/highlight/highlight.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,339 @@
    +/**
    + * highlight.c Highlight on customized words.
    + *
    + * 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
    + * 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
    + */
    +
    +/* Pack/Local headers */
    +#include "../common/pp_internal.h"
    +
    +#include <plugin.h>
    +
    +#include <account.h>
    +#include <blist.h>
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <notify.h>
    +#include <util.h>
    +
    +#include <string.h>
    +
    +#define PREF_PREFIX "/plugins/core/highlight"
    +#define PREF_WORDS PREF_PREFIX "/words"
    +
    +#define DELIMS " \t.,;|<>?/\\`~!@#$%^&*()+={}[]:'\""
    +
    +#define PROP "highlight-words"
    +
    +static char **words;
    +static PurpleCmdId cmd;
    +
    +/**
    + * History stuff.
    + */
    +/* XXX: SAVE THE NAME AND ACCOUNT OF THE CONVERSATION. OTHERWISE, IT CAUSES
    + * A CRASH WHEN A HIGHLIGHTED CONVERSATION NO LONGER EXISTS.
    + */
    +static GHashTable *history;
    +
    +static void
    +string_destroy(gpointer data)
    +{
    + g_string_free(data, TRUE);
    +}
    +
    +static void
    +print_history_from_one_conv(gpointer key, gpointer value, gpointer data)
    +{
    + g_string_append_printf(data, "<b>Highlights from %s (%s)<br>%s<br><br><hr>",
    + purple_conversation_get_name(key),
    + purple_account_get_username(purple_conversation_get_account(key)),
    + ((GString*)value)->str);
    +}
    +
    +static void
    +print_history()
    +{
    + GString *str = g_string_new(NULL);
    + g_hash_table_foreach(history, print_history_from_one_conv, str);
    + purple_notify_formatted(NULL, _("Highlight History"), _("Highlight History"),
    + NULL, str->str, NULL, NULL);
    + string_destroy(str);
    +}
    +
    +static void
    +clear_history()
    +{
    + g_hash_table_destroy(history);
    + history = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, string_destroy);
    +}
    +
    +static void
    +add_to_history(PurpleConversation *conv, const char *message, const char *who, time_t mtime)
    +{
    + GString *str = g_hash_table_lookup(history, conv);
    + if (str == NULL) {
    + str = g_string_new(NULL);
    + g_hash_table_replace(history, conv, str);
    + }
    + g_string_append_printf(str, "<br>(%s) <b>%s</b>: %s",
    + purple_time_format(localtime(&mtime)),
    + who, message);
    +}
    +
    +/* End of history */
    +
    +static void
    +casefold_collate_strings(char **string)
    +{
    + int i;
    +
    + for (i = 0; string[i]; i++) {
    + char *store = string[i];
    + char *cs = g_utf8_casefold(store, -1);
    + string[i] = g_utf8_collate_key(cs, -1);
    + g_free(cs);
    + g_free(store);
    + }
    +}
    +
    +static void
    +sort(char **strings, int length)
    +{
    + int half;
    + char **left, **middle, **m, **right;
    + char **ret, **r;
    +
    + if (length <= 1)
    + return;
    +
    + r = ret = g_new0(char *, length);
    +
    + half = length / 2;
    + sort(strings, half);
    + sort(strings + half, length - half);
    +
    + left = strings;
    + middle = m = strings + half;
    + right = strings + length;
    +
    + while (left < middle && m < right) {
    + int comp = strcmp(*left, *m);
    + if (comp <= 0)
    + *r++ = *left++;
    + else
    + *r++ = *m++;
    + }
    + while (left < middle)
    + *r++ = *left++;
    + while (m < right)
    + *r++ = *m++;
    + for (half = 0; half < length; half++)
    + strings[half] = ret[half];
    + g_free(ret);
    +}
    +
    +static gboolean
    +writing_msg_callback(PurpleAccount *account, char *who, char **message, PurpleConversation *conv,
    + PurpleMessageFlags flags)
    +{
    + if (flags & PURPLE_MESSAGE_NICK)
    + add_to_history(conv, *message, who, time(NULL));
    + return FALSE;
    +}
    +
    +static gboolean
    +msg_callback(PurpleAccount *account, char **who, char **message, PurpleConversation *conv,
    + PurpleMessageFlags *flags)
    +{
    + char **splits;
    + int len;
    + int wl, sl;
    + const char *me;
    +
    + if (*flags & PURPLE_MESSAGE_NICK) {
    + return FALSE; /* this message is already highlighted */
    + }
    +
    + if (!words)
    + return FALSE;
    +
    + me = purple_connection_get_display_name(purple_account_get_connection(account));
    + if (me != NULL && g_utf8_collate(*who, me) == 0)
    + return FALSE;
    +
    + splits = g_strsplit_set(*message, DELIMS, -1);
    + if (!splits)
    + return FALSE;
    + len = 0;
    + while (splits[len])
    + len++;
    + casefold_collate_strings(splits);
    + sort(splits, len);
    +
    + /* this is probably over-engineering. */
    + for (sl = 0, wl = 0; words[wl] && splits[sl]; ) {
    + int val = strcmp(words[wl], splits[sl]);
    + if (val == 0) {
    + *flags |= PURPLE_MESSAGE_NICK;
    + break;
    + }
    + if (val < 0)
    + wl++;
    + else
    + sl++;
    + }
    + g_strfreev(splits);
    + return FALSE;
    +}
    +
    +static void
    +construct_list()
    +{
    + int len = 0;
    + g_strfreev(words);
    + words = g_strsplit_set(purple_prefs_get_string(PREF_WORDS), DELIMS, -1);
    + if (!words)
    + return;
    + while (words[len])
    + len++;
    + casefold_collate_strings(words);
    + sort(words, len);
    +}
    +
    +static PurpleCmdRet
    +highlight_cmd(PurpleConversation *conv, const gchar *cmd, gchar **args, gchar **error, void *data)
    +{
    + if (g_utf8_collate(args[0], "history") == 0) {
    + print_history();
    + } else if (g_utf8_collate(args[0], "clear") == 0) {
    + clear_history();
    + } else {
    + }
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    +#if !GLIB_CHECK_VERSION(2,4,0)
    + return FALSE;
    +#else
    + construct_list();
    +
    + purple_signal_connect(purple_conversations_get_handle(), "receiving-chat-msg", plugin,
    + G_CALLBACK(msg_callback), NULL);
    + purple_signal_connect(purple_conversations_get_handle(), "writing-chat-msg", plugin,
    + G_CALLBACK(writing_msg_callback), NULL);
    + purple_prefs_connect_callback(plugin, PREF_WORDS,
    + (PurplePrefCallback)construct_list, NULL);
    +
    + cmd = purple_cmd_register("highlight", "ws", PURPLE_CMD_P_DEFAULT,
    + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL, highlight_cmd,
    + _("/highlight history: shows the list of highlighted sentences from the history.\n"
    + "/highlight clear: clears the history.\n"
    + "/highlight +&lt;word&gt;: adds &lt;word&gt; to the highlight word list for this conversation only.\n"
    + "/highlight -&lt;word&gt;: removes &lt;word&gt; from the highlight word list for this conversation only.\n"),
    + NULL);
    +
    + history = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, string_destroy);
    + return TRUE;
    +#endif
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + if (cmd)
    + purple_cmd_unregister(cmd);
    + g_hash_table_destroy(history);
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_WORDS, _("Words to highlight on\n"
    + "(separate words by space)"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + "core-plugin_pack-highlight",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
    + PP_WEBSITE,
    + plugin_load,
    + plugin_unload,
    + NULL,
    + NULL,
    + NULL,
    + &prefs_info,
    + 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 */
    + purple_prefs_add_none(PREF_PREFIX);
    + purple_prefs_add_string(PREF_WORDS, "");
    +
    + info.name = _("Highlight");
    + info.summary = _("Support for highlighting words.");
    + info.description = _("Support for highlighting words.");
    +}
    +
    +PURPLE_INIT_PLUGIN(ignore, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/highlight/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Highlight]
    +type=default
    +depends=purple
    +provides=highlight
    +summary=Support for highlighting words
    +description=%(summary)s
    +authors=Sadrul Habib Chowdhury
    +introduced=2.0.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,60 @@
    +INCLUDES = \
    + callbacks.h \
    + ignorance.h \
    + ignorance_denizen.h \
    + ignorance_internal.h \
    + ignorance_level.h \
    + ignorance_rule.h \
    + ignorance_violation.h \
    + interface.h \
    + regex.h \
    + support.h
    +
    +EXTRA_DIST = \
    + plugins.cfg \
    + ignorance.conf \
    + $(INCLUDES)
    +
    +ignorancedir = $(PIDGIN_LIBDIR)/pidgin
    +
    +ignorance_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +ignorance_LTLIBRARIES = \
    + ignorance.la
    +
    +ignorance_confdir = \
    + $(sysconfdir)/pidgin
    +
    +ignorance_conf_DATA = \
    + ignorance.conf
    +
    +ignorance_la_SOURCES = \
    + ignorance.c \
    + ignorance_level.c \
    + ignorance_rule.c \
    + ignorance_violation.c \
    + ignorance_denizen.c \
    + callbacks.c \
    + interface.c \
    + support.c
    +
    +ignorance_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS) \
    + $(GLIB_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DVERSION=\"$(VERSION)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + -DIGNORANCE_CONFDIR=\"$(ignorance_confdir)\" \
    + $(DEBUG_CFLAGS) \
    + $(GTK_CFLAGS) \
    + $(PIDGIN_CFLAGS) \
    + $(PLUGIN_CFLAGS)
    +
    +ignorance.conf:
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/callbacks.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,701 @@
    +/*
    + * 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 <string.h>
    +#include <gtk/gtk.h>
    +
    +#include "callbacks.h"
    +#include "interface.h"
    +#include "support.h"
    +#include "ignorance.h"
    +#include "ignorance_internal.h"
    +
    +static gboolean populate_panel(GtkTreeSelection *sel);
    +static gboolean verify_form();
    +static gboolean add_rule_from_form(GtkTreeView *view);
    +static gboolean add_group_from_form(GtkTreeView *view);
    +static gboolean edit_rule_from_form(GtkTreeView *view);
    +static gboolean del_rule_from_form(GtkTreeView *view);
    +static gboolean del_group_from_form(GtkTreeView *view);
    +static gboolean ignorance_rule_valid(const gchar *ruletext, gint ruletype);
    +static gboolean ignorance_rulename_valid(const gchar *rule_name);
    +
    +gboolean on_levelView_row_activated (GtkTreeSelection *sel, gpointer user_data) {
    + populate_panel(sel);
    + return FALSE;
    +}
    +
    +void on_levelAdd_clicked (GtkButton *button, gpointer user_data) {
    +
    + if(verify_form()) {
    + if(!add_rule_from_form(user_data)) {
    + /* XXX: */
    + }
    + }
    +}
    +
    +void on_groupAdd_clicked (GtkButton *button, gpointer user_data) {
    + if(verify_form()) {
    + if(!add_group_from_form(user_data)) {
    + /* XXX: Do something I guess? -- sadrul */
    + }
    + }
    +}
    +
    +void on_levelEdit_clicked (GtkButton *button, gpointer user_data) {
    + if(verify_form()) {
    + if(!edit_rule_from_form(user_data)) {
    + /* XXX: Do something I guess? -- sadrul */
    + }
    + }
    +}
    +
    +void
    +on_levelDel_clicked (GtkButton *button, gpointer user_data) {
    + if(rule_selected) {
    + if(verify_form()) {
    + if(!del_rule_from_form(user_data)) {
    + /* XXX: */
    + }
    + }
    + } else {
    + del_group_from_form(user_data);
    + }
    +}
    +
    +static gboolean populate_panel(GtkTreeSelection *sel) {
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    + gchar *levnames, *rulenames;
    + ignorance_level *level=NULL;
    + ignorance_rule *rule=NULL;
    + GString *levgs, *rulegs;
    +
    + if (gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + gtk_tree_model_get (model, &iter, LEVEL_COLUMN, &levnames, -1);
    + gtk_tree_model_get(model,&iter,RULE_COLUMN,&rulenames,-1);
    + if(strlen(rulenames)) {
    + rule_selected=TRUE;
    + levgs=g_string_new(levnames);
    + rulegs=g_string_new(rulenames);
    + level=ignorance_get_level_name(levgs);
    + rule=ignorance_level_get_rule(level,rulegs);
    + if(!rule) {
    + fprintf(stderr,"Ignorance: Unable to find rule %s on level %s\n",
    + rulegs->str,level->name->str);
    + return FALSE;
    + }
    +
    + gtk_entry_set_text(GTK_ENTRY(rulename),rulenames);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(filter_cb),
    + rule->score & IGNORANCE_FLAG_FILTER);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ignore_cb),
    + rule->score & IGNORANCE_FLAG_IGNORE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(message_cb),
    + rule->score & IGNORANCE_FLAG_MESSAGE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sound_cb),
    + rule->score & IGNORANCE_FLAG_SOUND);
    +
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(execute_cb),
    + rule->score & IGNORANCE_FLAG_EXECUTE);
    +
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(message_cb))) {
    + gtk_entry_set_text(GTK_ENTRY(message_entry),rule->message);
    + }
    +
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sound_cb))) {
    + gtk_entry_set_text(GTK_ENTRY(sound_entry),rule->sound);
    + }
    +
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(execute_cb))) {
    + gtk_entry_set_text(GTK_ENTRY(execute_entry),rule->command);
    + }
    +
    + gtk_entry_set_text(GTK_ENTRY(filtervalue),
    + (const gchar*)(rule->value));
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(im_type_cb),
    + rule->flags & IGNORANCE_APPLY_IM);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chat_type_cb),
    + rule->flags & IGNORANCE_APPLY_CHAT);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(username_type_cb),
    + rule->flags & IGNORANCE_APPLY_USER);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enterleave_type_cb),
    + rule->flags & IGNORANCE_APPLY_ENTERLEAVE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(invite_type_cb),
    + rule->flags & IGNORANCE_APPLY_INVITE);
    +
    +
    +#ifdef HAVE_REGEX_H
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(regex_cb),
    + rule->type & IGNORANCE_RULE_REGEX);
    +#endif
    +
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(repeat_cb),
    + rule->type & IGNORANCE_RULE_REPEAT);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enabled_cb),
    + rule->enabled);
    +
    + gtk_button_set_label(GTK_BUTTON(levelDel),"Remove rule");
    +
    + g_string_free(levgs,TRUE);
    + g_string_free(rulegs,TRUE);
    + } else {
    + rule_selected=FALSE;
    + gtk_entry_set_text(GTK_ENTRY(rulename),"");
    + gtk_entry_set_text(GTK_ENTRY(filtervalue),"");
    +
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(im_type_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(chat_type_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(username_type_cb),
    + FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enterleave_type_cb),
    + FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(invite_type_cb),
    + FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(filter_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ignore_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(message_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sound_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(execute_cb),FALSE);
    +
    +#ifdef HAVE_REGEX_H
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(regex_cb),FALSE);
    +#endif
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(repeat_cb),FALSE);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enabled_cb),FALSE);
    +
    + gtk_button_set_label(GTK_BUTTON(levelDel),"Remove group");
    + }
    +
    + g_free(levnames);
    + g_free(rulenames);
    + }
    +
    + return FALSE;
    +}
    +
    +void on_filter_cb_toggled (GtkButton *button, gpointer user_data) {
    + gboolean amdisabled=!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
    +
    + if(!amdisabled) {
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(message_cb),amdisabled);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ignore_cb),amdisabled);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sound_cb),amdisabled);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(execute_cb),amdisabled);
    + }
    +
    + /* Maybe we *should* allow them to send a message on filter */
    + gtk_widget_set_sensitive(message_cb,amdisabled);
    + gtk_widget_set_sensitive(ignore_cb,amdisabled);
    + gtk_widget_set_sensitive(sound_cb,amdisabled);
    + gtk_widget_set_sensitive(execute_cb,amdisabled);
    +}
    +
    +void on_ignore_cb_toggled (GtkButton *button, gpointer user_data) {
    + gboolean amdisabled=!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
    +
    + if(!amdisabled) {
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(message_cb),amdisabled);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(filter_cb),amdisabled);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(sound_cb),amdisabled);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(execute_cb),amdisabled);
    + }
    +
    + /* Maybe we *should* allow them to send a message on ignore */
    + gtk_widget_set_sensitive(message_cb,amdisabled);
    + gtk_widget_set_sensitive(filter_cb,amdisabled);
    + gtk_widget_set_sensitive(sound_cb,amdisabled);
    + gtk_widget_set_sensitive(execute_cb,amdisabled);
    +}
    +
    +void on_message_cb_toggled (GtkButton *button, gpointer user_data) {
    + gboolean isactive=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(message_cb));
    + if(!isactive) {
    + gtk_entry_set_text(GTK_ENTRY(message_entry),"");
    + }
    + gtk_widget_set_sensitive(message_entry,isactive);
    +}
    +
    +void on_sound_cb_toggled (GtkButton *button, gpointer user_data) {
    + gboolean isactive=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sound_cb));
    +
    + if(!isactive) {
    + gtk_entry_set_text(GTK_ENTRY(sound_entry),"");
    + }
    + gtk_widget_set_sensitive(sound_entry,isactive);
    +#if GTK_CHECK_VERSION(2,4,0)
    + gtk_widget_set_sensitive(sound_browse,isactive);
    +#endif
    +}
    +
    +void on_execute_cb_toggled (GtkButton *button, gpointer user_data) {
    + gboolean isactive=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(execute_cb));
    + if(!isactive) {
    + gtk_entry_set_text(GTK_ENTRY(execute_entry),"");
    + }
    + gtk_widget_set_sensitive(execute_entry,isactive);
    + /*gtk_widget_set_sensitive(execute_browse,isactive);*/
    +}
    +
    +void on_sound_browse_clicked (GtkButton *button, gpointer user_data) {
    +#if GTK_CHECK_VERSION(2,4,0)
    + GtkWidget *fcd=gtk_file_chooser_dialog_new("Choose sound",NULL,
    + GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL,
    + GTK_RESPONSE_CANCEL, GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
    + NULL);
    + gchar *filename=NULL;
    +
    + if(GTK_RESPONSE_ACCEPT == gtk_dialog_run(GTK_DIALOG(fcd))) {
    + filename=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fcd));
    + if(filename) {
    + gtk_entry_set_text(GTK_ENTRY(sound_entry),filename);
    + g_free(filename);
    + }
    + }
    +
    + gtk_widget_destroy(fcd);
    +#endif
    +}
    +
    +/*No command browser for now
    +void on_execute_browse_clicked (GtkButton *button, gpointer user_data) {
    + gchar *filename=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(execute_browse));
    + if(filename) {
    + gtk_entry_set_text(GTK_ENTRY(execute_entry),filename);
    + }
    +}
    +*/
    +
    +static gboolean verify_form() {
    + gboolean rv=TRUE;
    + gchar *tmp;
    + gint type=IGNORANCE_RULE_SIMPLETEXT;
    +
    + tmp=(gchar*)gtk_entry_get_text(GTK_ENTRY(filtervalue));
    +#ifdef HAVE_REGEX_H
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(regex_cb))) {
    + type=IGNORANCE_RULE_REGEX;
    + }
    +#endif
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(repeat_cb))) {
    + type=IGNORANCE_RULE_REPEAT;
    + }
    +
    + rv=ignorance_rule_valid(tmp,type);
    +
    + if(!rv) {
    + purple_debug_error("ignorance","Rule invalid: %s\n",tmp);
    + }else{
    + tmp=(gchar*)gtk_entry_get_text(GTK_ENTRY(rulename));
    + rv=ignorance_rulename_valid(tmp);
    +
    + if(!rv) {
    + purple_debug_error("ignorance","Rule name invalid: %s\n",tmp);
    + }
    + }
    +
    + return rv;
    +}
    +
    +
    +static gboolean add_rule_from_form(GtkTreeView *view) {
    + ignorance_rule *rule=NULL;
    + ignorance_level *level=NULL;
    + GString *rule_name, *level_name;
    + GtkTreeSelection *sel;
    + GtkTreeIter iter, childiter;
    + GtkTreeModel *model;
    + gchar *rule_value=NULL, *rule_message=NULL, *rule_sound=NULL,
    + *rule_command=NULL;
    + gint rule_score=0, rule_type=IGNORANCE_RULE_SIMPLETEXT, rule_flags=0;
    + gboolean rv=FALSE, rule_enable;
    +
    + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    +
    + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + return rv;
    + }
    +
    + gtk_tree_model_get (model, &iter, LEVEL_COLUMN, &rule_value, -1);
    +
    + if(!rule_value) {
    + return rv;
    + }else if(!strlen(rule_value)) {
    + g_free(rule_value);
    + return rv;
    + }
    +
    + level_name=g_string_new(rule_value);
    + g_free(rule_value);
    +
    + gtk_tree_model_get(model,&iter,RULE_COLUMN,&rule_value,-1);
    + if(strlen(rule_value)) {
    + childiter=iter;
    + gtk_tree_model_iter_parent(model,&iter,&childiter);
    + }
    +
    + g_free(rule_value);
    +
    + level=ignorance_get_level_name(level_name);
    +
    + rule_name=g_string_new(gtk_entry_get_text(GTK_ENTRY(rulename)));
    +
    + rule=ignorance_level_get_rule(level,rule_name);
    +
    + if(rule) {
    + g_string_free(level_name,TRUE);
    + g_string_free(rule_name,TRUE);
    + return rv;
    + }
    +
    +#ifdef HAVE_REGEX_H
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(regex_cb))) {
    + rule_type=IGNORANCE_RULE_REGEX;
    + }
    +#endif
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(repeat_cb))) {
    + rule_type=IGNORANCE_RULE_REPEAT;
    + }
    +
    + rule_value=(gchar*)gtk_entry_get_text(GTK_ENTRY(filtervalue));
    +
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(im_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_IM;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chat_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_CHAT;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(username_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_USER;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(enterleave_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_ENTERLEAVE;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(invite_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_INVITE;
    + }
    +
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(filter_cb))) {
    + rule_score |= IGNORANCE_FLAG_FILTER;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ignore_cb))) {
    + rule_score |= IGNORANCE_FLAG_IGNORE;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(message_cb))) {
    + rule_score |= IGNORANCE_FLAG_MESSAGE;
    + rule_message=(gchar*)gtk_entry_get_text(GTK_ENTRY(message_entry));
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sound_cb))) {
    + rule_score |= IGNORANCE_FLAG_SOUND;
    + rule_sound=(gchar*)gtk_entry_get_text(GTK_ENTRY(sound_entry));
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(execute_cb))) {
    + rule_score |= IGNORANCE_FLAG_EXECUTE;
    + rule_command=(gchar*)gtk_entry_get_text(GTK_ENTRY(execute_entry));
    + }
    +
    + rule_enable=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(enabled_cb));
    +
    + rule=ignorance_rule_newp(rule_name,rule_type,rule_value,rule_score,
    + rule_flags,rule_enable,rule_message,rule_sound,
    + rule_command);
    +
    + rv=ignorance_level_add_rule(level,rule);
    +
    + if(rv) {
    + gtk_tree_store_append(GTK_TREE_STORE(model),&childiter,&iter);
    + gtk_tree_store_set(GTK_TREE_STORE(model),&childiter,LEVEL_COLUMN,level_name->str,RULE_COLUMN,rule_name->str,-1);
    + }
    +
    + g_string_free(level_name,TRUE);
    + g_string_free(rule_name,TRUE);
    +
    + return rv;
    +}
    +
    +static gboolean add_group_from_form(GtkTreeView *view) {
    + gboolean rv=TRUE;
    + GtkTreeIter iter;
    + GtkTreeStore *store = GTK_TREE_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(view)));
    +
    + ignorance_level *level=ignorance_level_new();
    + level->name=g_string_new(gtk_entry_get_text(GTK_ENTRY(rulename)));
    +
    + rv=ignorance_add_level(level);
    +
    + if(rv) {
    + gtk_tree_store_append(store,&iter,NULL);
    + gtk_tree_store_set (store, &iter, LEVEL_COLUMN, level->name->str,
    + RULE_COLUMN, "", -1);
    + }
    +
    + return rv;
    +}
    +
    +static gboolean edit_rule_from_form(GtkTreeView *view) {
    + ignorance_level *level;
    + ignorance_rule *rule;
    + GString *rule_name, *level_name;
    + GtkTreeSelection *sel;
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    + gchar *rule_value, *rule_message=NULL, *rule_sound=NULL, *rule_command=NULL;
    + gint rule_score=0, rule_type=IGNORANCE_RULE_SIMPLETEXT, rule_flags=0;
    + gboolean rule_enable, rv;
    +
    + sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    +
    + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + return FALSE;
    + }
    +
    + gtk_tree_model_get (model, &iter, LEVEL_COLUMN, &rule_value, -1);
    +
    + if(!rule_value) {
    + return FALSE;
    + }else if(!strlen(rule_value)) {
    + g_free(rule_value);
    + return FALSE;
    + }
    +
    + level_name=g_string_new(rule_value);
    + g_free(rule_value);
    + level=ignorance_get_level_name(level_name);
    +
    + rule_name=g_string_new(gtk_entry_get_text(GTK_ENTRY(rulename)));
    +
    + rule=ignorance_level_get_rule(level,rule_name);
    +
    + if(!rule) {
    + fprintf(stderr,"Ignorance: Rule \"%s\" not found on level %s\n",rule_name->str,level_name->str);
    + g_string_free(rule_name,TRUE);
    + g_string_free(level_name,TRUE);
    + return FALSE;
    + }
    +
    +#ifdef HAVE_REGEX_H
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(regex_cb))) {
    + rule_type=IGNORANCE_RULE_REGEX;
    + }
    +#endif
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(repeat_cb))) {
    + rule_type=IGNORANCE_RULE_REPEAT;
    + }
    +
    + rule->type=rule_type;
    +
    + rule_value=(gchar*)gtk_entry_get_text(GTK_ENTRY(filtervalue));
    + g_free(rule->value);
    + rule->value=g_strdup(rule_value);
    +
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(im_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_IM;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(chat_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_CHAT;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(username_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_USER;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(enterleave_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_ENTERLEAVE;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(invite_type_cb))) {
    + rule_flags |= IGNORANCE_APPLY_INVITE;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(filter_cb))) {
    + rule_score |= IGNORANCE_FLAG_FILTER;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ignore_cb))) {
    + rule_score |= IGNORANCE_FLAG_IGNORE;
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(message_cb))) {
    + rule_score |= IGNORANCE_FLAG_MESSAGE;
    + rule_message=(gchar*)gtk_entry_get_text(GTK_ENTRY(message_entry));
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(sound_cb))) {
    + rule_score |= IGNORANCE_FLAG_SOUND;
    + rule_sound=(gchar*)gtk_entry_get_text(GTK_ENTRY(sound_entry));
    + }
    + if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(execute_cb))) {
    + rule_score |= IGNORANCE_FLAG_EXECUTE;
    + rule_command=(gchar*)gtk_entry_get_text(GTK_ENTRY(execute_entry));
    + }
    +
    + rule_enable=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(enabled_cb));
    +
    + rule=ignorance_rule_newp(rule_name, rule_type, rule_value, rule_score,
    + rule_flags,rule_enable,rule_message,rule_sound,
    + rule_command);
    +
    + rv=ignorance_level_remove_rule(level,rule_name);
    +
    + if(rv) {
    + rv=ignorance_level_add_rule(level,rule);
    + }
    +
    + rule->flags=rule_flags;
    + rule->enabled=gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(enabled_cb));
    +
    + g_string_free(rule_name,TRUE);
    + g_string_free(level_name,TRUE);
    +
    + return rv;
    +}
    +
    +static gboolean del_rule_from_form(GtkTreeView *view) {
    + ignorance_level *level;
    + ignorance_rule *rule;
    + GString *rule_name, *level_name;
    + GtkTreeSelection *sel;
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    + gchar *rule_value;
    + gboolean rv=FALSE;
    +
    + sel=gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    +
    + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + return rv;
    + }
    +
    + gtk_tree_model_get (model, &iter, LEVEL_COLUMN, &rule_value, -1);
    +
    + if(!rule_value) {
    + return rv;
    + }else if(!strlen(rule_value)) {
    + g_free(rule_value);
    + return rv;
    + }
    +
    + level_name=g_string_new(rule_value);
    + g_free(rule_value);
    + level=ignorance_get_level_name(level_name);
    +
    + rule_name=g_string_new(gtk_entry_get_text(GTK_ENTRY(rulename)));
    +
    + rule=ignorance_level_get_rule(level,rule_name);
    +
    + if(!rule) {
    + fprintf(stderr,"Ignorance: Rule \"%s\" not found on level %s\n",
    + rule_name->str,level_name->str);
    + g_string_free(rule_name,TRUE);
    + g_string_free(level_name,TRUE);
    + return rv;
    + }
    +
    + rv=ignorance_level_remove_rule(level,rule_name);
    +
    + if(rv) {
    + gtk_tree_store_remove(GTK_TREE_STORE(model),&iter);
    + }
    +
    + g_string_free(rule_name,TRUE);
    + g_string_free(level_name,TRUE);
    +
    + return rv;
    +}
    +
    +static gboolean del_group_from_form(GtkTreeView *view) {
    + GString *level_name;
    + GtkTreeSelection *sel;
    + GtkTreeIter iter;
    + GtkTreeModel *model;
    + gchar *rule_value;
    + gboolean rv=FALSE;
    +
    + sel=gtk_tree_view_get_selection(GTK_TREE_VIEW(view));
    +
    + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) {
    + return rv;
    + }
    +
    + gtk_tree_model_get (model, &iter, LEVEL_COLUMN, &rule_value, -1);
    +
    + if(!rule_value) {
    + return rv;
    + }else if(!strlen(rule_value)) {
    + g_free(rule_value);
    + return rv;
    + }
    +
    + level_name=g_string_new(rule_value);
    + g_free(rule_value);
    +
    + ignorance_remove_level(level_name);
    + gtk_tree_store_remove(GTK_TREE_STORE(model),&iter);
    + rv=TRUE;
    +
    + return rv;
    +}
    +
    +
    +
    +gboolean load_form_with_levels(GtkTreeView *tree, GPtrArray *levels) {
    + int i,j;
    + GtkTreeStore *store=GTK_TREE_STORE(gtk_tree_view_get_model(tree));
    + GtkTreeIter iter, ruleiter;
    + ignorance_level *level;
    + ignorance_rule *rule;
    +
    + if(!levels) {
    + return FALSE;
    + }
    +
    + for(i=0;i<levels->len;++i) {
    + level=(ignorance_level*)g_ptr_array_index(levels,i);
    + gtk_tree_store_append(store,&iter,NULL);
    + gtk_tree_store_set (store, &iter, LEVEL_COLUMN, level->name->str,
    + RULE_COLUMN, "", -1);
    +
    + for(j=0;j<level->rules->len;++j) {
    + rule=(ignorance_rule*)g_ptr_array_index(level->rules,j);
    + gtk_tree_store_append(store,&ruleiter,&iter);
    + gtk_tree_store_set(store,&ruleiter, LEVEL_COLUMN, level->name->str,
    + RULE_COLUMN, rule->name->str, -1);
    + }
    + }
    +
    + return FALSE;
    +}
    +
    +static gboolean ignorance_rule_valid(const gchar *ruletext, gint ruletype) {
    + gboolean rv=FALSE;
    +
    +#ifdef HAVE_REGEX_H
    + regex_t reg;
    +#endif
    +
    + switch(ruletype) {
    + case IGNORANCE_RULE_SIMPLETEXT:
    + case IGNORANCE_RULE_REPEAT:
    + rv=TRUE;
    + break;
    +#ifdef HAVE_REGEX_H
    + case IGNORANCE_RULE_REGEX:
    + rv=!((gboolean)regcomp(&reg,ruletext,REG_EXTENDED | REG_NOSUB));
    + rv=(rv && strlen(ruletext));
    + regfree(&reg);
    + break;
    +#endif
    + }
    +
    + return rv;
    +}
    +
    +static gboolean ignorance_rulename_valid(const gchar *rule_name) {
    + return TRUE;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/callbacks.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,60 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_CALLBACKS_H
    +#define IGNORANCE_CALLBACKS_H
    +
    +#include <gtk/gtk.h>
    +
    +#ifdef HAVE_REGEX_H
    +# include <regex.h>
    +#endif
    +
    +enum{
    + LEVEL_COLUMN,
    + RULE_COLUMN,
    + NUM_COLUMNS
    +};
    +
    +gboolean on_levelView_row_activated (GtkTreeSelection *sel, gpointer user_data);
    +
    +void on_levelAdd_clicked (GtkButton *button, gpointer user_data);
    +
    +void on_groupAdd_clicked (GtkButton *button, gpointer user_data);
    +
    +void on_levelEdit_clicked (GtkButton *button, gpointer user_data);
    +
    +void on_levelDel_clicked (GtkButton *button, gpointer user_data);
    +
    +void on_sound_browse_clicked (GtkButton *button, gpointer user_data);
    +
    +/*
    +void on_execute_browse_clicked (GtkButton *button, gpointer user_data);
    +*/
    +
    +void on_filter_cb_toggled (GtkButton *button, gpointer user_data);
    +
    +void on_ignore_cb_toggled (GtkButton *button, gpointer user_data);
    +
    +void on_message_cb_toggled (GtkButton *button, gpointer user_data);
    +
    +void on_sound_cb_toggled (GtkButton *button, gpointer user_data);
    +
    +void on_execute_cb_toggled (GtkButton *button, gpointer user_data);
    +
    +gboolean load_form_with_levels (GtkTreeView *tree, GPtrArray *levels);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,1209 @@
    +/*
    + * Ignorance - Make up for deficiencies in Purple's privacy
    + *
    + * Copyright (c) 200?-2006 Levi Bard
    + * Copyright (c) 2005-2006 Peter Lawler
    + * Copyright (c) 2005-2006 John Bailey
    + *
    + * 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.
    + */
    +
    +#define PURPLE_PLUGINS
    +
    +/* ignorance headers */
    +#include "ignorance_level.h"
    +#include "ignorance_internal.h"
    +#include "ignorance.h"
    +#include "ignorance_denizen.h"
    +#include "ignorance_violation.h"
    +#include "ignorance_rule.h"
    +
    +/* GTK Purple headers */
    +#include <gtkplugin.h>
    +#include <gtkutils.h>
    +
    +#include "interface.h"
    +
    +/* libpurple headers */
    +#include <blist.h>
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <purple.h>
    +#include <plugin.h>
    +#include <privacy.h>
    +#include <signals.h>
    +#include <sound.h>
    +#include <util.h>
    +#include <version.h>
    +
    +/* libc headers */
    +#include <errno.h>
    +#include <signal.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <stdlib.h>
    +#include <time.h>
    +#include <unistd.h>
    +
    +/* for OSes that can at least *pretend* to be decent... */
    +#ifndef _WIN32
    +# include <sys/wait.h>
    +# include <sys/types.h>
    +# include <sys/select.h>
    +# include <fcntl.h>
    +# include <sys/stat.h>
    +#endif
    +
    +/* globals */
    +static GPtrArray *levels;
    +
    +static ignorance_level* ignorance_get_default_level() {
    + gint i = 0;
    + ignorance_level *il;
    +
    + for(i = 0; i < levels->len; ++i) {
    + il = (ignorance_level*)g_ptr_array_index(levels, i);
    +
    + if(!strcmp(il->name->str, "Default"))
    + return il;
    + }
    +
    + return NULL;
    +}
    +
    +static gboolean ignorance_user_match(ignorance_level *il, const GString *username){
    + return (ignorance_level_has_denizen(il,username));
    +}
    +
    +static ignorance_level* ignorance_get_user_level(const GString *username){
    + gint i = 0;
    + ignorance_level *il = NULL;
    +
    + for(i = 0; i < levels->len; ++i){
    + il = (ignorance_level*)g_ptr_array_index(levels, i);
    + if(ignorance_user_match(il, username))
    + return il;
    + }
    +
    + return ignorance_get_default_level();
    +}
    +
    +static void purple_buddy_add(gpointer key, gpointer value, gpointer user_data) {
    + PurpleBuddy *buddy = (PurpleBuddy*)value;
    + ignorance_level *level = (ignorance_level*)user_data;
    + PurpleAccount *account = NULL;
    + gchar *name = NULL;
    + GString *tmp;
    +
    + if(buddy && level) {
    + name = (gchar*)buddy->name;
    + account = buddy->account;
    + tmp = g_string_new(purple_account_get_protocol_id(account));
    + g_string_append(tmp, purple_normalize_nocase(account, name));
    +
    + if(ignorance_get_user_level(tmp) == ignorance_get_default_level()) {
    + ignorance_level_add_denizen(level, tmp);
    +
    + if(strstr(level->name->str, "WL")) {
    + purple_privacy_deny_remove(account, name, FALSE);
    + purple_privacy_permit_add(account, name, FALSE);
    + } else if(strstr(level->name->str,"BL")) {
    + purple_privacy_permit_remove(account, name, FALSE);
    + purple_privacy_deny_add(account, name, FALSE);
    + }
    + }
    +
    + g_string_free(tmp, TRUE);
    + } else {
    + purple_debug_error("ignorance", "Bad arguments to purple_buddy_add\n");
    + }
    +}
    +
    +static gint buf_get_line(gchar *ibuf, gchar **buf, gint *position, gint len) {
    + gint pos = *position,
    + spos = pos;
    +
    + if (pos == len)
    + return 0;
    +
    + while (ibuf[pos++] != '\n') {
    + if (pos == len)
    + return 0;
    + }
    +
    + pos--;
    + ibuf[pos] = 0;
    + *buf = &ibuf[spos];
    + pos++;
    + *position = pos;
    +
    + return 1;
    +}
    +
    +static gboolean import_curphoo_list() {
    + gchar *buf, *ibuf;
    + gint pnt = 0,
    + rule_flags = IGNORANCE_APPLY_CHAT | IGNORANCE_APPLY_IM | IGNORANCE_APPLY_ENTERLEAVE;
    + gsize size = 0;
    + FILE *curphoofile;
    + ignorance_level *curphoolevel = NULL;
    + ignorance_rule *curphoorule = NULL;
    + GString *tmp = NULL;
    +
    + buf = g_build_filename(g_get_home_dir(), ".curphoo", "ignore", NULL);
    +
    + if(!(curphoofile = fopen(buf, "r"))) {
    + purple_debug_error("ignorance", "Unable to open %s\n", buf);
    + g_free(buf);
    +
    + return FALSE;
    + } else
    + fclose(curphoofile);
    +
    + g_file_get_contents(buf, &ibuf, &size, NULL);
    +
    + tmp = g_string_new("CurphooBL");
    + curphoolevel = ignorance_get_level_name(tmp);
    +
    + if(!curphoolevel) {
    + purple_debug_info("ignorance", "Creating new Curphoo blacklist\n");
    +#ifdef HAVE_REGEX_H
    + curphoorule = ignorance_rule_newp(g_string_new("Everything"),
    + IGNORANCE_RULE_REGEX,(gchar*)".*", IGNORANCE_FLAG_FILTER,
    + rule_flags, TRUE, NULL, NULL, NULL);
    +#else
    + curphoorule=ignorance_rule_newp(g_string_new("Everything"),
    + IGNORANCE_RULE_SIMPLETEXT, (gchar*)"", IGNORANCE_FLAG_FILTER,
    + rule_flags, TRUE, NULL, NULL, NULL);
    +#endif
    + curphoolevel = ignorance_level_new();
    + curphoolevel->name = g_string_new(tmp->str);
    + ignorance_level_add_rule(curphoolevel, curphoorule);
    + ignorance_add_level(curphoolevel);
    + }
    +
    + if(!tmp)
    + tmp = g_string_new("");
    +
    + g_free(buf);
    +
    + purple_debug_info("ignorance", "Preparing to read in curphoo blacklist users\n");
    +
    + for(; buf_get_line(ibuf, &buf, &pnt, size); ){
    + g_string_assign(tmp, "prpl-yahoo");
    + g_string_append(tmp, purple_normalize_nocase(NULL,buf));
    +
    + if(ignorance_get_user_level(tmp) == ignorance_get_default_level())
    + ignorance_level_add_denizen(curphoolevel, tmp);
    + }
    +
    + g_free(ibuf);
    +
    + pnt = 0;
    + buf = g_build_filename(g_get_home_dir(), ".curphoo", "buddies", NULL);
    +
    + g_file_get_contents(buf, &ibuf, &size, NULL);
    +
    + g_string_assign(tmp, "CurphooWL");
    +
    + curphoolevel = ignorance_get_level_name(tmp);
    +
    + if(!curphoolevel) {
    + purple_debug_info("ignorance", "Creating new Curphoo whitelist\n");
    +
    + curphoolevel = ignorance_level_new();
    + curphoolevel->name = g_string_new(tmp->str);
    + ignorance_add_level(curphoolevel);
    + }
    +
    + g_free(buf);
    +
    + purple_debug_info("ignorance", "Preparing to read in curphoo whitelist users\n");
    +
    + for(; buf_get_line(ibuf, &buf, &pnt, size); ) {
    + g_string_assign(tmp, "prpl-yahoo");
    + g_string_append(tmp, purple_normalize_nocase(NULL, buf));
    +
    + if(ignorance_get_user_level(tmp) == ignorance_get_default_level())
    + ignorance_level_add_denizen(curphoolevel, tmp);
    + }
    +
    + g_free(ibuf);
    +
    + purple_debug_info("ignorance", "Done importing Curphoo users\n");
    +
    + return TRUE;
    +}
    +
    +static gboolean import_purple_list() {
    + PurpleBuddyList *bl;
    + GString *wlname;
    + ignorance_level *wl;
    + gboolean rv = FALSE;
    +
    + bl = purple_get_blist();
    + wlname = g_string_new("WL");
    + wl = ignorance_get_level_name(wlname);
    +
    + if(bl && wl) {
    + g_hash_table_foreach(bl->buddies, purple_buddy_add, wl);
    + rv = TRUE;
    + } else
    + purple_debug_error("ignorance", "Unable to get Purple buddy list!\n");
    +
    + g_string_free(wlname, TRUE);
    +
    + return rv;
    +}
    +
    +static gboolean import_zinc_list()
    +{
    + FILE *zincfile;
    + gchar *buf, *ibuf;
    + gint pnt = 0,
    + rule_flags = IGNORANCE_APPLY_CHAT | IGNORANCE_APPLY_IM | IGNORANCE_APPLY_ENTERLEAVE;
    + gsize size = 0;
    + ignorance_level *zinclevel = NULL;
    + ignorance_rule *zincrule = NULL;
    + GString *tmp = NULL;
    +
    + buf = g_build_filename(g_get_home_dir(), ".zinc", "ignore", NULL);
    +
    + if(!(zincfile = fopen(buf, "r"))) {
    + purple_debug_error("ignorance", "Unable to open %s\n",buf);
    +
    + g_free(buf);
    +
    + return FALSE;
    + } else
    + fclose(zincfile);
    +
    + g_file_get_contents(buf, &ibuf, &size, NULL);
    +
    + tmp = g_string_new("ZincBL");
    +
    + zinclevel = ignorance_get_level_name(tmp);
    +
    + if(!zinclevel){
    + purple_debug_info("ignorance", "Creating new Zinc blacklist\n");
    +#ifdef HAVE_REGEX_H
    + zincrule = ignorance_rule_newp(g_string_new("Everything"),
    + IGNORANCE_RULE_REGEX, (gchar*)".*", IGNORANCE_FLAG_FILTER,
    + rule_flags, TRUE, NULL, NULL, NULL);
    +#else
    + zincrule = ignorance_rule_newp(g_string_new("Everything"),
    + IGNORANCE_RULE_SIMPLETEXT, (gchar*)"", IGNORANCE_FLAG_FILTER,
    + rule_flags, TRUE, NULL, NULL, NULL);
    +#endif
    + zinclevel = ignorance_level_new();
    + zinclevel->name = g_string_new(tmp->str);
    + ignorance_level_add_rule(zinclevel, zincrule);
    + ignorance_add_level(zinclevel);
    + }
    +
    + if(!tmp)
    + tmp=g_string_new("");
    +
    + g_free(buf);
    +
    + purple_debug_info("ignorance", "Preparing to read in zinc blacklist users\n");
    +
    + for(; buf_get_line(ibuf, &buf, &pnt, size); ) {
    + g_string_assign(tmp, "prpl-yahoo");
    + g_string_append(tmp, purple_normalize_nocase(NULL, buf));
    + if(ignorance_get_user_level(tmp) == ignorance_get_default_level())
    + ignorance_level_add_denizen(zinclevel, tmp);
    + }
    +
    + g_free(ibuf);
    +
    + pnt = 0;
    + buf = g_build_filename(g_get_home_dir(), ".zinc", "whitelist", NULL);
    +
    + g_file_get_contents(buf, &ibuf, &size, NULL);
    +
    + g_string_assign(tmp, "ZincWL");
    +
    + zinclevel = ignorance_get_level_name(tmp);
    +
    + if(!zinclevel) {
    + purple_debug_info("ignorance", "Creating new Zinc whitelist\n");
    + zinclevel = ignorance_level_new();
    + zinclevel->name = g_string_new(tmp->str);
    + ignorance_add_level(zinclevel);
    + }
    +
    + g_free(buf);
    +
    + purple_debug_info("ignorance", "Preparing to read in zinc whitelist users\n");
    +
    + for(; buf_get_line(ibuf, &buf, &pnt, size); ) {
    + /* maybe this will be this way eventually, when i kill unnecessary
    + * GStrings - rekkanoryo */
    + /* tmp = g_strdup_printf("prpl-yahoo %s", purple_normalize_nocase(NULL, buf)); */
    + g_string_assign(tmp, "prpl-yahoo");
    + g_string_append(tmp, purple_normalize_nocase(NULL, buf));
    + if(ignorance_get_user_level(tmp) == ignorance_get_default_level())
    + ignorance_level_add_denizen(zinclevel, tmp);
    + }
    +
    + g_free(ibuf);
    +
    + purple_debug_info("ignorance", "Done importing Zinc users\n");
    +
    + return TRUE;
    +}
    +
    +static gboolean ignorance_rm_user(PurpleConversation *conv, const gchar *username) {
    + gchar *msgbuf = NULL, *cursor = NULL;
    + gboolean retval = FALSE;
    + GString *usergs;
    + ignorance_level *level = NULL;
    + int len=0;
    +
    + usergs = g_string_new(purple_normalize_nocase(NULL, username));
    +
    + level=ignorance_get_user_level(usergs);
    +
    + if(level) {
    + retval = ignorance_level_remove_denizen(level, usergs);
    +
    + purple_debug_info("ignorance", "Done removing denizen from level\n");
    +
    + if(conv) {
    + purple_debug_info("ignorance",
    + "Creating status message for username %x and level %x\n",
    + username, level);
    +
    + if(retval) {
    + msgbuf = g_strdup_printf(_("Successfully removed %s from %s"),
    + username, level->name->str);
    +
    + retval = TRUE;
    + } else
    + msgbuf = g_strdup_printf(_("Unable to remove %s from %s\n"),
    + username, level->name->str);
    +
    + purple_debug_info("ignorance", "Writing status message\n");
    +
    + purple_conversation_write(conv, NULL, msgbuf, PURPLE_MESSAGE_NO_LOG,
    + time(NULL));
    +
    + g_free(msgbuf);
    + }
    + }
    +
    + if(conv) {
    + purple_debug_info("ignorance",
    + "Preparing to push through to purple privacy\n");
    +
    + len = strlen(purple_account_get_protocol_id(
    + purple_conversation_get_account(conv)));
    +
    + cursor = (gchar*)username + len;
    +
    + if(cursor && (strlen(username) > len)) {
    + purple_debug_info("ignorance", "Removing from permit list\n");
    +
    + purple_privacy_permit_remove(purple_conversation_get_account(conv),
    + cursor, FALSE);
    +
    + purple_debug_info("ignorance", "Removing from deny list\n");
    +
    + purple_privacy_deny_remove(purple_conversation_get_account(conv), cursor,
    + FALSE);
    +
    + if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT) {
    + purple_debug_info("ignorance", "Removing from chat ignore list\n");
    +
    + purple_conv_chat_unignore(PURPLE_CONV_CHAT(conv), cursor);
    + }
    + }
    + }
    +
    + g_string_free(usergs, TRUE);
    +
    + purple_debug_info("ignorance", "Exiting\n");
    +
    + return retval;
    +}
    +
    +static gboolean
    +ignorance_place_user_name(const GString *level_name, const GString *username) {
    + ignorance_level *current_level = ignorance_get_user_level(username),
    + *newlevel = ignorance_get_level_name(level_name);
    +
    + if(newlevel) {
    + if(newlevel != current_level) {
    + ignorance_level_remove_denizen(current_level, username);
    + ignorance_level_add_denizen(newlevel, username);
    +
    + return TRUE;
    + }
    + } else
    + purple_debug_error("ignorance", "Invalid level %s\n", level_name->str);
    +
    + return FALSE;
    +}
    +
    +static gboolean ignorance_bl_user(PurpleConversation *conv, const gchar *username,
    + const gchar *actual_levelname)
    +{
    + gboolean retval = FALSE;
    + gchar *msgbuf = NULL;
    + GString *wlname, *usergs;
    + PurpleAccount *account = NULL;
    +
    + wlname = g_string_new(actual_levelname);
    +
    + g_return_val_if_fail(conv != NULL, retval);
    +
    + account = purple_conversation_get_account(conv);
    + usergs = g_string_new(purple_account_get_protocol_id(account));
    + g_string_append(usergs, purple_normalize_nocase(NULL, username));
    +
    + if(ignorance_place_user_name(wlname, usergs)){
    + retval = TRUE;
    + purple_privacy_permit_remove(account, username, FALSE);
    + purple_privacy_deny_add(account, username, FALSE);
    +
    + if(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT)
    + purple_conv_chat_ignore(PURPLE_CONV_CHAT(conv), username);
    +
    + msgbuf = g_strdup_printf(_("Assigned user %s to %s"), username,
    + actual_levelname);
    + } else
    + msgbuf = g_strdup_printf(
    + _("Unable to assign user %s to %s - may already be there"),
    + username, actual_levelname);
    +
    + purple_conversation_write(conv, NULL, msgbuf, PURPLE_MESSAGE_NO_LOG, time(NULL));
    +
    + g_free(msgbuf);
    + g_string_free(usergs, TRUE);
    + g_string_free(wlname, TRUE);
    +
    + return retval;
    +}
    +
    +#ifndef _WIN32
    +static int read_nonblock(int fd,unsigned long len,unsigned long timeout,GString *inp){
    + int chrs=0, timedout=FALSE, rv=0;
    + unsigned long timeout_usec=timeout*1000000, tlapsed=0;
    + static const int SLEEP_INTERVAL=50000;
    +
    + gchar *ptr=g_malloc((len+1)*sizeof(gchar));
    +
    + while (chrs < len){
    + if (tlapsed > timeout_usec){
    + timedout = TRUE;
    + break;
    + }
    + if ((rv = read (fd, ptr, (len - chrs))) < 0){
    + rv = errno;
    + usleep (SLEEP_INTERVAL);
    + tlapsed += SLEEP_INTERVAL;
    + continue;
    + } else if (rv == 0)
    + break;
    + chrs += rv;
    + *(ptr + rv)= 0;
    + g_string_append(inp,ptr);
    + *ptr=0;
    + }
    + g_free(ptr);
    + if(timedout)
    + return -1;
    + else
    + return chrs;
    +}
    +#endif
    +
    +static gchar *
    +yahoo_strip_tattoo(gchar *origmessage) {
    + gchar *cursor = NULL, *cursor2 = NULL, *beginstack = NULL,
    + *endstack = NULL, *message = NULL;
    + gint rvindex;
    +
    + message = g_ascii_strdown(origmessage, -1);
    + cursor = strstr(message, "<font");
    +
    + if(message == cursor) {
    + cursor2 = strstr(message, "tattoo");
    +
    + if(cursor2) {
    + cursor = strstr(cursor2, ">");
    +
    + for( ; cursor; ) {
    + endstack = strstr(cursor, "</");
    + beginstack = strstr(cursor, "<");
    +
    + if(endstack == NULL || beginstack== NULL) {
    + cursor = NULL;
    + break;
    + }
    +
    + cursor = strstr(endstack, ">");
    +
    + if(beginstack == endstack)
    + break;
    + }
    +
    + if(cursor) {
    + rvindex = cursor + 1 - message;
    +
    + purple_debug_info("yahoo", "%s\nconverted to \n%s\n%s\n\n",
    + origmessage, cursor + 1, origmessage + rvindex);
    +
    + g_free(message);
    +
    + return origmessage + rvindex;
    + }
    + }
    + }
    +
    + g_free(message);
    +
    + return origmessage;
    +}
    +
    +static int
    +handle_exec_command (const gchar *command, GString *result,unsigned long maxlen) {
    +#ifndef _WIN32
    + GString *inp=g_string_new("");
    + int p[2], pid, chrs;
    +
    + pipe(p);
    +
    + chrs = inp->len;
    + if ((pid = fork ()) == -1) {
    + g_string_assign (result, command);
    + g_string_append (result, ": couldn't fork");
    + return -1;
    + } else if (pid){
    + int rv;
    + int flags = fcntl (p[0], F_GETFL, 0);
    +
    + close (p[1]);
    + fcntl (p[0], F_SETFL, flags | O_NONBLOCK);
    +
    + rv=read_nonblock(p[0],maxlen-chrs,EXEC_TIMEOUT,inp);
    +
    + if (kill (pid, 0) == 0)
    + kill (pid, SIGKILL);
    + if (rv < 0)
    + g_string_append (inp, "[process timed out]");
    + else if(maxlen==(rv+chrs))
    + g_string_append(inp,"...");
    + else if(('\n'==(inp->str)[inp->len-1]))
    + g_string_truncate(inp,inp->len-1);
    +
    + g_string_assign (result, inp->str);
    +
    + g_string_free(inp,TRUE);
    + waitpid (pid, NULL, 0);
    + } else {
    + close (p[0]);
    + if (p[1] != STDOUT_FILENO){
    + dup2 (p[1], STDOUT_FILENO);
    + close (p[1]);
    + }
    + if (STDERR_FILENO != STDOUT_FILENO)
    + dup2 (STDOUT_FILENO, STDERR_FILENO);
    + execlp ("sh", "sh", "-c", command, NULL);
    + }
    + return 0;
    +#else
    + gint retval;
    + gchar *message = NULL;
    +
    + g_string_assign (result, command);
    + g_string_append (result, ": ");
    +
    + if (G_WIN32_HAVE_WIDECHAR_API ()) {
    + wchar_t *wc_cmd = g_utf8_to_utf16(command,
    + -1, NULL, NULL, NULL);
    +
    + retval = (gint)ShellExecuteW(NULL,NULL,wc_cmd,NULL,NULL,SW_SHOWNORMAL);
    + g_free(wc_cmd);
    + } else {
    + char *l_cmd = g_locale_from_utf8(command,
    + -1, NULL, NULL, NULL);
    +
    + retval = (gint)ShellExecuteA(NULL,NULL,l_cmd,NULL,NULL,SW_SHOWNORMAL);
    + g_free(l_cmd);
    + }
    +
    + if (retval<=32) {
    + message = g_win32_error_message(retval);
    + g_string_append(result,message);
    + }
    +
    + purple_debug_info("Ignorance", "Execute command called for: "
    + "%s\n%s%s%s", command, retval ? "" : "Error: ",
    + retval ? "" : message, retval ? "" : "\n");
    + g_free(message);
    + return 0;
    +#endif
    +}
    +
    +static gboolean
    +apply_rule(PurpleConversation *conv, PurpleAccount *account,
    + const GString *username, const GString *text, gint flags)
    +{
    + gint text_score = 0;
    + gboolean rv = TRUE, newconv = FALSE;
    + GList *violations = NULL, *cursor = NULL;
    + GString *tmp;
    + GString *prpluser = g_string_new(purple_account_get_protocol_id(account));
    + ignorance_level *user_level;
    + ignorance_violation *viol;
    + PurpleConversationType conv_type;
    +
    + g_string_append(prpluser, purple_normalize_nocase(account, username->str));
    + user_level=ignorance_get_user_level(prpluser);
    +
    + purple_debug_info("ignorance", "Preparing to check %s\n", text->str);
    +
    + text_score=ignorance_level_rulecheck(user_level, prpluser, text, flags,
    + &violations);
    +
    + purple_debug_info("ignorance", "Got score %d\n", text_score);
    +
    + if(!(text_score & (IGNORANCE_FLAG_FILTER | IGNORANCE_FLAG_IGNORE))) {
    + rv = FALSE;
    + if(text_score) {
    + for(cursor = violations; cursor; cursor = cursor->next) {
    + viol = (ignorance_violation*)(cursor->data);
    + purple_debug_info("ignorance", "Got violation type %d: %s\n",
    + viol->type,viol->value);
    +
    + switch(viol->type) {
    + case IGNORANCE_FLAG_SOUND:
    + purple_debug_info("ignorance",
    + "Attempting to play sound %s\n", viol->value);
    +
    + purple_sound_play_file(viol->value, account);
    +
    + break;
    +
    + case IGNORANCE_FLAG_EXECUTE:
    + purple_debug_info("ignorance",
    + "Attempting to execute command %s\n",
    + viol->value);
    +
    + tmp = g_string_new("");
    +
    + handle_exec_command(viol->value, tmp, 512);
    +
    + if(conv)
    + purple_conversation_write(conv,
    + purple_account_get_username(account),
    + tmp->str, PURPLE_MESSAGE_NO_LOG, time(NULL));
    +
    + g_string_free(tmp, TRUE);
    +
    + break;
    +
    + case IGNORANCE_FLAG_MESSAGE:
    + if(!conv) {
    + newconv = TRUE;
    + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM,
    + account, username->str);
    + }
    +
    + conv_type = purple_conversation_get_type(conv);
    +
    + if(conv_type == PURPLE_CONV_TYPE_IM) {
    + purple_conv_im_send(PURPLE_CONV_IM(conv), viol->value);
    + } else if(conv_type == PURPLE_CONV_TYPE_CHAT) {
    + purple_conv_chat_send(PURPLE_CONV_CHAT(conv),
    + viol->value);
    + } /* braces for readability only */
    +
    + if(newconv)
    + purple_conversation_destroy(conv);
    +
    + break;
    +
    + default:
    + break;
    + }
    + }
    + }
    + } else if(text_score & IGNORANCE_FLAG_IGNORE)
    + ignorance_bl_user(conv, username->str, "BL");
    +
    + g_string_free(prpluser, TRUE);
    +
    + purple_debug_info("ignorance", "Preparing to free violation items\n");
    + g_list_foreach(violations, ignorance_violation_free_g, NULL);
    +
    + purple_debug_info("ignorance",
    + "Done freeing violation items, now freeing list itself\n");
    + g_list_free(violations);
    +
    + purple_debug_info("ignorance",
    + "Done checking, returning from applying rules\n");
    +
    + return rv;
    +}
    +
    +static gboolean substitute (PurpleConversation *conv,PurpleAccount *account,
    + const gchar *sender, gchar **message, gint msgflags) {
    + GString *username=NULL, *text=NULL;
    + gboolean rv=FALSE;
    + gchar *newmsg, *cursor;
    +
    + if (NULL == message)
    + return FALSE;
    + else if (NULL == (*message))
    + return FALSE;
    +
    +
    + username=g_string_new(purple_normalize_nocase(account,sender));
    +
    + purple_debug_info("ignorance","Got message \"%s\" from user \"%s\"\n",*message,sender);
    +
    + cursor=yahoo_strip_tattoo(*message);
    + if(cursor!=*message){
    + newmsg=g_strdup(cursor);
    + g_free(*message);
    + (*message)=newmsg;
    + }
    +
    + text=g_string_new(*message);
    +
    + rv=apply_rule(conv,account,username,text,msgflags);
    +
    + if(rv)
    + purple_debug_info("ignorance", "%s: %s violated!\n",username->str,text->str);
    +
    + g_string_free(username,TRUE);
    + g_string_free(text,TRUE);
    +
    + purple_debug_info("ignorance","Returning from substitution\n");
    +
    + return rv;
    +}
    +
    +static gboolean chat_cb(PurpleAccount *account, gchar **sender, gchar **buffer,
    + PurpleConversation *chat, void *data){
    + return substitute(chat, account, *sender, buffer,
    + IGNORANCE_APPLY_CHAT | IGNORANCE_APPLY_USER);
    +}
    +
    +static gboolean im_cb(PurpleAccount *account, gchar **sender, gchar **buffer,
    + gint *flags, void *data){
    + PurpleConversation *gc;
    +
    + gc=purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,*sender,account);
    +
    + return substitute(gc, account, *sender,buffer,
    + IGNORANCE_APPLY_IM | IGNORANCE_APPLY_USER);
    +}
    +
    +static gboolean chat_joinleave_cb (PurpleConversation *conv, const gchar *name,
    + void *data){
    + gchar *message=g_strdup(name);
    + gboolean rv=substitute(conv, purple_conversation_get_account(conv), name,
    + &message,IGNORANCE_APPLY_ENTERLEAVE | IGNORANCE_APPLY_USER);
    + g_free(message);
    +
    + return rv;
    +}
    +
    +static gint chat_invited_cb(PurpleAccount *account,const gchar *inviter, const gchar *chat, const gchar *invite_message, const GHashTable *components, void *data){
    + gchar *message=g_strdup(invite_message);
    + gboolean rv;
    + gint invite_ask=0;
    + PurpleConversation *gc;
    + g_free(message);
    +
    + gc=purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,inviter,account);
    +
    + rv=substitute(gc,account,inviter,&message,IGNORANCE_APPLY_INVITE | IGNORANCE_APPLY_USER | IGNORANCE_APPLY_CHAT);
    +
    + if(rv)
    + invite_ask=-1;
    +
    + return invite_ask;
    +}
    +
    +static void buddy_added_cb(PurpleBuddy *buddy, gpointer data){
    + GString *wlname=g_string_new("WL");
    + ignorance_level *wl=ignorance_get_level_name(wlname);
    + PurpleAccount *account=buddy->account;
    + gchar *name=(gchar*)buddy->name;
    +
    + purple_debug_info("ignorance","Caught buddy-added for %s%s\n",
    + purple_account_get_protocol_id(account), name);
    + purple_buddy_add(NULL,buddy,wl);
    +
    + g_string_free(wlname,TRUE);
    +}
    +
    +static void buddy_removed_cb(PurpleBuddy *buddy, gpointer data){
    + GString *tmp=NULL;
    + PurpleConversation *conv=NULL;
    + PurpleAccount *account=buddy->account;
    + gchar *name=(gchar*)buddy->name;
    +
    + tmp=g_string_new(purple_account_get_protocol_id(account));
    +
    + conv=purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,tmp->str,account);
    +
    + purple_debug_info("ignorance","Caught buddy-removed for %s%s\n",
    + purple_account_get_protocol_id(account), name);
    +
    + g_string_append(tmp,purple_normalize_nocase(account,name));
    +
    + ignorance_rm_user(conv,tmp->str);
    +
    + g_string_free(tmp,TRUE);
    +}
    +
    +static gboolean generate_default_levels() {
    + ignorance_level *tmplvl;
    + ignorance_rule *tmprule;
    + GString *tmpgs;
    +
    + tmplvl=ignorance_level_new();
    + tmplvl->name=g_string_new("Default");
    + ignorance_add_level(tmplvl);
    +
    + tmplvl=ignorance_level_new();
    + tmplvl->name=g_string_new("WL");
    + ignorance_add_level(tmplvl);
    +
    + tmplvl=ignorance_level_new();
    + tmplvl->name=g_string_new("BL");
    + ignorance_add_level(tmplvl);
    +
    + tmpgs=g_string_new("Everything");
    +#ifdef HAVE_REGEX_H
    + tmprule=ignorance_rule_newp(tmpgs,IGNORANCE_RULE_REGEX,".*",
    + IGNORANCE_FLAG_FILTER,
    + IGNORANCE_APPLY_CHAT | IGNORANCE_APPLY_IM,
    + TRUE,NULL,NULL,NULL);
    +#else
    + tmprule=ignorance_rule_newp(tmpgs,IGNORANCE_RULE_SIMPLETEXT,"",
    + IGNORANCE_FLAG_FILTER,
    + IGNORANCE_APPLY_CHAT | IGNORANCE_APPLY_IM,
    + TRUE,NULL,NULL,NULL);
    +#endif
    + ignorance_level_add_rule(tmplvl,tmprule);
    + g_string_free(tmpgs,TRUE);
    +
    + return TRUE;
    +}
    +
    +gboolean save_conf()
    +{
    + FILE *f;
    + gchar *name, tempfilename[BUF_LONG];
    + gint fd, i;
    +
    + name = g_build_filename(purple_user_dir(), "ignorance", NULL);
    + strcpy(tempfilename, name);
    + strcat(tempfilename,".XXXXXX");
    + fd = g_mkstemp(tempfilename);
    + if(fd<0) {
    + perror(tempfilename);
    + g_free(name);
    + return FALSE;
    + }
    + if (!(f = fdopen(fd, "w"))) {
    + perror("fdopen");
    + close(fd);
    + g_free(name);
    + return FALSE;
    + }
    +
    + fchmod(fd, S_IRUSR | S_IWUSR);
    +
    + for(i=0;i<levels->len;++i){
    + ignorance_level_write(g_ptr_array_index(levels,i),f);
    + }
    +
    + if(fclose(f)) {
    + purple_debug_error("ignorance",
    + "Error writing to %s: %m\n", tempfilename);
    + unlink(tempfilename);
    + g_free(name);
    + return FALSE;
    + }
    + rename(tempfilename, name);
    + g_free(name);
    + return TRUE;
    +}
    +
    +ignorance_level* ignorance_get_level_name(const GString *levelname){
    + gint i=0;
    + ignorance_level *il=NULL;
    +
    + for(i=0;i<levels->len;++i){
    + il=(ignorance_level*)g_ptr_array_index(levels,i);
    + if(g_string_equal(levelname,il->name))
    + return il;
    + }
    +
    + return NULL;
    +}
    +
    +gboolean ignorance_add_level(ignorance_level *level){
    + gboolean rv=FALSE;
    +
    + if(level){
    + g_ptr_array_add(levels,level);
    + rv=TRUE;
    + }
    +
    + return rv;
    +}
    +
    +gboolean ignorance_remove_level(const GString *levelname){
    + ignorance_level *level=ignorance_get_level_name(levelname);
    + gboolean rv=FALSE;
    +
    + if(level){
    + rv=g_ptr_array_remove(levels,level);
    + ignorance_level_free(level);
    + }
    +
    + return rv;
    +}
    +
    +static void
    +ignorance_signals_connect(PurplePlugin *plugin)
    +{
    + void *conv_handle, *blist_handle;
    +
    + conv_handle = purple_conversations_get_handle();
    + blist_handle = purple_blist_get_handle();
    +
    + purple_signal_connect(conv_handle, "receiving-im-msg", plugin,
    + PURPLE_CALLBACK (im_cb), NULL);
    + purple_signal_connect(conv_handle, "receiving-chat-msg", plugin,
    + PURPLE_CALLBACK (chat_cb), NULL);
    + purple_signal_connect(conv_handle,"chat-buddy-joining", plugin,
    + PURPLE_CALLBACK(chat_joinleave_cb),NULL);
    + purple_signal_connect(conv_handle,"chat-buddy-leaving", plugin,
    + PURPLE_CALLBACK(chat_joinleave_cb),NULL);
    + purple_signal_connect(conv_handle,"chat-invited", plugin,
    + PURPLE_CALLBACK(chat_invited_cb),NULL);
    + purple_signal_connect(blist_handle,"buddy-added", plugin,
    + PURPLE_CALLBACK(buddy_added_cb),NULL);
    + purple_signal_connect(blist_handle,"buddy-removed", plugin,
    + PURPLE_CALLBACK(buddy_removed_cb),NULL);
    +
    + return;
    +}
    +
    +static gboolean load_conf() {
    + gchar *buf, *ibuf;
    + gint pnt = 0;
    + gsize size;
    + FILE *conffile = NULL;
    + static ignorance_level *tmplvl = NULL;
    + static ignorance_rule *tmprule = NULL;
    + GString *tmpgs = NULL;
    +
    + buf = g_build_filename(purple_user_dir(), "ignorance", NULL);
    +
    + purple_debug_info("ignorance", "Attempting to load conf file %s\n",buf);
    +
    + levels = g_ptr_array_new();
    +
    + if(!(conffile = fopen(buf, "r"))) {
    + g_free(buf);
    +
    + buf=g_build_filename(IGNORANCE_CONFDIR,"ignorance.conf",NULL);
    +
    + if(!(conffile = fopen(buf,"r"))) {
    + purple_debug_info("ignorance",
    + "Unable to open local or global conf files; falling back to defaults\n");
    + generate_default_levels();
    + import_purple_list();
    + import_zinc_list();
    + import_curphoo_list();
    +
    + g_free(buf);
    + return FALSE;
    + }
    + }
    +
    + g_file_get_contents(buf, &ibuf, &size, NULL);
    + fclose(conffile);
    + g_free(buf);
    +
    + if(!ibuf) {
    + generate_default_levels();
    + import_purple_list();
    + import_zinc_list();
    + import_curphoo_list();
    + return FALSE;
    + }
    +
    + while(buf_get_line(ibuf, &buf, &pnt, size)) {
    + if((*buf) == '#'){
    + /* ignore */
    + } else if(strstr(buf, "level")){
    + if('\0' == buf[5]){
    + tmpgs = g_string_new("");
    +
    + for( ; buf != strstr(buf, "/level");
    + buf_get_line(ibuf, &buf, &pnt, size))
    + {
    + g_string_append(tmpgs, buf);
    + g_string_append(tmpgs, "\n");
    + }
    +
    + tmplvl = ignorance_level_read(tmpgs->str);
    +
    + g_string_free(tmpgs, TRUE);
    + } else
    + tmplvl=ignorance_level_read(buf);
    +
    + if(tmplvl) {
    + purple_debug_info("ignorance", "Adding level %s\n",
    + tmplvl->name->str);
    +
    + ignorance_add_level(tmplvl);
    + }
    + } else if(strstr(buf, "rule") && tmplvl) {
    + if('\0' == buf[4]) {
    + tmpgs = g_string_new("");
    +
    + for( ; buf != strstr(buf, "/rule");
    + buf_get_line(ibuf, &buf, &pnt, size))
    + {
    + g_string_append(tmpgs, buf);
    + g_string_append(tmpgs, "\n");
    + }
    +
    + purple_debug_info("ignorance", "Attempting to read rule %s\n",
    + tmpgs->str);
    +
    + tmprule = ignorance_rule_read(tmpgs->str);
    +
    + g_string_free(tmpgs, TRUE);
    + } else
    + tmprule = ignorance_rule_read(buf);
    + if(tmprule) {
    + purple_debug_info("ignorance", "Adding rule %s: %s\n",
    + tmprule->name->str, (gchar*)(tmprule->value));
    +
    + ignorance_level_add_rule(tmplvl, tmprule);
    + }
    + } else if(tmplvl) {
    + tmpgs = g_string_new(purple_normalize_nocase(NULL, buf));
    +
    + purple_debug_info("ignorance", "Adding denizen %s\n", buf);
    +
    + if(ignorance_get_user_level(tmpgs) == ignorance_get_default_level())
    + ignorance_level_add_denizen(tmplvl, tmpgs);
    +
    + g_string_free(tmpgs,TRUE);
    + }
    + }
    +
    + g_free(ibuf);
    +
    + import_purple_list();
    + import_zinc_list();
    + import_curphoo_list();
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +ignorance_load (PurplePlugin *plugin) {
    + purple_debug_info("ignorance", "Loading ignorance plugin");
    +
    + load_conf();
    +
    + ignorance_signals_connect(plugin);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + purple_debug_info("ignorance", "Unloading ignorance plugin\n");
    + save_conf();
    +
    + return TRUE;
    +}
    +
    +static GtkWidget *get_config_frame(PurplePlugin *plugin) {
    + return create_uiinfo(levels);
    +}
    +
    +static PidginPluginUiInfo ui_info = {
    + get_config_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static PurplePluginInfo ig_info = {
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + PIDGIN_PLUGIN_TYPE,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + IGNORANCE_PLUGIN_ID,
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Peter Lawler <bleeter from users.sf.net>, "
    + "Levi Bard <taktaktaktaktaktaktaktaktaktak@gmail.com> (original author)",
    + "http://guifications.sourceforge.net",
    + ignorance_load,
    + plugin_unload,
    + NULL,
    + &ui_info,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void
    +ignorance_init (PurplePlugin * plugin)
    +{
    +#ifdef ENABLE_NLS
    + bindtextdomain(PP_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(PP_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + ig_info.name = _("Ignorance");
    + ig_info.summary =
    + _("Allows you to manage lists of users with various levels of allowable activity.");
    + ig_info.description =
    + _("Allows you to manage lists of users with various levels of allowable activity.");
    +}
    +
    +PURPLE_INIT_PLUGIN (ignorance, ignorance_init, ig_info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance.conf Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,20 @@
    +level index="100" name="Default" upper_threshhold="10" lower_threshhold="-10" allow_passthrough="0"
    +rule name="link" type="1" score="1" value="http://" flags="3" enabled="0"
    +rule name="PM" type="2" score="1" value="." flags="2" enabled="1"
    +rule name="playing" type="2" score="1" value="[Pp]laying" flags="3" enabled="0"
    +rule name="enterleave" type="2" score="1" value="." flags="16" enabled="1"
    +rule name="invite" type="2" score="1" value="." flags="32" enabled="1"
    +rule name="profilebot" type="1" score="1" value="profile" flags="4" enabled="1"
    +level index="1" name="WL" upper_threshhold="10" lower_threshhold="-10" allow_passthrough="0"
    +level index="80" name="PM" upper_threshhold="10" lower_threshhold="-10" allow_passthrough="0"
    +rule name="enterleave" type="2" score="1" value="." flags="16" enabled="1"
    +level index="90" name="Link" upper_threshhold="10" lower_threshhold="-10" allow_passthrough="0"
    +rule name="PM" type="2" score="1" value="." flags="2" enabled="1"
    +rule name="enterleave" type="2" score="1" value="." flags="16" enabled="1"
    +rule name="playing" type="2" score="1" value="[Pp]laying" flags="3" enabled="0"
    +rule name="invite" type="2" score="1" value="." flags="32" enabled="1"
    +level index="1001" name="BL" upper_threshhold="10" lower_threshhold="-10" allow_passthrough="0"
    +rule name="everything" type="2" score="1" value="." flags="55" enabled="1"
    +level index="999" name="ZincBL" upper_threshhold="9" lower_threshhold="-9" allow_passthrough="0"
    +rule name="Everything" type="2" score="1" value="." flags="3" enabled="1"
    +level index="2" name="ZincWL" upper_threshhold="9" lower_threshhold="-9" allow_passthrough="0"
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,45 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_H
    +#define IGNORANCE_H
    +
    +#ifdef HAVE_CONFIG_H
    +# include "../pp_config.h"
    +#endif
    +
    +#include "../common/pp_internal.h"
    +
    +#include "ignorance_level.h"
    +
    +#define IGNORANCE_APPLY_CHAT 1
    +#define IGNORANCE_APPLY_IM 2
    +#define IGNORANCE_APPLY_USER 4
    +#define IGNORANCE_APPLY_HOST 8
    +#define IGNORANCE_APPLY_ENTERLEAVE 16
    +#define IGNORANCE_APPLY_INVITE 32
    +
    +ignorance_level* ignorance_get_level_name(const GString *levelname);
    +
    +gboolean ignorance_add_level(ignorance_level *level);
    +
    +gboolean ignorance_remove_level(const GString *levelname);
    +
    +gint ignorance_get_new_level_index();
    +
    +gboolean save_conf();
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_denizen.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,62 @@
    +/*
    + * 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 "ignorance_denizen.h"
    +#include "ignorance_internal.h"
    +
    +ignorance_denizen* ignorance_denizen_new(const gchar *newname) {
    + ignorance_denizen *id=(ignorance_denizen*)g_malloc(sizeof(ignorance_denizen));
    +
    + id->name=g_strdup(newname);
    + id->last_message=g_strdup("");
    + id->repeats=0;
    +
    + return id;
    +}
    +
    +void ignorance_denizen_free(ignorance_denizen *id) {
    + g_free(id->name);
    + g_free(id->last_message);
    + g_free(id);
    +}
    +
    +gchar *ignorance_denizen_get_name(ignorance_denizen *id) {
    + return id->name;
    +}
    +
    +gchar *ignorance_denizen_get_last_message(ignorance_denizen *id) {
    + return id->last_message;
    +}
    +
    +gint ignorance_denizen_get_repeats(ignorance_denizen *id) {
    + return id->repeats;
    +}
    +
    +gint ignorance_denizen_set_message(ignorance_denizen *id, const gchar *message) {
    + if(!strcasecmp(id->last_message,message)){
    + purple_debug_info("ignorance","Got repeat %d for message %s\n",
    + id->repeats+1,message);
    + id->repeats++;
    + } else {
    + purple_debug_info("ignorance","New message %s replacing old message %s",
    + message,id->last_message);
    + g_free(id->last_message);
    + id->last_message=g_strdup(message);
    + id->repeats=0;
    + }
    +
    + return id->repeats;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_denizen.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,42 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_DENIZEN_H
    +#define IGNORANCE_DENIZEN_H
    +
    +#include <string.h>
    +
    +#include "ignorance.h"
    +
    +typedef struct ignorance_denizen{
    + gchar *name;
    + gchar *last_message;
    + gint repeats;
    +} ignorance_denizen;
    +
    +ignorance_denizen* ignorance_denizen_new(const gchar *newname);
    +
    +void ignorance_denizen_free(ignorance_denizen *denizen);
    +
    +gchar* ignorance_denizen_get_name(ignorance_denizen *id);
    +
    +gchar* ignorance_denizen_get_last_message(ignorance_denizen *id);
    +
    +gint ignorance_denizen_get_repeats(ignorance_denizen *id);
    +
    +gint ignorance_denizen_set_message(ignorance_denizen *id, const gchar *message);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_internal.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,72 @@
    +/*
    + * Guifications - The end all, be all, toaster popup plugin
    + * Copyright (C) 2003-2005 Gary Kramlich
    + *
    + * 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.
    + */
    +#ifndef IG_INTERNAL_H
    +#define IG_INTERNAL_H
    +
    +#include <glib.h>
    +
    +#define IGNORANCE_PLUGIN_ID "gtk-bleeter-ignorance"
    +#define SHORTDESC "Ignorance filter"
    +
    +#ifndef IGNORANCE_CONFDIR
    +# define IGNORANCE_CONFDIR purple_user_dir()
    +#endif
    +
    +#define EXEC_TIMEOUT 10
    +
    +#define GREATER(x,y) ((x)?((x)>(y)):(y))
    +
    +#if ((PURPLE_MAJOR_VERSION) < 2)
    +#define PURPLE_CONV_TYPE_CHAT PURPLE_CONV_CHAT
    +#define PURPLE_CONV_TYPE_IM PURPLE_CONV_IM
    +#endif
    +
    +#if GLIB_CHECK_VERSION(2,6,0)
    +# include <glib/gstdio.h>
    +#endif
    +
    +#ifdef _WIN32
    +# include <win32dep.h>
    +#endif
    +
    +#if !GLIB_CHECK_VERSION(2,6,0)
    +# define g_freopen freopen
    +# define g_fopen fopen
    +# define g_rmdir rmdir
    +# define g_remove remove
    +# define g_unlink unlink
    +# define g_lstat lstat
    +# define g_stat stat
    +# define g_mkdir mkdir
    +# define g_rename rename
    +# define g_open open
    +#endif
    +
    +#ifdef HAVE_ENDIAN_H
    +# include <endian.h>
    +#endif
    +
    +#define MSG_LEN 2048
    +/* The above should normally be the same as BUF_LEN,
    + * but just so we're explicitly asking for the max message
    + * length. */
    +#define BUF_LEN MSG_LEN
    +#define BUF_LONG BUF_LEN * 2
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_level.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,328 @@
    +/*
    + * 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 "ignorance.h"
    +#include "ignorance_denizen.h"
    +#include "ignorance_internal.h"
    +#include "ignorance_level.h"
    +#include "ignorance_violation.h"
    +#include "regex.h"
    +
    +#include <string.h>
    +#include <strings.h>
    +
    +void ignorance_level_write_hashitem(gpointer key, gpointer value,
    + gpointer user_data);
    +void ignorance_hash_free(gpointer key, gpointer value,
    + gpointer user_data);
    +void ignorance_level_regex_hashitem(gpointer key, gpointer value,
    + gpointer user_data);
    +
    +gboolean assign_level_token(ignorance_level *lvl,const gchar *tokentxt);
    +gint g_string_compare(gconstpointer a, gconstpointer z);
    +
    +ignorance_level* ignorance_level_new() {
    + ignorance_level *il=(ignorance_level*)g_malloc(sizeof(ignorance_level));
    + il->name=g_string_new("Default");
    + il->denizens_hash=g_hash_table_new(g_str_hash,g_str_equal);
    + il->rules=g_ptr_array_new();
    +
    + return il;
    +}
    +
    +void ignorance_level_free(ignorance_level *il) {
    + g_string_free(il->name,TRUE);
    + g_hash_table_foreach(il->denizens_hash,ignorance_hash_free,NULL);
    + g_hash_table_destroy(il->denizens_hash);
    + g_ptr_array_foreach(il->rules,ignorance_rule_free_g,NULL);
    +}
    +
    +void ignorance_level_free_g(gpointer il,gpointer user_data) {
    + ignorance_level_free((ignorance_level*)il);
    +}
    +
    +gboolean ignorance_level_add_rule(ignorance_level *level,ignorance_rule *rule) {
    + g_ptr_array_add(level->rules,(gpointer)rule);
    +
    + return TRUE;
    +}
    +
    +ignorance_rule* ignorance_level_get_rule(ignorance_level *level,
    + const GString *rulename) {
    +
    + int i=0;
    + ignorance_rule *rule=NULL;
    +
    + for(i=0;i<level->rules->len;++i){
    + rule=g_ptr_array_index(level->rules,i);
    + if(g_string_equal(rulename,rule->name))
    + return g_ptr_array_index(level->rules,i);
    + }
    +
    + return NULL;
    +}
    +
    +gboolean ignorance_level_remove_rule(ignorance_level *level,
    + const GString *rulename) {
    + return g_ptr_array_remove_fast(level->rules,
    + ignorance_level_get_rule(level,rulename));
    +}
    +
    +gboolean ignorance_level_add_denizen(ignorance_level *level,
    + const GString *username) {
    +
    + if(!g_hash_table_lookup(level->denizens_hash,username->str)){
    + ignorance_denizen *id=ignorance_denizen_new(username->str);
    + g_hash_table_insert(level->denizens_hash,
    + ignorance_denizen_get_name(id),id);
    + }
    +
    + return TRUE;
    +}
    +
    +#ifdef HAVE_REGEX_H
    +gboolean ignorance_level_has_denizen_regex(ignorance_level *level,
    + const gchar *regex, GList **denizens) {
    +
    + regex_t reg;
    + gpointer udata[2];
    +
    + udata[0]=denizens;
    +
    + if(regcomp(&reg,regex,REG_EXTENDED | REG_NOSUB)) {
    + purple_debug_error("ignorance", "Error parsing regex %s\n",
    + regex);
    + regfree(&reg);
    + return FALSE;
    + }
    +
    + udata[1]=&reg;
    +
    + g_hash_table_foreach(level->denizens_hash,ignorance_level_regex_hashitem,
    + (gpointer)udata);
    +
    + regfree(&reg);
    +
    + return (denizens!=NULL);
    +}
    +#endif
    +
    +
    +gboolean ignorance_level_has_denizen(ignorance_level *level,
    + const GString *username) {
    + gboolean rv=FALSE;
    +
    + rv = (NULL != g_hash_table_lookup(level->denizens_hash,username->str));
    +
    + return rv;
    +}
    +
    +gint g_string_compare(gconstpointer a, gconstpointer z) {
    + const GString *gsa=(const GString*)a, *gsz=(const GString*)z;
    +
    + return g_ascii_strcasecmp(gsa->str, gsz->str);
    +}
    +
    +
    +gboolean ignorance_level_remove_denizen(ignorance_level *level,
    + const GString *username) {
    +
    + gpointer kptr=NULL, vptr=NULL;
    + gboolean rv=FALSE;
    +
    + rv=g_hash_table_lookup_extended(level->denizens_hash, username->str, &kptr,
    + &vptr);
    +
    + purple_debug_info("ignorance","Remove: found id %x\n",vptr);
    + if(rv){
    + purple_debug_info("ignorance","Removing from hash\n");
    + g_hash_table_remove(level->denizens_hash,username->str);
    +
    + purple_debug_info("ignorance","Freeing denizen\n");
    + ignorance_denizen_free((ignorance_denizen*)vptr);
    + purple_debug_info("ignorance","Done freeing denizen\n");
    + }
    +
    + return rv;
    +}
    +
    +gint ignorance_level_rulecheck(ignorance_level *level,
    + const GString *username, const GString *text,
    + gint flags, GList **violations) {
    + int i=0, totalscore=0, curscore;
    + ignorance_rule *cur;
    + ignorance_denizen *id;
    +
    + purple_debug_info("ignorance","Preparing to lookup %s\n", username->str);
    + id = g_hash_table_lookup(level->denizens_hash, username->str);
    + purple_debug_info("ignorance","Got denizen %x\n",id);
    + if(id){
    + purple_debug_info("ignorance","Making sure text isn't name\n");
    + if(strcasecmp(ignorance_denizen_get_name(id),text->str)){
    + purple_debug_info("ignorance","Setting new message to %s\n", text->str);
    + ignorance_denizen_set_message(id, text->str);
    + }
    + }
    +
    + for(i=0;i<level->rules->len;++i) {
    + cur=(ignorance_rule*)g_ptr_array_index(level->rules,i);
    + if(cur->flags & IGNORANCE_APPLY_USER) {
    + curscore=ignorance_rule_rulecheck(cur,username,flags);
    + totalscore|=curscore;
    + if(curscore){
    + if(curscore & IGNORANCE_FLAG_MESSAGE)
    + (*violations)=g_list_prepend(*violations,
    + ignorance_violation_newp(IGNORANCE_FLAG_MESSAGE,
    + cur->message));
    + if(curscore & IGNORANCE_FLAG_SOUND)
    + (*violations)=g_list_prepend(*violations,
    + ignorance_violation_newp(IGNORANCE_FLAG_SOUND,
    + cur->sound));
    + if(curscore & IGNORANCE_FLAG_EXECUTE)
    + (*violations)=g_list_prepend(*violations,
    + ignorance_violation_newp(IGNORANCE_FLAG_EXECUTE,
    + cur->command));
    + }
    + }
    +
    + curscore=ignorance_rule_rulecheck(cur, text,
    + flags & (~IGNORANCE_APPLY_USER));
    +
    + totalscore|=curscore;
    + if(curscore){
    + if(curscore & IGNORANCE_FLAG_MESSAGE)
    + (*violations)=g_list_prepend(*violations,
    + ignorance_violation_newp(IGNORANCE_FLAG_MESSAGE, cur->message));
    + if(curscore & IGNORANCE_FLAG_SOUND)
    + (*violations)=g_list_prepend(*violations,
    + ignorance_violation_newp(IGNORANCE_FLAG_SOUND, cur->sound));
    + if(curscore & IGNORANCE_FLAG_EXECUTE)
    + (*violations)=g_list_prepend(*violations,
    + ignorance_violation_newp(IGNORANCE_FLAG_EXECUTE, cur->command));
    + }
    + }
    +
    + return totalscore;
    +}
    +
    +
    +ignorance_level* ignorance_level_read_old(const gchar *lvltext) {
    + gchar *tokptr=strchr((gchar*)lvltext,' '), **tokens=NULL;
    + ignorance_level *lvl=ignorance_level_new();
    + int i=0;
    +
    + if(!tokptr){
    + ignorance_level_free(lvl);
    + return NULL;
    + }
    +
    + tokens=g_strsplit(lvltext," ",INT_MAX);
    +
    + for(i=0;tokens[i];++i)
    + assign_level_token(lvl,tokens[i]);
    +
    + g_strfreev(tokens);
    +
    + return lvl;
    +}
    +
    +ignorance_level* ignorance_level_read(const gchar *lvltext) {
    + gchar *tokptr=strchr((gchar*)lvltext,'\n'), **tokens=NULL;
    + ignorance_level *lvl=ignorance_level_new();
    + int i=0;
    +
    + if(!tokptr){
    + ignorance_level_free(lvl);
    + return ignorance_level_read_old(lvltext);
    + }
    + tokens=g_strsplit(lvltext,"\n",INT_MAX);
    + for(i=0;tokens[i];++i)
    + assign_level_token(lvl,tokens[i]);
    +
    + g_strfreev(tokens);
    +
    + return lvl;
    +}
    +
    +
    +/* Parses out a token of the form tokenname="value" and assigns it to a rulename
    + *
    + * rule is the rule to be updated
    + * tokentxt is the token string
    + * true returned if token is valid and successfully added to the rule
    + *
    + * level name1="value1" name2="value2" ...
    + */
    +gboolean assign_level_token(ignorance_level *lvl,const gchar *tokentxt) {
    + gchar *name=NULL, *value=NULL;
    + gboolean rv=TRUE;
    + gint cursor=0;
    +
    + value=strchr(tokentxt,'=');
    + if(value) {
    + (*value)='\0';
    + ++value;
    +
    + if('"'==(*value)) {
    + ++value;
    + cursor=strlen(value)-1;
    + if('"'==value[cursor])
    + value[cursor]='\0';
    + }
    + name=(gchar*)tokentxt;
    +
    + if(!strncasecmp(name,"name",BUFSIZ))
    + g_string_assign(lvl->name,value);
    + else
    + rv=FALSE;
    + } else
    + rv=FALSE;
    +
    + return rv;
    +}
    +
    +gboolean ignorance_level_write(ignorance_level *level,FILE *f) {
    + gint i;
    +
    + fprintf(f,"level\nname=\"%s\"\n/level\n", level->name->str);
    +
    + for(i=0;i<level->rules->len;++i)
    + ignorance_rule_write(g_ptr_array_index(level->rules,i),f);
    +
    + g_hash_table_foreach(level->denizens_hash,ignorance_level_write_hashitem,f);
    +
    + return TRUE;
    +}
    +
    +void ignorance_level_regex_hashitem(gpointer key, gpointer value,
    + gpointer user_data) {
    + gpointer *udata=(gpointer*)user_data;
    + GList **denizens=(GList**)udata[0];
    +
    + if(!(regexec((regex_t*)udata[1],(gchar*)key,1,NULL,0)))
    + (*denizens)=g_list_prepend(*denizens,g_string_new((gchar*)key));
    +}
    +
    +void ignorance_hash_free(gpointer key, gpointer value, gpointer user_data) {
    + ignorance_denizen_free((ignorance_denizen*)value);
    +}
    +
    +void ignorance_level_write_hashitem(gpointer key, gpointer value,
    + gpointer user_data){
    +
    + fprintf((FILE*)user_data,"%s\n",(gchar*)key);
    +
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_level.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,99 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_LEVEL_H
    +#define IGNORANCE_LEVEL_H
    +
    +#include "ignorance_rule.h"
    +
    +/*
    + * describes an ignorance level
    + *
    + * index is an index for keeping track
    + * of levels' relative positions
    + * the idea is for 0 to be the "default" level,
    + * >0 to be "better" levels, and <0 to be "worse"
    + *
    + * allow_passthrough flags whether a user
    + * is allowed to be "passed through" this level
    + * to the next consecutive level, as in:
    + * if someone on my friends list does something
    + * that flags 12 of my rules, do I send them
    + * straight to /dev/null, or just
    + * bump them down a level?
    + *
    + * name - user's name for the level
    + *
    + * denizens - list of users in this level
    + * I may get rid of this in favor of a more global
    + * userlist
    + *
    + * rules - list of rules for this level
    + */
    +typedef struct ignorance_level{
    + GString *name;
    + /*GList *denizens;*/
    + GHashTable *denizens_hash;
    + GPtrArray *rules;
    +} ignorance_level;
    +
    +ignorance_level* ignorance_level_new();
    +void ignorance_level_free(ignorance_level *il);
    +void ignorance_level_free_g(gpointer il,gpointer user_data);
    +
    +gboolean ignorance_level_add_rule(ignorance_level *level,ignorance_rule *rule);
    +
    +ignorance_rule* ignorance_level_get_rule(ignorance_level *level,
    + const GString *rulename);
    +
    +gboolean ignorance_level_remove_rule(ignorance_level *level,
    + const GString *rulename);
    +
    +gboolean ignorance_level_add_denizen(ignorance_level *level,
    + const GString *username);
    +
    +gboolean ignorance_level_add_denizen_fast(ignorance_level *level,
    + const GString *username);
    +
    +gboolean ignorance_level_has_denizen(ignorance_level *level,
    + const GString *username);
    +
    +#ifdef HAVE_REGEX_H
    +gboolean ignorance_level_has_denizen_regex(ignorance_level *level,
    + const gchar *regex, GList **denizens);
    +#endif
    +
    +gboolean ignorance_level_remove_denizen(ignorance_level *level,
    + const GString *username);
    +
    +/*
    + * Determines whether a string violates one of
    + * the rules defined in an ignorance level
    + *
    + * il is the ignorance level
    + * text is the possibly offending string
    + * flags are the rule flags to match
    + * returns score
    + */
    +gint ignorance_level_rulecheck(ignorance_level *il,const GString *username,
    + const GString *text, gint flags,
    + GList **violations);
    +
    +ignorance_level* ignorance_level_read(const gchar *lvltext);
    +
    +gboolean ignorance_level_write(ignorance_level *level,FILE *f);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_rule.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,243 @@
    +/*
    + * 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 "ignorance_rule.h"
    +#include <string.h>
    +#include <strings.h>
    +#include "ignorance_internal.h"
    +
    +
    +gboolean assign_rule_token(ignorance_rule *rule, const char *tokentxt);
    +
    +ignorance_rule* ignorance_rule_new() {
    + ignorance_rule *ir=(ignorance_rule*)g_malloc(sizeof(ignorance_rule));
    +
    + ir->name=g_string_new("");
    + ir->type=IGNORANCE_RULE_SIMPLETEXT;
    + ir->score=0;
    + ir->flags=0;
    + ir->enabled=TRUE;
    + ir->value=NULL;
    + ir->message=NULL;
    + ir->sound=NULL;
    + ir->command=NULL;
    +
    + return ir;
    +}
    +
    +ignorance_rule* ignorance_rule_newp(const GString *name, gint type,
    + const gchar *value, gint score, gint flags,
    + gboolean enabled, const gchar *message,
    + const gchar *sound, const gchar *command) {
    + ignorance_rule *ir=(ignorance_rule*)g_malloc(sizeof(ignorance_rule));
    +
    + ir->name=g_string_new(name->str);
    + if(ignorance_rule_has_type(type))
    + ir->type=type;
    + else
    + ir->type=IGNORANCE_RULE_INVALID;
    + ir->value=g_strdup(value);
    + ir->score=score;
    + ir->flags=flags;
    + ir->enabled=enabled;
    + ir->message=g_strdup(message);
    + ir->sound=g_strdup(sound);
    + ir->command=g_strdup(command);
    +
    + return ir;
    +}
    +
    +void ignorance_rule_free(ignorance_rule *ir) {
    + g_string_free(ir->name,TRUE);
    + g_free(ir->value);
    + g_free(ir->message);
    + g_free(ir->sound);
    + g_free(ir->command);
    + g_free(ir);
    +}
    +
    +void ignorance_rule_free_g(gpointer ir,gpointer user_data) {
    + ignorance_rule_free((ignorance_rule*)ir);
    +}
    +
    +gboolean ignorance_rule_has_type(gint type) {
    + if((type>=IGNORANCE_RULE_MINVALID) || (type<=IGNORANCE_RULE_INVALID))
    + return FALSE;
    + return TRUE;
    +}
    +
    +
    +gint ignorance_rule_rulecheck(ignorance_rule *rule, const GString *text,
    + gint flags) {
    + if((flags & rule->flags) && rule->enabled){
    + switch(rule->type){
    + case IGNORANCE_RULE_SIMPLETEXT:
    + return simple_text_rulecheck(rule,text);
    +#ifdef HAVE_REGEX_H
    + case IGNORANCE_RULE_REGEX:
    + return regex_rulecheck(rule,text);
    +#endif
    + default:
    + return 0;
    + }
    + }
    +
    + return 0;
    +}
    +
    +gint simple_text_rulecheck(ignorance_rule *rule,const GString *text) {
    + const gchar *rulevalue=(const gchar*)(rule->value);
    +
    + if(NULL!=g_strstr_len(text->str,text->len,rulevalue))
    + return rule->score;
    +
    + return 0;
    +}
    +
    +#ifdef HAVE_REGEX_H
    +gint regex_rulecheck(ignorance_rule *rule, const GString *text) {
    + regex_t reg;
    + gint rv=0;
    +
    + if(regcomp(&reg,(const gchar*)rule->value,REG_EXTENDED | REG_NOSUB))
    + purple_debug_error("ignorance", "Error parsing regex %s\n",
    + (const gchar*)(rule->value));
    + else if(!regexec(&reg,text->str,1,NULL,0))
    + rv=rule->score;
    +
    + regfree(&reg);
    + return rv;
    +}
    +#endif
    +
    +gint repeat_rulecheck(ignorance_rule *rule, gint repeats) {
    + gint allowed_repeats = atoi((gchar*)(rule->value));
    + gint score=0;
    +
    + if(repeats >= allowed_repeats)
    + score=rule->score;
    +
    + return score;
    +}
    +
    +ignorance_rule* ignorance_rule_read_old(const gchar *ruletext) {
    + gchar *tokptr=strchr((gchar*)ruletext,' '), **tokens=NULL;
    + ignorance_rule *rule=ignorance_rule_new();
    + int i=0;
    +
    + if(!tokptr){
    + ignorance_rule_free(rule);
    + return NULL;
    + }
    +
    + tokens=g_strsplit(ruletext," ",INT_MAX);
    +
    + for(i=0;tokens[i];++i)
    + assign_rule_token(rule,tokens[i]);
    +
    + if(rule->score > 9 || rule->score < -9)
    + rule->score=IGNORANCE_FLAG_IGNORE;
    + else
    + rule->score=IGNORANCE_FLAG_FILTER;
    +
    + g_strfreev(tokens);
    +
    + return rule;
    +}
    +
    +ignorance_rule* ignorance_rule_read(const gchar *ruletext) {
    + gchar*tokptr=strchr((gchar*)ruletext,'\n'), **tokens;
    + ignorance_rule *rule=ignorance_rule_new();
    + int i=0;
    +
    + if(!tokptr){
    + ignorance_rule_free(rule);
    + return ignorance_rule_read_old(ruletext);
    + }
    +
    + tokens=g_strsplit(ruletext,"\n",INT_MAX);
    +
    + for(i=0;tokens[i];++i)
    + assign_rule_token(rule,tokens[i]);
    +
    + g_strfreev(tokens);
    +
    + return rule;
    +}
    +
    +/*
    + * Parses out a token of the form tokenname="value" and assigns it to a rulename
    + *
    + * rule is the rule to be updated
    + * tokentxt is the token string
    + * true returned if token is valid and successfully added to the rule
    + *
    + * level name1="value1" name2="value2" ...
    + */
    +gboolean assign_rule_token(ignorance_rule *rule,const gchar *tokentxt) {
    + gchar *name=(gchar*)tokentxt, *value=NULL;
    + gboolean rv=TRUE;
    + gint cursor=0;
    +
    + value=strchr(tokentxt,'=');
    + if(value) {
    + (*value)='\0';
    + ++value;
    +
    + if('"'==(*value)){
    + ++value;
    + cursor=strlen(value)-1;
    + if('"'==value[cursor])
    + value[cursor]='\0';
    + }
    +
    + if(!strncasecmp(name,"name",BUFSIZ))
    + g_string_assign(rule->name,value);
    + else if(!strncasecmp(name,"type",BUFSIZ))
    + rule->type=atoi(value);
    + else if(!strncasecmp(name,"value",BUFSIZ)) {
    + rule->value=(gchar*)g_malloc((strlen(value)+1)*sizeof(gchar));
    + strncpy(rule->value,value,strlen(value)+1);
    + }else if(!strncasecmp(name,"score",BUFSIZ))
    + rule->score=atoi(value);
    + else if(!strncasecmp(name,"flags",BUFSIZ))
    + rule->flags=atoi(value);
    + else if(!strncasecmp(name,"enabled",BUFSIZ))
    + rule->enabled=(gboolean)atoi(value);
    + else if(!strncasecmp(name,"message",BUFSIZ)) {
    + rule->message=(gchar*)g_malloc((strlen(value)+1)*sizeof(gchar));
    + strncpy(rule->message,value,strlen(value)+1);
    + } else if(!strncasecmp(name,"command",BUFSIZ)) {
    + rule->command=(gchar*)g_malloc((strlen(value)+1)*sizeof(gchar));
    + strncpy(rule->command,value,strlen(value)+1);
    + } else if(!strncasecmp(name,"sound",BUFSIZ)) {
    + rule->sound=(gchar*)g_malloc((strlen(value)+1)*sizeof(gchar));
    + strncpy(rule->sound,value,strlen(value)+1);
    + } else
    + rv=FALSE;
    + } else
    + rv=FALSE;
    +
    + return rv;
    +}
    +
    +gboolean ignorance_rule_write(ignorance_rule *rule,FILE *f){
    + fprintf(f,"rule\nname=\"%s\"\ntype=\"%d\"\nscore=\"%d\"\nvalue=\"%s\"\nflags=\"%d\"\nenabled=\"%d\"\nmessage=\"%s\"\ncommand=\"%s\"\nsound=\"%s\"\n/rule\n",
    + rule->name->str, rule->type, rule->score, (gchar*)(rule->value),
    + rule->flags, rule->enabled, rule->message, rule->command, rule->sound);
    +
    + return TRUE;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_rule.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,117 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_RULE_H
    +#define IGNORANCE_RULE_H
    +
    +#ifdef HAVE_CONFIG_H
    +# include "../pp_config.h"
    +#endif
    +
    +#include <stdlib.h>
    +#include <sys/types.h>
    +#include <glib.h>
    +#include <glib/gprintf.h>
    +
    +#include "debug.h"
    +
    +#define IGNORANCE_RULE_INVALID 0
    +#define IGNORANCE_RULE_MINVALID INT_MAX
    +
    +#define IGNORANCE_RULE_SIMPLETEXT 1
    +#define IGNORANCE_RULE_SIMPLETEXT_NUMTOKENS 6
    +
    +#ifdef HAVE_REGEX_H
    +#include <regex.h>
    +#define IGNORANCE_RULE_REGEX 2
    +#define IGNORANCE_RULE_REGEX_NUMTOKENS 6
    +#endif
    +
    +#define IGNORANCE_RULE_REPEAT 4
    +#define IGNORANCE_RULE_REPEAT_NUMTOKENS 6
    +
    +#define IGNORANCE_FLAG_FILTER 1
    +#define IGNORANCE_FLAG_IGNORE 2
    +#define IGNORANCE_FLAG_MESSAGE 4
    +#define IGNORANCE_FLAG_EXECUTE 8
    +#define IGNORANCE_FLAG_SOUND 16
    +
    +/*
    + * describes an ignorance rule
    + *
    + * name - user's name for the rule
    + * type - one of the ruletypes defined within this struct
    + *
    + * value - the actual value of the rule
    + * could be a string/regex, integer, other
    + * depending on the rule type
    + *
    + * score - an arbitrary (user-assigned) number
    + * that determines the severity of the rule
    + */
    +typedef struct ignorance_rule {
    + GString *name;
    + gint type;
    + gchar *value;
    + gint score;
    + gint flags;
    + gboolean enabled;
    + gchar *message,
    + *command,
    + *sound;
    +} ignorance_rule;
    +
    +ignorance_rule* ignorance_rule_new();
    +
    +ignorance_rule* ignorance_rule_newp(const GString *name, gint type,
    + const gchar *value, gint score, gint flags,
    + gboolean enabled, const gchar *message,
    + const gchar *sound, const gchar *command);
    +void ignorance_rule_free(ignorance_rule *ir);
    +void ignorance_rule_free_g(gpointer ir,gpointer user_data);
    +
    +gboolean ignorance_rule_has_type(gint type);
    +
    +/*
    + * Determines whether a string violates a rule
    + *
    + * rule is the rule
    + * text is the possibly offending string
    + * returns 0/1 boolean
    + */
    +gint ignorance_rule_rulecheck(ignorance_rule *rule, const GString *text,
    + gint flags);
    +
    +gint simple_text_rulecheck(ignorance_rule *rule, const GString *text);
    +
    +#ifdef HAVE_REGEX_H
    +gint regex_rulecheck(ignorance_rule *rule, const GString *text);
    +#endif
    +
    +gint repeat_rulecheck(ignorance_rule *rule, gint repeats);
    +
    +ignorance_rule* ignorance_rule_read(const gchar *ruletext);
    +
    +/*
    + * Writes out an ignorance rule to a file
    + *
    + * rule is the rule to write
    + * f is the file which will be written
    + * success/failure returned
    + */
    +gboolean ignorance_rule_write(ignorance_rule *rule, FILE *f);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_violation.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,45 @@
    +/*
    + * 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 "ignorance_violation.h"
    +#include "ignorance_internal.h"
    +
    +ignorance_violation* ignorance_violation_new() {
    + return ignorance_violation_newp(IGNORANCE_FLAG_MESSAGE,"");
    +}
    +
    +ignorance_violation* ignorance_violation_newp(gint newtype,
    + const gchar *newvalue){
    + ignorance_violation *iv=(ignorance_violation*)g_malloc(sizeof(ignorance_violation));
    +
    + if(iv){
    + iv->type=newtype;
    + iv->value=g_strdup(newvalue);
    + }
    +
    + return iv;
    +}
    +
    +void ignorance_violation_free(ignorance_violation *iv) {
    + if(iv){
    + g_free(iv->value);
    + g_free(iv);
    + }
    +}
    +
    +void ignorance_violation_free_g(gpointer iv, gpointer user_data) {
    + ignorance_violation_free((ignorance_violation*)iv);
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/ignorance_violation.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,32 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_VIOLATION_H
    +#define IGNORANCE_VIOLATION_H
    +
    +#include "ignorance.h"
    +
    +typedef struct ignorance_violation {
    + gint type;
    + gchar *value;
    +} ignorance_violation;
    +
    +ignorance_violation* ignorance_violation_new();
    +ignorance_violation* ignorance_violation_newp(gint newtype, const gchar *newvalue);
    +void ignorance_violation_free(ignorance_violation *iv);
    +void ignorance_violation_free_g(gpointer iv, gpointer user_data);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/interface.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,284 @@
    +/*
    + * 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 <sys/types.h>
    +#include <sys/stat.h>
    +#include <unistd.h>
    +#include <string.h>
    +#include <stdio.h>
    +
    +#include <gdk/gdkkeysyms.h>
    +#include <gtk/gtk.h>
    +
    +#include <gtkutils.h>
    +
    +#include "callbacks.h"
    +#include "interface.h"
    +#include "support.h"
    +#include "ignorance.h"
    +#include "ignorance_internal.h"
    +
    +GtkWidget* create_uiinfo (GPtrArray *levels) {
    + GtkWidget *frame, *table, *hbox, *label;
    + GtkWidget *scrolledwindow, *levelView, *button;
    +
    + rule_selected=TRUE;
    + vbox1=gtk_vbox_new(FALSE, 0);
    + gtk_widget_show(vbox1);
    +
    + vbox2=gtk_vbox_new(FALSE, PURPLE_HIG_BOX_SPACE);
    + gtk_widget_show(vbox2);
    + gtk_box_pack_start(GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0);
    +
    + scrolledwindow=gtk_scrolled_window_new (NULL, NULL);
    + gtk_widget_show(scrolledwindow);
    + gtk_box_pack_start(GTK_BOX (vbox2), scrolledwindow, TRUE, TRUE, 0);
    + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolledwindow),
    + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    + gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW (scrolledwindow),
    + GTK_SHADOW_IN);
    +
    + store=gtk_tree_store_new(NUM_COLUMNS,G_TYPE_STRING,G_TYPE_STRING);
    +
    + levelView=gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
    +
    + renderer=gtk_cell_renderer_text_new ();
    + column=gtk_tree_view_column_new_with_attributes ("Levels", renderer,
    + "text", LEVEL_COLUMN, NULL);
    + gtk_tree_view_append_column(GTK_TREE_VIEW (levelView), column);
    + column=gtk_tree_view_column_new_with_attributes("Rules", renderer, "text",
    + RULE_COLUMN,NULL);
    + gtk_tree_view_append_column (GTK_TREE_VIEW (levelView), column);
    +
    + load_form_with_levels(GTK_TREE_VIEW(levelView), levels);
    +
    + gtk_widget_show(levelView);
    + gtk_container_add(GTK_CONTAINER(scrolledwindow), levelView);
    +
    + sel=gtk_tree_view_get_selection(GTK_TREE_VIEW(levelView));
    + gtk_tree_selection_set_mode(sel, GTK_SELECTION_SINGLE);
    +
    + hbox = gtk_hbox_new (FALSE, 0);
    + gtk_widget_show (hbox);
    + gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0);
    +
    + button=pidgin_pixbuf_button_from_stock(_("Create new rule"),
    + GTK_STOCK_ADD, PIDGIN_BUTTON_HORIZONTAL);
    + gtk_widget_show (button);
    + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
    + g_signal_connect((gpointer) button, "clicked",
    + G_CALLBACK (on_levelAdd_clicked), levelView);
    +
    + button=pidgin_pixbuf_button_from_stock(_("Create new group"),
    + GTK_STOCK_ADD, PIDGIN_BUTTON_HORIZONTAL);
    + gtk_widget_show (button);
    + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0);
    + g_signal_connect ((gpointer) button, "clicked",
    + G_CALLBACK (on_groupAdd_clicked), levelView);
    +
    + button=pidgin_pixbuf_button_from_stock(_("Save changes"), GTK_STOCK_YES,
    + PIDGIN_BUTTON_HORIZONTAL);
    + gtk_widget_show (button);
    + gtk_box_pack_start(GTK_BOX (hbox), button, TRUE, TRUE, 0);
    + g_signal_connect((gpointer) button, "clicked",
    + G_CALLBACK (on_levelEdit_clicked), levelView);
    +
    +/* XXX: The stock-icon for levelDel doesn't show, because the text is
    + * set from callback.c. Can we do with just `Remove' for the text
    + * and not updating as the selection in the tree changes?
    + */
    + levelDel=pidgin_pixbuf_button_from_stock(_("Remove rule"), GTK_STOCK_REMOVE,
    + PIDGIN_BUTTON_HORIZONTAL);
    + gtk_widget_show (levelDel);
    + gtk_box_pack_start (GTK_BOX (hbox), levelDel, TRUE, TRUE, 0);
    + g_signal_connect((gpointer) levelDel, "clicked",
    + G_CALLBACK (on_levelDel_clicked), levelView);
    +
    + table=gtk_table_new(3, 2, FALSE);
    + gtk_container_set_border_width(GTK_CONTAINER(table), PURPLE_HIG_BOX_SPACE);
    + gtk_table_set_col_spacings(GTK_TABLE(table), PURPLE_HIG_BOX_SPACE);
    + gtk_table_set_row_spacings(GTK_TABLE(table), PURPLE_HIG_BOX_SPACE);
    + gtk_widget_show (table);
    + gtk_box_pack_start (GTK_BOX (vbox2), table, FALSE, TRUE, 0);
    +
    + label=gtk_label_new_with_mnemonic(_("Name: "));
    + gtk_widget_show(label);
    + gtk_table_attach(GTK_TABLE (table), label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0);
    +
    + rulename=gtk_entry_new ();
    + gtk_widget_show(rulename);
    + gtk_table_attach_defaults(GTK_TABLE (table), rulename, 1, 2, 0, 1);
    +
    + label=gtk_label_new_with_mnemonic(_("Filter: "));
    + gtk_widget_show(label);
    + gtk_table_attach(GTK_TABLE (table), label, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
    +
    + filtervalue=gtk_entry_new ();
    + gtk_widget_show(filtervalue);
    + gtk_table_attach_defaults(GTK_TABLE (table), filtervalue, 1, 2, 1, 2);
    +
    + hbox=gtk_hbox_new (FALSE, 0);
    + gtk_widget_show(hbox);
    + gtk_table_attach_defaults(GTK_TABLE (table), hbox, 0, 2, 2, 3);
    +
    + enabled_cb=gtk_check_button_new_with_mnemonic(_("Enabled"));
    + gtk_widget_show(enabled_cb);
    + gtk_box_pack_start(GTK_BOX(hbox),enabled_cb,FALSE,FALSE,0);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(enabled_cb),TRUE);
    +
    +#ifdef HAVE_REGEX_H
    + regex_cb=gtk_check_button_new_with_mnemonic (_("Regular Expression"));
    + gtk_widget_show(regex_cb);
    + gtk_box_pack_start (GTK_BOX (hbox), regex_cb, FALSE, FALSE, 0);
    +#endif
    +
    + repeat_cb=gtk_check_button_new_with_mnemonic (_("Repeat"));
    + gtk_widget_set_sensitive(repeat_cb,FALSE);
    + gtk_widget_show(repeat_cb);
    + gtk_box_pack_start(GTK_BOX (hbox), repeat_cb, FALSE, FALSE, 0);
    + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (repeat_cb), FALSE);
    +
    + frame=gtk_frame_new (NULL);
    + gtk_widget_show(frame);
    + gtk_box_pack_start(GTK_BOX (vbox2), frame, FALSE, TRUE, 0);
    +
    + table=gtk_table_new(4, 2, FALSE);
    + gtk_container_set_border_width(GTK_CONTAINER(table), PURPLE_HIG_BOX_SPACE);
    + gtk_table_set_col_spacings(GTK_TABLE(table), PURPLE_HIG_BOX_SPACE);
    + gtk_widget_show(table);
    + gtk_container_add(GTK_CONTAINER(frame), table);
    +
    + hbox=gtk_hbox_new(FALSE, 0);
    + gtk_widget_show(hbox);
    + gtk_table_attach_defaults(GTK_TABLE(table), hbox, 0, 2, 0, 1);
    +
    + filter_cb=gtk_check_button_new_with_label(_("Filter"));
    + gtk_widget_show(filter_cb);
    + gtk_box_pack_start(GTK_BOX (hbox), filter_cb, FALSE, FALSE, 0);
    +
    + ignore_cb=gtk_check_button_new_with_label(_("Ignore"));
    + gtk_widget_show(ignore_cb);
    + gtk_box_pack_start(GTK_BOX (hbox), ignore_cb, FALSE, FALSE, 0);
    +
    + message_cb = gtk_check_button_new_with_label(_("Send Message"));
    + gtk_widget_show(message_cb);
    + gtk_table_attach(GTK_TABLE(table), message_cb, 0, 1, 1, 2, GTK_FILL, 0, 0, 0);
    +
    + message_entry=gtk_entry_new();
    + gtk_widget_set_sensitive(message_entry,FALSE);
    + gtk_widget_show(message_entry);
    + gtk_table_attach_defaults(GTK_TABLE(table), message_entry, 1, 2, 1, 2);
    +
    + sound_cb = gtk_check_button_new_with_label(_("Play sound"));
    + gtk_widget_show(sound_cb);
    + gtk_table_attach(GTK_TABLE(table), sound_cb, 0, 1, 2, 3, GTK_FILL, 0, 0, 0);
    +
    + hbox=gtk_hbox_new (FALSE, 0);
    + gtk_widget_show (hbox);
    + gtk_table_attach_defaults(GTK_TABLE(table), hbox, 1, 2, 2, 3);
    +
    + sound_entry=gtk_entry_new();
    + gtk_widget_set_sensitive(sound_entry,FALSE);
    + gtk_widget_show(sound_entry);
    + gtk_box_pack_start(GTK_BOX(hbox),sound_entry,FALSE,FALSE,0);
    +
    + sound_browse=gtk_button_new_with_label(_("Browse"));
    + gtk_widget_set_sensitive(sound_browse,FALSE);
    + gtk_widget_show(sound_browse);
    + gtk_box_pack_start(GTK_BOX(hbox),sound_browse,FALSE,FALSE,0);
    +
    + execute_cb=gtk_check_button_new_with_label(_("Execute command"));
    + gtk_widget_show(execute_cb);
    + gtk_table_attach(GTK_TABLE(table), execute_cb, 0, 1, 3, 4, GTK_FILL, 0, 0, 0);
    +
    + execute_entry=gtk_entry_new();
    + gtk_widget_set_sensitive(execute_entry,FALSE);
    + gtk_widget_show(execute_entry);
    + gtk_table_attach_defaults(GTK_TABLE(table), execute_entry, 1, 2, 3, 4);
    +
    + label=gtk_label_new(_("Take action"));
    + gtk_widget_show(label);
    + gtk_frame_set_label_widget(GTK_FRAME (frame), label);
    +
    + frame=gtk_frame_new (NULL);
    + gtk_widget_show(frame);
    + gtk_box_pack_start(GTK_BOX (vbox2), frame, FALSE, TRUE, 0);
    +
    + table=gtk_table_new (3, 2, FALSE);
    + gtk_widget_show(table);
    + gtk_container_set_border_width(GTK_CONTAINER(table), PURPLE_HIG_BOX_SPACE);
    + gtk_table_set_col_spacings(GTK_TABLE(table), PURPLE_HIG_BOX_SPACE);
    + gtk_container_add(GTK_CONTAINER (frame), table);
    +
    + im_type_cb=gtk_check_button_new_with_mnemonic(_("IM Text"));
    + gtk_widget_show(im_type_cb);
    + gtk_table_attach(GTK_TABLE (table), im_type_cb, 0, 1, 0, 1,
    + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
    + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
    +
    + chat_type_cb=gtk_check_button_new_with_mnemonic(_("Chat Text"));
    + gtk_widget_show(chat_type_cb);
    + gtk_table_attach(GTK_TABLE (table), chat_type_cb, 1, 2, 0, 1,
    + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
    + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
    + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON (chat_type_cb), TRUE);
    +
    + username_type_cb=gtk_check_button_new_with_mnemonic(_("User names"));
    + gtk_widget_show(username_type_cb);
    + gtk_table_attach(GTK_TABLE (table), username_type_cb, 0, 1, 1, 2,
    + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
    + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
    +
    + enterleave_type_cb=gtk_check_button_new_with_mnemonic (_("Enter/Leave"));
    + gtk_widget_show(enterleave_type_cb);
    + gtk_table_attach(GTK_TABLE (table), enterleave_type_cb, 1, 2, 1, 2,
    + (GtkAttachOptions) (GTK_FILL),
    + (GtkAttachOptions) (0), 0, 0);
    +
    + invite_type_cb=gtk_check_button_new_with_mnemonic (_("Invitations"));
    + gtk_widget_show(invite_type_cb);
    + gtk_table_attach(GTK_TABLE (table), invite_type_cb, 0, 1, 2, 3,
    + (GtkAttachOptions) (GTK_FILL),
    + (GtkAttachOptions) (0), 0, 0);
    +
    + label=gtk_label_new (_("Filter"));
    + gtk_widget_show(label);
    + gtk_frame_set_label_widget(GTK_FRAME (frame), label);
    +
    + g_signal_connect(GTK_WIDGET(vbox1), "destroy", G_CALLBACK(save_conf),NULL);
    + g_signal_connect((gpointer)sel, "changed",
    + G_CALLBACK (on_levelView_row_activated), NULL);
    +
    + g_signal_connect((gpointer) filter_cb, "toggled",
    + G_CALLBACK (on_filter_cb_toggled), NULL);
    +
    + g_signal_connect((gpointer) ignore_cb, "toggled",
    + G_CALLBACK (on_ignore_cb_toggled), NULL);
    +
    + g_signal_connect((gpointer) message_cb, "toggled",
    + G_CALLBACK (on_message_cb_toggled), NULL);
    +
    + g_signal_connect((gpointer) sound_cb, "toggled",
    + G_CALLBACK (on_sound_cb_toggled), NULL);
    +
    + g_signal_connect((gpointer) execute_cb, "toggled",
    + G_CALLBACK (on_execute_cb_toggled), NULL);
    +
    + g_signal_connect((gpointer) sound_browse, "clicked",
    + G_CALLBACK (on_sound_browse_clicked), NULL);
    +
    + return vbox1;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/interface.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,48 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_INTERFACE_H
    +#define IGNORANCE_INTERFACE_H
    +
    +GtkWidget *vbox1;
    +GtkWidget *vbox2;
    +GtkWidget *levelDel;
    +GtkWidget *rulename;
    +GtkWidget *filtervalue;
    +GtkWidget *repeat_cb;
    +GtkWidget *regex_cb;
    +GtkWidget *im_type_cb;
    +GtkWidget *chat_type_cb;
    +GtkWidget *username_type_cb;
    +GtkWidget *enterleave_type_cb;
    +GtkWidget *invite_type_cb;
    +GtkWidget *enabled_cb;
    +GtkWidget *filter_cb;
    +GtkWidget *ignore_cb;
    +GtkWidget *message_cb, *message_entry;
    +GtkWidget *sound_cb, *sound_entry, *sound_browse;
    +
    +GtkWidget *execute_cb, *execute_entry, *execute_browse;
    +
    +GtkCellRenderer *renderer;
    +GtkTreeViewColumn *column;
    +GtkTreeStore *store;
    +GtkTreeSelection *sel;
    +gboolean rule_selected;
    +
    +GtkWidget* create_uiinfo(GPtrArray *levels);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Ignorance]
    +type=incomplete
    +depends=pidgin
    +provides=ignorance
    +summary=Allows management of users with various levels of activity
    +description=%(summary)s
    +authors=Levi Bard
    +introduced=1.0beta7
    +notes=Needs some TLC. It builds and probably works, but is far from an acceptible state.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/regex.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,4952 @@
    +/* Extended regular expression matching and search library,
    + version 0.12.
    + (Implements POSIX draft P10003.2/D11.2, except for
    + internationalization features.)
    +
    + Copyright (C) 1993 Free Software Foundation, Inc.
    +
    + 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */
    +
    +/* AIX requires this to be the first thing in the file. */
    +#if defined (_AIX) && !defined (REGEX_MALLOC)
    + #pragma alloca
    +#endif
    +
    +#define _GNU_SOURCE
    +
    +/* We need this for `regex.h', and perhaps for the Emacs include files. */
    +#include <sys/types.h>
    +
    +#ifdef HAVE_CONFIG_H
    +#include "../ig_config.h"
    +#endif
    +
    +/* The `emacs' switch turns on certain matching commands
    + that make sense only in Emacs. */
    +#ifdef emacs
    +
    +#include "lisp.h"
    +#include "buffer.h"
    +#include "syntax.h"
    +
    +/* Emacs uses `NULL' as a predicate. */
    +#undef NULL
    +
    +#else /* not emacs */
    +
    +/* We used to test for `BSTRING' here, but only GCC and Emacs define
    + `BSTRING', as far as I know, and neither of them use this code. */
    +#if HAVE_STRING_H || STDC_HEADERS
    +#include <string.h>
    +#ifndef bcmp
    +#define bcmp(s1, s2, n) memcmp ((s1), (s2), (n))
    +#endif
    +#ifndef bcopy
    +#define bcopy(s, d, n) memcpy ((d), (s), (n))
    +#endif
    +#ifndef bzero
    +#define bzero(s, n) memset ((s), 0, (n))
    +#endif
    +#else
    +#include <strings.h>
    +#endif
    +
    +#ifdef STDC_HEADERS
    +#include <stdlib.h>
    +#else
    +char *malloc ();
    +char *realloc ();
    +#endif
    +
    +
    +/* Define the syntax stuff for \<, \>, etc. */
    +
    +/* This must be nonzero for the wordchar and notwordchar pattern
    + commands in re_match_2. */
    +#ifndef Sword
    +#define Sword 1
    +#endif
    +
    +#ifdef SYNTAX_TABLE
    +
    +extern char *re_syntax_table;
    +
    +#else /* not SYNTAX_TABLE */
    +
    +/* How many characters in the character set. */
    +#define CHAR_SET_SIZE 256
    +
    +static char re_syntax_table[CHAR_SET_SIZE];
    +
    +static void
    +init_syntax_once ()
    +{
    + register int c;
    + static int done = 0;
    +
    + if (done)
    + return;
    +
    + bzero (re_syntax_table, sizeof re_syntax_table);
    +
    + for (c = 'a'; c <= 'z'; c++)
    + re_syntax_table[c] = Sword;
    +
    + for (c = 'A'; c <= 'Z'; c++)
    + re_syntax_table[c] = Sword;
    +
    + for (c = '0'; c <= '9'; c++)
    + re_syntax_table[c] = Sword;
    +
    + re_syntax_table['_'] = Sword;
    +
    + done = 1;
    +}
    +
    +#endif /* not SYNTAX_TABLE */
    +
    +#define SYNTAX(c) re_syntax_table[c]
    +
    +#endif /* not emacs */
    +
    +/* Get the interface, including the syntax bits. */
    +#include "regex.h"
    +
    +/* isalpha etc. are used for the character classes. */
    +#include <ctype.h>
    +
    +#ifndef isascii
    +#define isascii(c) 1
    +#endif
    +
    +#ifdef isblank
    +#define ISBLANK(c) (isascii (c) && isblank (c))
    +#else
    +#define ISBLANK(c) ((c) == ' ' || (c) == '\t')
    +#endif
    +#ifdef isgraph
    +#define ISGRAPH(c) (isascii (c) && isgraph (c))
    +#else
    +#define ISGRAPH(c) (isascii (c) && isprint (c) && !isspace (c))
    +#endif
    +
    +#define ISPRINT(c) (isascii (c) && isprint (c))
    +#define ISDIGIT(c) (isascii (c) && isdigit (c))
    +#define ISALNUM(c) (isascii (c) && isalnum (c))
    +#define ISALPHA(c) (isascii (c) && isalpha (c))
    +#define ISCNTRL(c) (isascii (c) && iscntrl (c))
    +#define ISLOWER(c) (isascii (c) && islower (c))
    +#define ISPUNCT(c) (isascii (c) && ispunct (c))
    +#define ISSPACE(c) (isascii (c) && isspace (c))
    +#define ISUPPER(c) (isascii (c) && isupper (c))
    +#define ISXDIGIT(c) (isascii (c) && isxdigit (c))
    +
    +#ifndef NULL
    +#define NULL 0
    +#endif
    +
    +/* We remove any previous definition of `SIGN_EXTEND_CHAR',
    + since ours (we hope) works properly with all combinations of
    + machines, compilers, `char' and `unsigned char' argument types.
    + (Per Bothner suggested the basic approach.) */
    +#undef SIGN_EXTEND_CHAR
    +#if __STDC__
    +#define SIGN_EXTEND_CHAR(c) ((signed char) (c))
    +#else /* not __STDC__ */
    +/* As in Harbison and Steele. */
    +#define SIGN_EXTEND_CHAR(c) ((((unsigned char) (c)) ^ 128) - 128)
    +#endif
    +
    +/* Should we use malloc or alloca? If REGEX_MALLOC is not defined, we
    + use `alloca' instead of `malloc'. This is because using malloc in
    + re_search* or re_match* could cause memory leaks when C-g is used in
    + Emacs; also, malloc is slower and causes storage fragmentation. On
    + the other hand, malloc is more portable, and easier to debug.
    +
    + Because we sometimes use alloca, some routines have to be macros,
    + not functions -- `alloca'-allocated space disappears at the end of the
    + function it is called in. */
    +
    +#ifdef REGEX_MALLOC
    +
    +#define REGEX_ALLOCATE malloc
    +#define REGEX_REALLOCATE(source, osize, nsize) realloc (source, nsize)
    +
    +#else /* not REGEX_MALLOC */
    +
    +/* Emacs already defines alloca, sometimes. */
    +#ifndef alloca
    +
    +/* Make alloca work the best possible way. */
    +#ifdef __GNUC__
    +#define alloca __builtin_alloca
    +#else /* not __GNUC__ */
    +#if HAVE_ALLOCA_H
    +#include <alloca.h>
    +#else /* not __GNUC__ or HAVE_ALLOCA_H */
    +#ifndef _AIX /* Already did AIX, up at the top. */
    +char *alloca ();
    +#endif /* not _AIX */
    +#endif /* not HAVE_ALLOCA_H */
    +#endif /* not __GNUC__ */
    +
    +#endif /* not alloca */
    +
    +#define REGEX_ALLOCATE alloca
    +
    +/* Assumes a `char *destination' variable. */
    +#define REGEX_REALLOCATE(source, osize, nsize) \
    + (destination = (char *) alloca (nsize), \
    + bcopy (source, destination, osize), \
    + destination)
    +
    +#endif /* not REGEX_MALLOC */
    +
    +
    +/* True if `size1' is non-NULL and PTR is pointing anywhere inside
    + `string1' or just past its end. This works if PTR is NULL, which is
    + a good thing. */
    +#define FIRST_STRING_P(ptr) \
    + (size1 && string1 <= (ptr) && (ptr) <= string1 + size1)
    +
    +/* (Re)Allocate N items of type T using malloc, or fail. */
    +#define TALLOC(n, t) ((t *) malloc ((n) * sizeof (t)))
    +#define RETALLOC(addr, n, t) ((addr) = (t *) realloc (addr, (n) * sizeof (t)))
    +#define REGEX_TALLOC(n, t) ((t *) REGEX_ALLOCATE ((n) * sizeof (t)))
    +
    +#define BYTEWIDTH 8 /* In bits. */
    +
    +#define STREQ(s1, s2) ((strcmp (s1, s2) == 0))
    +
    +#define MAX(a, b) ((a) > (b) ? (a) : (b))
    +#define MIN(a, b) ((a) < (b) ? (a) : (b))
    +
    +typedef char boolean;
    +#define false 0
    +#define true 1
    +
    +/* These are the command codes that appear in compiled regular
    + expressions. Some opcodes are followed by argument bytes. A
    + command code can specify any interpretation whatsoever for its
    + arguments. Zero bytes may appear in the compiled regular expression.
    +
    + The value of `exactn' is needed in search.c (search_buffer) in Emacs.
    + So regex.h defines a symbol `RE_EXACTN_VALUE' to be 1; the value of
    + `exactn' we use here must also be 1. */
    +
    +typedef enum
    +{
    + no_op = 0,
    +
    + /* Followed by one byte giving n, then by n literal bytes. */
    + exactn = 1,
    +
    + /* Matches any (more or less) character. */
    + anychar,
    +
    + /* Matches any one char belonging to specified set. First
    + following byte is number of bitmap bytes. Then come bytes
    + for a bitmap saying which chars are in. Bits in each byte
    + are ordered low-bit-first. A character is in the set if its
    + bit is 1. A character too large to have a bit in the map is
    + automatically not in the set. */
    + charset,
    +
    + /* Same parameters as charset, but match any character that is
    + not one of those specified. */
    + charset_not,
    +
    + /* Start remembering the text that is matched, for storing in a
    + register. Followed by one byte with the register number, in
    + the range 0 to one less than the pattern buffer's re_nsub
    + field. Then followed by one byte with the number of groups
    + inner to this one. (This last has to be part of the
    + start_memory only because we need it in the on_failure_jump
    + of re_match_2.) */
    + start_memory,
    +
    + /* Stop remembering the text that is matched and store it in a
    + memory register. Followed by one byte with the register
    + number, in the range 0 to one less than `re_nsub' in the
    + pattern buffer, and one byte with the number of inner groups,
    + just like `start_memory'. (We need the number of inner
    + groups here because we don't have any easy way of finding the
    + corresponding start_memory when we're at a stop_memory.) */
    + stop_memory,
    +
    + /* Match a duplicate of something remembered. Followed by one
    + byte containing the register number. */
    + duplicate,
    +
    + /* Fail unless at beginning of line. */
    + begline,
    +
    + /* Fail unless at end of line. */
    + endline,
    +
    + /* Succeeds if at beginning of buffer (if emacs) or at beginning
    + of string to be matched (if not). */
    + begbuf,
    +
    + /* Analogously, for end of buffer/string. */
    + endbuf,
    +
    + /* Followed by two byte relative address to which to jump. */
    + jump,
    +
    + /* Same as jump, but marks the end of an alternative. */
    + jump_past_alt,
    +
    + /* Followed by two-byte relative address of place to resume at
    + in case of failure. */
    + on_failure_jump,
    +
    + /* Like on_failure_jump, but pushes a placeholder instead of the
    + current string position when executed. */
    + on_failure_keep_string_jump,
    +
    + /* Throw away latest failure point and then jump to following
    + two-byte relative address. */
    + pop_failure_jump,
    +
    + /* Change to pop_failure_jump if know won't have to backtrack to
    + match; otherwise change to jump. This is used to jump
    + back to the beginning of a repeat. If what follows this jump
    + clearly won't match what the repeat does, such that we can be
    + sure that there is no use backtracking out of repetitions
    + already matched, then we change it to a pop_failure_jump.
    + Followed by two-byte address. */
    + maybe_pop_jump,
    +
    + /* Jump to following two-byte address, and push a dummy failure
    + point. This failure point will be thrown away if an attempt
    + is made to use it for a failure. A `+' construct makes this
    + before the first repeat. Also used as an intermediary kind
    + of jump when compiling an alternative. */
    + dummy_failure_jump,
    +
    + /* Push a dummy failure point and continue. Used at the end of
    + alternatives. */
    + push_dummy_failure,
    +
    + /* Followed by two-byte relative address and two-byte number n.
    + After matching N times, jump to the address upon failure. */
    + succeed_n,
    +
    + /* Followed by two-byte relative address, and two-byte number n.
    + Jump to the address N times, then fail. */
    + jump_n,
    +
    + /* Set the following two-byte relative address to the
    + subsequent two-byte number. The address *includes* the two
    + bytes of number. */
    + set_number_at,
    +
    + wordchar, /* Matches any word-constituent character. */
    + notwordchar, /* Matches any char that is not a word-constituent. */
    +
    + wordbeg, /* Succeeds if at word beginning. */
    + wordend, /* Succeeds if at word end. */
    +
    + wordbound, /* Succeeds if at a word boundary. */
    + notwordbound /* Succeeds if not at a word boundary. */
    +
    +#ifdef emacs
    + ,before_dot, /* Succeeds if before point. */
    + at_dot, /* Succeeds if at point. */
    + after_dot, /* Succeeds if after point. */
    +
    + /* Matches any character whose syntax is specified. Followed by
    + a byte which contains a syntax code, e.g., Sword. */
    + syntaxspec,
    +
    + /* Matches any character whose syntax is not that specified. */
    + notsyntaxspec
    +#endif /* emacs */
    +} re_opcode_t;
    +
    +/* Common operations on the compiled pattern. */
    +
    +/* Store NUMBER in two contiguous bytes starting at DESTINATION. */
    +
    +#define STORE_NUMBER(destination, number) \
    + do { \
    + (destination)[0] = (number) & 0377; \
    + (destination)[1] = (number) >> 8; \
    + } while (0)
    +
    +/* Same as STORE_NUMBER, except increment DESTINATION to
    + the byte after where the number is stored. Therefore, DESTINATION
    + must be an lvalue. */
    +
    +#define STORE_NUMBER_AND_INCR(destination, number) \
    + do { \
    + STORE_NUMBER (destination, number); \
    + (destination) += 2; \
    + } while (0)
    +
    +/* Put into DESTINATION a number stored in two contiguous bytes starting
    + at SOURCE. */
    +
    +#define EXTRACT_NUMBER(destination, source) \
    + do { \
    + (destination) = *(source) & 0377; \
    + (destination) += SIGN_EXTEND_CHAR (*((source) + 1)) << 8; \
    + } while (0)
    +
    +#ifdef DEBUG
    +static void
    +extract_number (dest, source)
    + int *dest;
    + unsigned char *source;
    +{
    + int temp = SIGN_EXTEND_CHAR (*(source + 1));
    + *dest = *source & 0377;
    + *dest += temp << 8;
    +}
    +
    +#ifndef EXTRACT_MACROS /* To debug the macros. */
    +#undef EXTRACT_NUMBER
    +#define EXTRACT_NUMBER(dest, src) extract_number (&dest, src)
    +#endif /* not EXTRACT_MACROS */
    +
    +#endif /* DEBUG */
    +
    +/* Same as EXTRACT_NUMBER, except increment SOURCE to after the number.
    + SOURCE must be an lvalue. */
    +
    +#define EXTRACT_NUMBER_AND_INCR(destination, source) \
    + do { \
    + EXTRACT_NUMBER (destination, source); \
    + (source) += 2; \
    + } while (0)
    +
    +#ifdef DEBUG
    +static void
    +extract_number_and_incr (destination, source)
    + int *destination;
    + unsigned char **source;
    +{
    + extract_number (destination, *source);
    + *source += 2;
    +}
    +
    +#ifndef EXTRACT_MACROS
    +#undef EXTRACT_NUMBER_AND_INCR
    +#define EXTRACT_NUMBER_AND_INCR(dest, src) \
    + extract_number_and_incr (&dest, &src)
    +#endif /* not EXTRACT_MACROS */
    +
    +#endif /* DEBUG */
    +
    +/* If DEBUG is defined, Regex prints many voluminous messages about what
    + it is doing (if the variable `debug' is nonzero). If linked with the
    + main program in `iregex.c', you can enter patterns and strings
    + interactively. And if linked with the main program in `main.c' and
    + the other test files, you can run the already-written tests. */
    +
    +#ifdef DEBUG
    +
    +/* We use standard I/O for debugging. */
    +#include <stdio.h>
    +
    +/* It is useful to test things that ``must'' be true when debugging. */
    +#include <assert.h>
    +
    +static int debug = 0;
    +
    +#define DEBUG_STATEMENT(e) e
    +#define DEBUG_PRINT1(x) if (debug) printf (x)
    +#define DEBUG_PRINT2(x1, x2) if (debug) printf (x1, x2)
    +#define DEBUG_PRINT3(x1, x2, x3) if (debug) printf (x1, x2, x3)
    +#define DEBUG_PRINT4(x1, x2, x3, x4) if (debug) printf (x1, x2, x3, x4)
    +#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e) \
    + if (debug) print_partial_compiled_pattern (s, e)
    +#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2) \
    + if (debug) print_double_string (w, s1, sz1, s2, sz2)
    +
    +
    +extern void printchar ();
    +
    +/* Print the fastmap in human-readable form. */
    +
    +void
    +print_fastmap (fastmap)
    + char *fastmap;
    +{
    + unsigned was_a_range = 0;
    + unsigned i = 0;
    +
    + while (i < (1 << BYTEWIDTH))
    + {
    + if (fastmap[i++])
    + {
    + was_a_range = 0;
    + printchar (i - 1);
    + while (i < (1 << BYTEWIDTH) && fastmap[i])
    + {
    + was_a_range = 1;
    + i++;
    + }
    + if (was_a_range)
    + {
    + printf ("-");
    + printchar (i - 1);
    + }
    + }
    + }
    + putchar ('\n');
    +}
    +
    +
    +/* Print a compiled pattern string in human-readable form, starting at
    + the START pointer into it and ending just before the pointer END. */
    +
    +void
    +print_partial_compiled_pattern (start, end)
    + unsigned char *start;
    + unsigned char *end;
    +{
    + int mcnt, mcnt2;
    + unsigned char *p = start;
    + unsigned char *pend = end;
    +
    + if (start == NULL)
    + {
    + printf ("(null)\n");
    + return;
    + }
    +
    + /* Loop over pattern commands. */
    + while (p < pend)
    + {
    + switch ((re_opcode_t) *p++)
    + {
    + case no_op:
    + printf ("/no_op");
    + break;
    +
    + case exactn:
    + mcnt = *p++;
    + printf ("/exactn/%d", mcnt);
    + do
    + {
    + putchar ('/');
    + printchar (*p++);
    + }
    + while (--mcnt);
    + break;
    +
    + case start_memory:
    + mcnt = *p++;
    + printf ("/start_memory/%d/%d", mcnt, *p++);
    + break;
    +
    + case stop_memory:
    + mcnt = *p++;
    + printf ("/stop_memory/%d/%d", mcnt, *p++);
    + break;
    +
    + case duplicate:
    + printf ("/duplicate/%d", *p++);
    + break;
    +
    + case anychar:
    + printf ("/anychar");
    + break;
    +
    + case charset:
    + case charset_not:
    + {
    + register int c;
    +
    + printf ("/charset%s",
    + (re_opcode_t) *(p - 1) == charset_not ? "_not" : "");
    +
    + assert (p + *p < pend);
    +
    + for (c = 0; c < *p; c++)
    + {
    + unsigned bit;
    + unsigned char map_byte = p[1 + c];
    +
    + putchar ('/');
    +
    + for (bit = 0; bit < BYTEWIDTH; bit++)
    + if (map_byte & (1 << bit))
    + printchar (c * BYTEWIDTH + bit);
    + }
    + p += 1 + *p;
    + break;
    + }
    +
    + case begline:
    + printf ("/begline");
    + break;
    +
    + case endline:
    + printf ("/endline");
    + break;
    +
    + case on_failure_jump:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/on_failure_jump/0/%d", mcnt);
    + break;
    +
    + case on_failure_keep_string_jump:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/on_failure_keep_string_jump/0/%d", mcnt);
    + break;
    +
    + case dummy_failure_jump:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/dummy_failure_jump/0/%d", mcnt);
    + break;
    +
    + case push_dummy_failure:
    + printf ("/push_dummy_failure");
    + break;
    +
    + case maybe_pop_jump:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/maybe_pop_jump/0/%d", mcnt);
    + break;
    +
    + case pop_failure_jump:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/pop_failure_jump/0/%d", mcnt);
    + break;
    +
    + case jump_past_alt:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/jump_past_alt/0/%d", mcnt);
    + break;
    +
    + case jump:
    + extract_number_and_incr (&mcnt, &p);
    + printf ("/jump/0/%d", mcnt);
    + break;
    +
    + case succeed_n:
    + extract_number_and_incr (&mcnt, &p);
    + extract_number_and_incr (&mcnt2, &p);
    + printf ("/succeed_n/0/%d/0/%d", mcnt, mcnt2);
    + break;
    +
    + case jump_n:
    + extract_number_and_incr (&mcnt, &p);
    + extract_number_and_incr (&mcnt2, &p);
    + printf ("/jump_n/0/%d/0/%d", mcnt, mcnt2);
    + break;
    +
    + case set_number_at:
    + extract_number_and_incr (&mcnt, &p);
    + extract_number_and_incr (&mcnt2, &p);
    + printf ("/set_number_at/0/%d/0/%d", mcnt, mcnt2);
    + break;
    +
    + case wordbound:
    + printf ("/wordbound");
    + break;
    +
    + case notwordbound:
    + printf ("/notwordbound");
    + break;
    +
    + case wordbeg:
    + printf ("/wordbeg");
    + break;
    +
    + case wordend:
    + printf ("/wordend");
    +
    +#ifdef emacs
    + case before_dot:
    + printf ("/before_dot");
    + break;
    +
    + case at_dot:
    + printf ("/at_dot");
    + break;
    +
    + case after_dot:
    + printf ("/after_dot");
    + break;
    +
    + case syntaxspec:
    + printf ("/syntaxspec");
    + mcnt = *p++;
    + printf ("/%d", mcnt);
    + break;
    +
    + case notsyntaxspec:
    + printf ("/notsyntaxspec");
    + mcnt = *p++;
    + printf ("/%d", mcnt);
    + break;
    +#endif /* emacs */
    +
    + case wordchar:
    + printf ("/wordchar");
    + break;
    +
    + case notwordchar:
    + printf ("/notwordchar");
    + break;
    +
    + case begbuf:
    + printf ("/begbuf");
    + break;
    +
    + case endbuf:
    + printf ("/endbuf");
    + break;
    +
    + default:
    + printf ("?%d", *(p-1));
    + }
    + }
    + printf ("/\n");
    +}
    +
    +
    +void
    +print_compiled_pattern (bufp)
    + struct re_pattern_buffer *bufp;
    +{
    + unsigned char *buffer = bufp->buffer;
    +
    + print_partial_compiled_pattern (buffer, buffer + bufp->used);
    + printf ("%d bytes used/%d bytes allocated.\n", bufp->used, bufp->allocated);
    +
    + if (bufp->fastmap_accurate && bufp->fastmap)
    + {
    + printf ("fastmap: ");
    + print_fastmap (bufp->fastmap);
    + }
    +
    + printf ("re_nsub: %d\t", bufp->re_nsub);
    + printf ("regs_alloc: %d\t", bufp->regs_allocated);
    + printf ("can_be_null: %d\t", bufp->can_be_null);
    + printf ("newline_anchor: %d\n", bufp->newline_anchor);
    + printf ("no_sub: %d\t", bufp->no_sub);
    + printf ("not_bol: %d\t", bufp->not_bol);
    + printf ("not_eol: %d\t", bufp->not_eol);
    + printf ("syntax: %d\n", bufp->syntax);
    + /* Perhaps we should print the translate table? */
    +}
    +
    +
    +void
    +print_double_string (where, string1, size1, string2, size2)
    + const char *where;
    + const char *string1;
    + const char *string2;
    + int size1;
    + int size2;
    +{
    + unsigned this_char;
    +
    + if (where == NULL)
    + printf ("(null)");
    + else
    + {
    + if (FIRST_STRING_P (where))
    + {
    + for (this_char = where - string1; this_char < size1; this_char++)
    + printchar (string1[this_char]);
    +
    + where = string2;
    + }
    +
    + for (this_char = where - string2; this_char < size2; this_char++)
    + printchar (string2[this_char]);
    + }
    +}
    +
    +#else /* not DEBUG */
    +
    +#undef assert
    +#define assert(e)
    +
    +#define DEBUG_STATEMENT(e)
    +#define DEBUG_PRINT1(x)
    +#define DEBUG_PRINT2(x1, x2)
    +#define DEBUG_PRINT3(x1, x2, x3)
    +#define DEBUG_PRINT4(x1, x2, x3, x4)
    +#define DEBUG_PRINT_COMPILED_PATTERN(p, s, e)
    +#define DEBUG_PRINT_DOUBLE_STRING(w, s1, sz1, s2, sz2)
    +
    +#endif /* not DEBUG */
    +
    +/* Set by `re_set_syntax' to the current regexp syntax to recognize. Can
    + also be assigned to arbitrarily: each pattern buffer stores its own
    + syntax, so it can be changed between regex compilations. */
    +reg_syntax_t re_syntax_options = RE_SYNTAX_EMACS;
    +
    +
    +/* Specify the precise syntax of regexps for compilation. This provides
    + for compatibility for various utilities which historically have
    + different, incompatible syntaxes.
    +
    + The argument SYNTAX is a bit mask comprised of the various bits
    + defined in regex.h. We return the old syntax. */
    +
    +reg_syntax_t
    +re_set_syntax (syntax)
    + reg_syntax_t syntax;
    +{
    + reg_syntax_t ret = re_syntax_options;
    +
    + re_syntax_options = syntax;
    + return ret;
    +}
    +
    +/* This table gives an error message for each of the error codes listed
    + in regex.h. Obviously the order here has to be same as there. */
    +
    +static const char *re_error_msg[] =
    + { NULL, /* REG_NOERROR */
    + "No match", /* REG_NOMATCH */
    + "Invalid regular expression", /* REG_BADPAT */
    + "Invalid collation character", /* REG_ECOLLATE */
    + "Invalid character class name", /* REG_ECTYPE */
    + "Trailing backslash", /* REG_EESCAPE */
    + "Invalid back reference", /* REG_ESUBREG */
    + "Unmatched [ or [^", /* REG_EBRACK */
    + "Unmatched ( or \\(", /* REG_EPAREN */
    + "Unmatched \\{", /* REG_EBRACE */
    + "Invalid content of \\{\\}", /* REG_BADBR */
    + "Invalid range end", /* REG_ERANGE */
    + "Memory exhausted", /* REG_ESPACE */
    + "Invalid preceding regular expression", /* REG_BADRPT */
    + "Premature end of regular expression", /* REG_EEND */
    + "Regular expression too big", /* REG_ESIZE */
    + "Unmatched ) or \\)", /* REG_ERPAREN */
    + };
    +
    +/* Subroutine declarations and macros for regex_compile. */
    +
    +static void store_op1 (), store_op2 ();
    +static void insert_op1 (), insert_op2 ();
    +static boolean at_begline_loc_p (), at_endline_loc_p ();
    +static boolean group_in_compile_stack ();
    +static reg_errcode_t compile_range ();
    +
    +/* Fetch the next character in the uncompiled pattern---translating it
    + if necessary. Also cast from a signed character in the constant
    + string passed to us by the user to an unsigned char that we can use
    + as an array index (in, e.g., `translate'). */
    +#define PATFETCH(c) \
    + do {if (p == pend) return REG_EEND; \
    + c = (unsigned char) *p++; \
    + if (translate) c = translate[c]; \
    + } while (0)
    +
    +/* Fetch the next character in the uncompiled pattern, with no
    + translation. */
    +#define PATFETCH_RAW(c) \
    + do {if (p == pend) return REG_EEND; \
    + c = (unsigned char) *p++; \
    + } while (0)
    +
    +/* Go backwards one character in the pattern. */
    +#define PATUNFETCH p--
    +
    +
    +/* If `translate' is non-null, return translate[D], else just D. We
    + cast the subscript to translate because some data is declared as
    + `char *', to avoid warnings when a string constant is passed. But
    + when we use a character as a subscript we must make it unsigned. */
    +#define TRANSLATE(d) (translate ? translate[(unsigned char) (d)] : (d))
    +
    +
    +/* Macros for outputting the compiled pattern into `buffer'. */
    +
    +/* If the buffer isn't allocated when it comes in, use this. */
    +#define INIT_BUF_SIZE 32
    +
    +/* Make sure we have at least N more bytes of space in buffer. */
    +#define GET_BUFFER_SPACE(n) \
    + while (b - bufp->buffer + (n) > bufp->allocated) \
    + EXTEND_BUFFER ()
    +
    +/* Make sure we have one more byte of buffer space and then add C to it. */
    +#define BUF_PUSH(c) \
    + do { \
    + GET_BUFFER_SPACE (1); \
    + *b++ = (unsigned char) (c); \
    + } while (0)
    +
    +
    +/* Ensure we have two more bytes of buffer space and then append C1 and C2. */
    +#define BUF_PUSH_2(c1, c2) \
    + do { \
    + GET_BUFFER_SPACE (2); \
    + *b++ = (unsigned char) (c1); \
    + *b++ = (unsigned char) (c2); \
    + } while (0)
    +
    +
    +/* As with BUF_PUSH_2, except for three bytes. */
    +#define BUF_PUSH_3(c1, c2, c3) \
    + do { \
    + GET_BUFFER_SPACE (3); \
    + *b++ = (unsigned char) (c1); \
    + *b++ = (unsigned char) (c2); \
    + *b++ = (unsigned char) (c3); \
    + } while (0)
    +
    +
    +/* Store a jump with opcode OP at LOC to location TO. We store a
    + relative address offset by the three bytes the jump itself occupies. */
    +#define STORE_JUMP(op, loc, to) \
    + store_op1 (op, loc, (to) - (loc) - 3)
    +
    +/* Likewise, for a two-argument jump. */
    +#define STORE_JUMP2(op, loc, to, arg) \
    + store_op2 (op, loc, (to) - (loc) - 3, arg)
    +
    +/* Like `STORE_JUMP', but for inserting. Assume `b' is the buffer end. */
    +#define INSERT_JUMP(op, loc, to) \
    + insert_op1 (op, loc, (to) - (loc) - 3, b)
    +
    +/* Like `STORE_JUMP2', but for inserting. Assume `b' is the buffer end. */
    +#define INSERT_JUMP2(op, loc, to, arg) \
    + insert_op2 (op, loc, (to) - (loc) - 3, arg, b)
    +
    +
    +/* This is not an arbitrary limit: the arguments which represent offsets
    + into the pattern are two bytes long. So if 2^16 bytes turns out to
    + be too small, many things would have to change. */
    +#define MAX_BUF_SIZE (1L << 16)
    +
    +
    +/* Extend the buffer by twice its current size via realloc and
    + reset the pointers that pointed into the old block to point to the
    + correct places in the new one. If extending the buffer results in it
    + being larger than MAX_BUF_SIZE, then flag memory exhausted. */
    +#define EXTEND_BUFFER() \
    + do { \
    + unsigned char *old_buffer = bufp->buffer; \
    + if (bufp->allocated == MAX_BUF_SIZE) \
    + return REG_ESIZE; \
    + bufp->allocated <<= 1; \
    + if (bufp->allocated > MAX_BUF_SIZE) \
    + bufp->allocated = MAX_BUF_SIZE; \
    + bufp->buffer = (unsigned char *) realloc (bufp->buffer, bufp->allocated);\
    + if (bufp->buffer == NULL) \
    + return REG_ESPACE; \
    + /* If the buffer moved, move all the pointers into it. */ \
    + if (old_buffer != bufp->buffer) \
    + { \
    + b = (b - old_buffer) + bufp->buffer; \
    + begalt = (begalt - old_buffer) + bufp->buffer; \
    + if (fixup_alt_jump) \
    + fixup_alt_jump = (fixup_alt_jump - old_buffer) + bufp->buffer;\
    + if (laststart) \
    + laststart = (laststart - old_buffer) + bufp->buffer; \
    + if (pending_exact) \
    + pending_exact = (pending_exact - old_buffer) + bufp->buffer; \
    + } \
    + } while (0)
    +
    +
    +/* Since we have one byte reserved for the register number argument to
    + {start,stop}_memory, the maximum number of groups we can report
    + things about is what fits in that byte. */
    +#define MAX_REGNUM 255
    +
    +/* But patterns can have more than `MAX_REGNUM' registers. We just
    + ignore the excess. */
    +typedef unsigned regnum_t;
    +
    +
    +/* Macros for the compile stack. */
    +
    +/* Since offsets can go either forwards or backwards, this type needs to
    + be able to hold values from -(MAX_BUF_SIZE - 1) to MAX_BUF_SIZE - 1. */
    +typedef int pattern_offset_t;
    +
    +typedef struct
    +{
    + pattern_offset_t begalt_offset;
    + pattern_offset_t fixup_alt_jump;
    + pattern_offset_t inner_group_offset;
    + pattern_offset_t laststart_offset;
    + regnum_t regnum;
    +} compile_stack_elt_t;
    +
    +
    +typedef struct
    +{
    + compile_stack_elt_t *stack;
    + unsigned size;
    + unsigned avail; /* Offset of next open position. */
    +} compile_stack_type;
    +
    +
    +#define INIT_COMPILE_STACK_SIZE 32
    +
    +#define COMPILE_STACK_EMPTY (compile_stack.avail == 0)
    +#define COMPILE_STACK_FULL (compile_stack.avail == compile_stack.size)
    +
    +/* The next available element. */
    +#define COMPILE_STACK_TOP (compile_stack.stack[compile_stack.avail])
    +
    +
    +/* Set the bit for character C in a list. */
    +#define SET_LIST_BIT(c) \
    + (b[((unsigned char) (c)) / BYTEWIDTH] \
    + |= 1 << (((unsigned char) c) % BYTEWIDTH))
    +
    +
    +/* Get the next unsigned number in the uncompiled pattern. */
    +#define GET_UNSIGNED_NUMBER(num) \
    + { if (p != pend) \
    + { \
    + PATFETCH (c); \
    + while (ISDIGIT (c)) \
    + { \
    + if (num < 0) \
    + num = 0; \
    + num = num * 10 + c - '0'; \
    + if (p == pend) \
    + break; \
    + PATFETCH (c); \
    + } \
    + } \
    + }
    +
    +#define CHAR_CLASS_MAX_LENGTH 6 /* Namely, `xdigit'. */
    +
    +#define IS_CHAR_CLASS(string) \
    + (STREQ (string, "alpha") || STREQ (string, "upper") \
    + || STREQ (string, "lower") || STREQ (string, "digit") \
    + || STREQ (string, "alnum") || STREQ (string, "xdigit") \
    + || STREQ (string, "space") || STREQ (string, "print") \
    + || STREQ (string, "punct") || STREQ (string, "graph") \
    + || STREQ (string, "cntrl") || STREQ (string, "blank"))
    +
    +/* `regex_compile' compiles PATTERN (of length SIZE) according to SYNTAX.
    + Returns one of error codes defined in `regex.h', or zero for success.
    +
    + Assumes the `allocated' (and perhaps `buffer') and `translate'
    + fields are set in BUFP on entry.
    +
    + If it succeeds, results are put in BUFP (if it returns an error, the
    + contents of BUFP are undefined):
    + `buffer' is the compiled pattern;
    + `syntax' is set to SYNTAX;
    + `used' is set to the length of the compiled pattern;
    + `fastmap_accurate' is zero;
    + `re_nsub' is the number of subexpressions in PATTERN;
    + `not_bol' and `not_eol' are zero;
    +
    + The `fastmap' and `newline_anchor' fields are neither
    + examined nor set. */
    +
    +static reg_errcode_t
    +regex_compile (pattern, size, syntax, bufp)
    + const char *pattern;
    + int size;
    + reg_syntax_t syntax;
    + struct re_pattern_buffer *bufp;
    +{
    + /* We fetch characters from PATTERN here. Even though PATTERN is
    + `char *' (i.e., signed), we declare these variables as unsigned, so
    + they can be reliably used as array indices. */
    + register unsigned char c, c1;
    +
    + /* A random tempory spot in PATTERN. */
    + const char *p1;
    +
    + /* Points to the end of the buffer, where we should append. */
    + register unsigned char *b;
    +
    + /* Keeps track of unclosed groups. */
    + compile_stack_type compile_stack;
    +
    + /* Points to the current (ending) position in the pattern. */
    + const char *p = pattern;
    + const char *pend = pattern + size;
    +
    + /* How to translate the characters in the pattern. */
    + char *translate = bufp->translate;
    +
    + /* Address of the count-byte of the most recently inserted `exactn'
    + command. This makes it possible to tell if a new exact-match
    + character can be added to that command or if the character requires
    + a new `exactn' command. */
    + unsigned char *pending_exact = 0;
    +
    + /* Address of start of the most recently finished expression.
    + This tells, e.g., postfix * where to find the start of its
    + operand. Reset at the beginning of groups and alternatives. */
    + unsigned char *laststart = 0;
    +
    + /* Address of beginning of regexp, or inside of last group. */
    + unsigned char *begalt;
    +
    + /* Place in the uncompiled pattern (i.e., the {) to
    + which to go back if the interval is invalid. */
    + const char *beg_interval;
    +
    + /* Address of the place where a forward jump should go to the end of
    + the containing expression. Each alternative of an `or' -- except the
    + last -- ends with a forward jump of this sort. */
    + unsigned char *fixup_alt_jump = 0;
    +
    + /* Counts open-groups as they are encountered. Remembered for the
    + matching close-group on the compile stack, so the same register
    + number is put in the stop_memory as the start_memory. */
    + regnum_t regnum = 0;
    +
    +#ifdef DEBUG
    + DEBUG_PRINT1 ("\nCompiling pattern: ");
    + if (debug)
    + {
    + unsigned debug_count;
    +
    + for (debug_count = 0; debug_count < size; debug_count++)
    + printchar (pattern[debug_count]);
    + putchar ('\n');
    + }
    +#endif /* DEBUG */
    +
    + /* Initialize the compile stack. */
    + compile_stack.stack = TALLOC (INIT_COMPILE_STACK_SIZE, compile_stack_elt_t);
    + if (compile_stack.stack == NULL)
    + return REG_ESPACE;
    +
    + compile_stack.size = INIT_COMPILE_STACK_SIZE;
    + compile_stack.avail = 0;
    +
    + /* Initialize the pattern buffer. */
    + bufp->syntax = syntax;
    + bufp->fastmap_accurate = 0;
    + bufp->not_bol = bufp->not_eol = 0;
    +
    + /* Set `used' to zero, so that if we return an error, the pattern
    + printer (for debugging) will think there's no pattern. We reset it
    + at the end. */
    + bufp->used = 0;
    +
    + /* Always count groups, whether or not bufp->no_sub is set. */
    + bufp->re_nsub = 0;
    +
    +#if !defined (emacs) && !defined (SYNTAX_TABLE)
    + /* Initialize the syntax table. */
    + init_syntax_once ();
    +#endif
    +
    + if (bufp->allocated == 0)
    + {
    + if (bufp->buffer)
    + { /* If zero allocated, but buffer is non-null, try to realloc
    + enough space. This loses if buffer's address is bogus, but
    + that is the user's responsibility. */
    + RETALLOC (bufp->buffer, INIT_BUF_SIZE, unsigned char);
    + }
    + else
    + { /* Caller did not allocate a buffer. Do it for them. */
    + bufp->buffer = TALLOC (INIT_BUF_SIZE, unsigned char);
    + }
    + if (!bufp->buffer) return REG_ESPACE;
    +
    + bufp->allocated = INIT_BUF_SIZE;
    + }
    +
    + begalt = b = bufp->buffer;
    +
    + /* Loop through the uncompiled pattern until we're at the end. */
    + while (p != pend)
    + {
    + PATFETCH (c);
    +
    + switch (c)
    + {
    + case '^':
    + {
    + if ( /* If at start of pattern, it's an operator. */
    + p == pattern + 1
    + /* If context independent, it's an operator. */
    + || syntax & RE_CONTEXT_INDEP_ANCHORS
    + /* Otherwise, depends on what's come before. */
    + || at_begline_loc_p (pattern, p, syntax))
    + BUF_PUSH (begline);
    + else
    + goto normal_char;
    + }
    + break;
    +
    +
    + case '$':
    + {
    + if ( /* If at end of pattern, it's an operator. */
    + p == pend
    + /* If context independent, it's an operator. */
    + || syntax & RE_CONTEXT_INDEP_ANCHORS
    + /* Otherwise, depends on what's next. */
    + || at_endline_loc_p (p, pend, syntax))
    + BUF_PUSH (endline);
    + else
    + goto normal_char;
    + }
    + break;
    +
    +
    + case '+':
    + case '?':
    + if ((syntax & RE_BK_PLUS_QM)
    + || (syntax & RE_LIMITED_OPS))
    + goto normal_char;
    + handle_plus:
    + case '*':
    + /* If there is no previous pattern... */
    + if (!laststart)
    + {
    + if (syntax & RE_CONTEXT_INVALID_OPS)
    + return REG_BADRPT;
    + else if (!(syntax & RE_CONTEXT_INDEP_OPS))
    + goto normal_char;
    + }
    +
    + {
    + /* Are we optimizing this jump? */
    + boolean keep_string_p = false;
    +
    + /* 1 means zero (many) matches is allowed. */
    + char zero_times_ok = 0, many_times_ok = 0;
    +
    + /* If there is a sequence of repetition chars, collapse it
    + down to just one (the right one). We can't combine
    + interval operators with these because of, e.g., `a{2}*',
    + which should only match an even number of `a's. */
    +
    + for (;;)
    + {
    + zero_times_ok |= c != '+';
    + many_times_ok |= c != '?';
    +
    + if (p == pend)
    + break;
    +
    + PATFETCH (c);
    +
    + if (c == '*'
    + || (!(syntax & RE_BK_PLUS_QM) && (c == '+' || c == '?')))
    + ;
    +
    + else if (syntax & RE_BK_PLUS_QM && c == '\\')
    + {
    + if (p == pend) return REG_EESCAPE;
    +
    + PATFETCH (c1);
    + if (!(c1 == '+' || c1 == '?'))
    + {
    + PATUNFETCH;
    + PATUNFETCH;
    + break;
    + }
    +
    + c = c1;
    + }
    + else
    + {
    + PATUNFETCH;
    + break;
    + }
    +
    + /* If we get here, we found another repeat character. */
    + }
    +
    + /* Star, etc. applied to an empty pattern is equivalent
    + to an empty pattern. */
    + if (!laststart)
    + break;
    +
    + /* Now we know whether or not zero matches is allowed
    + and also whether or not two or more matches is allowed. */
    + if (many_times_ok)
    + { /* More than one repetition is allowed, so put in at the
    + end a backward relative jump from `b' to before the next
    + jump we're going to put in below (which jumps from
    + laststart to after this jump).
    +
    + But if we are at the `*' in the exact sequence `.*\n',
    + insert an unconditional jump backwards to the .,
    + instead of the beginning of the loop. This way we only
    + push a failure point once, instead of every time
    + through the loop. */
    + assert (p - 1 > pattern);
    +
    + /* Allocate the space for the jump. */
    + GET_BUFFER_SPACE (3);
    +
    + /* We know we are not at the first character of the pattern,
    + because laststart was nonzero. And we've already
    + incremented `p', by the way, to be the character after
    + the `*'. Do we have to do something analogous here
    + for null bytes, because of RE_DOT_NOT_NULL? */
    + if (TRANSLATE (*(p - 2)) == TRANSLATE ('.')
    + && zero_times_ok
    + && p < pend && TRANSLATE (*p) == TRANSLATE ('\n')
    + && !(syntax & RE_DOT_NEWLINE))
    + { /* We have .*\n. */
    + STORE_JUMP (jump, b, laststart);
    + keep_string_p = true;
    + }
    + else
    + /* Anything else. */
    + STORE_JUMP (maybe_pop_jump, b, laststart - 3);
    +
    + /* We've added more stuff to the buffer. */
    + b += 3;
    + }
    +
    + /* On failure, jump from laststart to b + 3, which will be the
    + end of the buffer after this jump is inserted. */
    + GET_BUFFER_SPACE (3);
    + INSERT_JUMP (keep_string_p ? on_failure_keep_string_jump
    + : on_failure_jump,
    + laststart, b + 3);
    + pending_exact = 0;
    + b += 3;
    +
    + if (!zero_times_ok)
    + {
    + /* At least one repetition is required, so insert a
    + `dummy_failure_jump' before the initial
    + `on_failure_jump' instruction of the loop. This
    + effects a skip over that instruction the first time
    + we hit that loop. */
    + GET_BUFFER_SPACE (3);
    + INSERT_JUMP (dummy_failure_jump, laststart, laststart + 6);
    + b += 3;
    + }
    + }
    + break;
    +
    +
    + case '.':
    + laststart = b;
    + BUF_PUSH (anychar);
    + break;
    +
    +
    + case '[':
    + {
    + boolean had_char_class = false;
    +
    + if (p == pend) return REG_EBRACK;
    +
    + /* Ensure that we have enough space to push a charset: the
    + opcode, the length count, and the bitset; 34 bytes in all. */
    + GET_BUFFER_SPACE (34);
    +
    + laststart = b;
    +
    + /* We test `*p == '^' twice, instead of using an if
    + statement, so we only need one BUF_PUSH. */
    + BUF_PUSH (*p == '^' ? charset_not : charset);
    + if (*p == '^')
    + p++;
    +
    + /* Remember the first position in the bracket expression. */
    + p1 = p;
    +
    + /* Push the number of bytes in the bitmap. */
    + BUF_PUSH ((1 << BYTEWIDTH) / BYTEWIDTH);
    +
    + /* Clear the whole map. */
    + bzero (b, (1 << BYTEWIDTH) / BYTEWIDTH);
    +
    + /* charset_not matches newline according to a syntax bit. */
    + if ((re_opcode_t) b[-2] == charset_not
    + && (syntax & RE_HAT_LISTS_NOT_NEWLINE))
    + SET_LIST_BIT ('\n');
    +
    + /* Read in characters and ranges, setting map bits. */
    + for (;;)
    + {
    + if (p == pend) return REG_EBRACK;
    +
    + PATFETCH (c);
    +
    + /* \ might escape characters inside [...] and [^...]. */
    + if ((syntax & RE_BACKSLASH_ESCAPE_IN_LISTS) && c == '\\')
    + {
    + if (p == pend) return REG_EESCAPE;
    +
    + PATFETCH (c1);
    + SET_LIST_BIT (c1);
    + continue;
    + }
    +
    + /* Could be the end of the bracket expression. If it's
    + not (i.e., when the bracket expression is `[]' so
    + far), the ']' character bit gets set way below. */
    + if (c == ']' && p != p1 + 1)
    + break;
    +
    + /* Look ahead to see if it's a range when the last thing
    + was a character class. */
    + if (had_char_class && c == '-' && *p != ']')
    + return REG_ERANGE;
    +
    + /* Look ahead to see if it's a range when the last thing
    + was a character: if this is a hyphen not at the
    + beginning or the end of a list, then it's the range
    + operator. */
    + if (c == '-'
    + && !(p - 2 >= pattern && p[-2] == '[')
    + && !(p - 3 >= pattern && p[-3] == '[' && p[-2] == '^')
    + && *p != ']')
    + {
    + reg_errcode_t ret
    + = compile_range (&p, pend, translate, syntax, b);
    + if (ret != REG_NOERROR) return ret;
    + }
    +
    + else if (p[0] == '-' && p[1] != ']')
    + { /* This handles ranges made up of characters only. */
    + reg_errcode_t ret;
    +
    + /* Move past the `-'. */
    + PATFETCH (c1);
    +
    + ret = compile_range (&p, pend, translate, syntax, b);
    + if (ret != REG_NOERROR) return ret;
    + }
    +
    + /* See if we're at the beginning of a possible character
    + class. */
    +
    + else if (syntax & RE_CHAR_CLASSES && c == '[' && *p == ':')
    + { /* Leave room for the null. */
    + char str[CHAR_CLASS_MAX_LENGTH + 1];
    +
    + PATFETCH (c);
    + c1 = 0;
    +
    + /* If pattern is `[[:'. */
    + if (p == pend) return REG_EBRACK;
    +
    + for (;;)
    + {
    + PATFETCH (c);
    + if (c == ':' || c == ']' || p == pend
    + || c1 == CHAR_CLASS_MAX_LENGTH)
    + break;
    + str[c1++] = c;
    + }
    + str[c1] = '\0';
    +
    + /* If isn't a word bracketed by `[:' and:`]':
    + undo the ending character, the letters, and leave
    + the leading `:' and `[' (but set bits for them). */
    + if (c == ':' && *p == ']')
    + {
    + int ch;
    + boolean is_alnum = STREQ (str, "alnum");
    + boolean is_alpha = STREQ (str, "alpha");
    + boolean is_blank = STREQ (str, "blank");
    + boolean is_cntrl = STREQ (str, "cntrl");
    + boolean is_digit = STREQ (str, "digit");
    + boolean is_graph = STREQ (str, "graph");
    + boolean is_lower = STREQ (str, "lower");
    + boolean is_print = STREQ (str, "print");
    + boolean is_punct = STREQ (str, "punct");
    + boolean is_space = STREQ (str, "space");
    + boolean is_upper = STREQ (str, "upper");
    + boolean is_xdigit = STREQ (str, "xdigit");
    +
    + if (!IS_CHAR_CLASS (str)) return REG_ECTYPE;
    +
    + /* Throw away the ] at the end of the character
    + class. */
    + PATFETCH (c);
    +
    + if (p == pend) return REG_EBRACK;
    +
    + for (ch = 0; ch < 1 << BYTEWIDTH; ch++)
    + {
    + if ( (is_alnum && ISALNUM (ch))
    + || (is_alpha && ISALPHA (ch))
    + || (is_blank && ISBLANK (ch))
    + || (is_cntrl && ISCNTRL (ch))
    + || (is_digit && ISDIGIT (ch))
    + || (is_graph && ISGRAPH (ch))
    + || (is_lower && ISLOWER (ch))
    + || (is_print && ISPRINT (ch))
    + || (is_punct && ISPUNCT (ch))
    + || (is_space && ISSPACE (ch))
    + || (is_upper && ISUPPER (ch))
    + || (is_xdigit && ISXDIGIT (ch)))
    + SET_LIST_BIT (ch);
    + }
    + had_char_class = true;
    + }
    + else
    + {
    + c1++;
    + while (c1--)
    + PATUNFETCH;
    + SET_LIST_BIT ('[');
    + SET_LIST_BIT (':');
    + had_char_class = false;
    + }
    + }
    + else
    + {
    + had_char_class = false;
    + SET_LIST_BIT (c);
    + }
    + }
    +
    + /* Discard any (non)matching list bytes that are all 0 at the
    + end of the map. Decrease the map-length byte too. */
    + while ((int) b[-1] > 0 && b[b[-1] - 1] == 0)
    + b[-1]--;
    + b += b[-1];
    + }
    + break;
    +
    +
    + case '(':
    + if (syntax & RE_NO_BK_PARENS)
    + goto handle_open;
    + else
    + goto normal_char;
    +
    +
    + case ')':
    + if (syntax & RE_NO_BK_PARENS)
    + goto handle_close;
    + else
    + goto normal_char;
    +
    +
    + case '\n':
    + if (syntax & RE_NEWLINE_ALT)
    + goto handle_alt;
    + else
    + goto normal_char;
    +
    +
    + case '|':
    + if (syntax & RE_NO_BK_VBAR)
    + goto handle_alt;
    + else
    + goto normal_char;
    +
    +
    + case '{':
    + if (syntax & RE_INTERVALS && syntax & RE_NO_BK_BRACES)
    + goto handle_interval;
    + else
    + goto normal_char;
    +
    +
    + case '\\':
    + if (p == pend) return REG_EESCAPE;
    +
    + /* Do not translate the character after the \, so that we can
    + distinguish, e.g., \B from \b, even if we normally would
    + translate, e.g., B to b. */
    + PATFETCH_RAW (c);
    +
    + switch (c)
    + {
    + case '(':
    + if (syntax & RE_NO_BK_PARENS)
    + goto normal_backslash;
    +
    + handle_open:
    + bufp->re_nsub++;
    + regnum++;
    +
    + if (COMPILE_STACK_FULL)
    + {
    + RETALLOC (compile_stack.stack, compile_stack.size << 1,
    + compile_stack_elt_t);
    + if (compile_stack.stack == NULL) return REG_ESPACE;
    +
    + compile_stack.size <<= 1;
    + }
    +
    + /* These are the values to restore when we hit end of this
    + group. They are all relative offsets, so that if the
    + whole pattern moves because of realloc, they will still
    + be valid. */
    + COMPILE_STACK_TOP.begalt_offset = begalt - bufp->buffer;
    + COMPILE_STACK_TOP.fixup_alt_jump
    + = fixup_alt_jump ? fixup_alt_jump - bufp->buffer + 1 : 0;
    + COMPILE_STACK_TOP.laststart_offset = b - bufp->buffer;
    + COMPILE_STACK_TOP.regnum = regnum;
    +
    + /* We will eventually replace the 0 with the number of
    + groups inner to this one. But do not push a
    + start_memory for groups beyond the last one we can
    + represent in the compiled pattern. */
    + if (regnum <= MAX_REGNUM)
    + {
    + COMPILE_STACK_TOP.inner_group_offset = b - bufp->buffer + 2;
    + BUF_PUSH_3 (start_memory, regnum, 0);
    + }
    +
    + compile_stack.avail++;
    +
    + fixup_alt_jump = 0;
    + laststart = 0;
    + begalt = b;
    + /* If we've reached MAX_REGNUM groups, then this open
    + won't actually generate any code, so we'll have to
    + clear pending_exact explicitly. */
    + pending_exact = 0;
    + break;
    +
    +
    + case ')':
    + if (syntax & RE_NO_BK_PARENS) goto normal_backslash;
    +
    + if (COMPILE_STACK_EMPTY){
    + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD){
    + goto normal_backslash;
    + }else{
    + return REG_ERPAREN;
    + }
    + }
    +
    + handle_close:
    + if (fixup_alt_jump)
    + { /* Push a dummy failure point at the end of the
    + alternative for a possible future
    + `pop_failure_jump' to pop. See comments at
    + `push_dummy_failure' in `re_match_2'. */
    + BUF_PUSH (push_dummy_failure);
    +
    + /* We allocated space for this jump when we assigned
    + to `fixup_alt_jump', in the `handle_alt' case below. */
    + STORE_JUMP (jump_past_alt, fixup_alt_jump, b - 1);
    + }
    +
    + /* See similar code for backslashed left paren above. */
    + if (COMPILE_STACK_EMPTY){
    + if (syntax & RE_UNMATCHED_RIGHT_PAREN_ORD){
    + goto normal_char;
    + }else{
    + return REG_ERPAREN;
    + }
    + }
    +
    + /* Since we just checked for an empty stack above, this
    + ``can't happen''. */
    + assert (compile_stack.avail != 0);
    + {
    + /* We don't just want to restore into `regnum', because
    + later groups should continue to be numbered higher,
    + as in `(ab)c(de)' -- the second group is #2. */
    + regnum_t this_group_regnum;
    +
    + compile_stack.avail--;
    + begalt = bufp->buffer + COMPILE_STACK_TOP.begalt_offset;
    + fixup_alt_jump
    + = COMPILE_STACK_TOP.fixup_alt_jump
    + ? bufp->buffer + COMPILE_STACK_TOP.fixup_alt_jump - 1
    + : 0;
    + laststart = bufp->buffer + COMPILE_STACK_TOP.laststart_offset;
    + this_group_regnum = COMPILE_STACK_TOP.regnum;
    + /* If we've reached MAX_REGNUM groups, then this open
    + won't actually generate any code, so we'll have to
    + clear pending_exact explicitly. */
    + pending_exact = 0;
    +
    + /* We're at the end of the group, so now we know how many
    + groups were inside this one. */
    + if (this_group_regnum <= MAX_REGNUM)
    + {
    + unsigned char *inner_group_loc
    + = bufp->buffer + COMPILE_STACK_TOP.inner_group_offset;
    +
    + *inner_group_loc = regnum - this_group_regnum;
    + BUF_PUSH_3 (stop_memory, this_group_regnum,
    + regnum - this_group_regnum);
    + }
    + }
    + break;
    +
    +
    + case '|': /* `\|'. */
    + if (syntax & RE_LIMITED_OPS || syntax & RE_NO_BK_VBAR)
    + goto normal_backslash;
    + handle_alt:
    + if (syntax & RE_LIMITED_OPS)
    + goto normal_char;
    +
    + /* Insert before the previous alternative a jump which
    + jumps to this alternative if the former fails. */
    + GET_BUFFER_SPACE (3);
    + INSERT_JUMP (on_failure_jump, begalt, b + 6);
    + pending_exact = 0;
    + b += 3;
    +
    + /* The alternative before this one has a jump after it
    + which gets executed if it gets matched. Adjust that
    + jump so it will jump to this alternative's analogous
    + jump (put in below, which in turn will jump to the next
    + (if any) alternative's such jump, etc.). The last such
    + jump jumps to the correct final destination. A picture:
    + _____ _____
    + | | | |
    + | v | v
    + a | b | c
    +
    + If we are at `b', then fixup_alt_jump right now points to a
    + three-byte space after `a'. We'll put in the jump, set
    + fixup_alt_jump to right after `b', and leave behind three
    + bytes which we'll fill in when we get to after `c'. */
    +
    + if (fixup_alt_jump)
    + STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
    +
    + /* Mark and leave space for a jump after this alternative,
    + to be filled in later either by next alternative or
    + when know we're at the end of a series of alternatives. */
    + fixup_alt_jump = b;
    + GET_BUFFER_SPACE (3);
    + b += 3;
    +
    + laststart = 0;
    + begalt = b;
    + break;
    +
    +
    + case '{':
    + /* If \{ is a literal. */
    + if (!(syntax & RE_INTERVALS)
    + /* If we're at `\{' and it's not the open-interval
    + operator. */
    + || ((syntax & RE_INTERVALS) && (syntax & RE_NO_BK_BRACES))
    + || (p - 2 == pattern && p == pend))
    + goto normal_backslash;
    +
    + handle_interval:
    + {
    + /* If got here, then the syntax allows intervals. */
    +
    + /* At least (most) this many matches must be made. */
    + int lower_bound = -1, upper_bound = -1;
    +
    + beg_interval = p - 1;
    +
    + if (p == pend)
    + {
    + if (syntax & RE_NO_BK_BRACES)
    + goto unfetch_interval;
    + else
    + return REG_EBRACE;
    + }
    +
    + GET_UNSIGNED_NUMBER (lower_bound);
    +
    + if (c == ',')
    + {
    + GET_UNSIGNED_NUMBER (upper_bound);
    + if (upper_bound < 0) upper_bound = RE_DUP_MAX;
    + }
    + else
    + /* Interval such as `{1}' => match exactly once. */
    + upper_bound = lower_bound;
    +
    + if (lower_bound < 0 || upper_bound > RE_DUP_MAX
    + || lower_bound > upper_bound)
    + {
    + if (syntax & RE_NO_BK_BRACES)
    + goto unfetch_interval;
    + else
    + return REG_BADBR;
    + }
    +
    + if (!(syntax & RE_NO_BK_BRACES))
    + {
    + if (c != '\\') return REG_EBRACE;
    +
    + PATFETCH (c);
    + }
    +
    + if (c != '}')
    + {
    + if (syntax & RE_NO_BK_BRACES)
    + goto unfetch_interval;
    + else
    + return REG_BADBR;
    + }
    +
    + /* We just parsed a valid interval. */
    +
    + /* If it's invalid to have no preceding re. */
    + if (!laststart)
    + {
    + if (syntax & RE_CONTEXT_INVALID_OPS)
    + return REG_BADRPT;
    + else if (syntax & RE_CONTEXT_INDEP_OPS)
    + laststart = b;
    + else
    + goto unfetch_interval;
    + }
    +
    + /* If the upper bound is zero, don't want to succeed at
    + all; jump from `laststart' to `b + 3', which will be
    + the end of the buffer after we insert the jump. */
    + if (upper_bound == 0)
    + {
    + GET_BUFFER_SPACE (3);
    + INSERT_JUMP (jump, laststart, b + 3);
    + b += 3;
    + }
    +
    + /* Otherwise, we have a nontrivial interval. When
    + we're all done, the pattern will look like:
    + set_number_at <jump count> <upper bound>
    + set_number_at <succeed_n count> <lower bound>
    + succeed_n <after jump addr> <succed_n count>
    + <body of loop>
    + jump_n <succeed_n addr> <jump count>
    + (The upper bound and `jump_n' are omitted if
    + `upper_bound' is 1, though.) */
    + else
    + { /* If the upper bound is > 1, we need to insert
    + more at the end of the loop. */
    + unsigned nbytes = 10 + (upper_bound > 1) * 10;
    +
    + GET_BUFFER_SPACE (nbytes);
    +
    + /* Initialize lower bound of the `succeed_n', even
    + though it will be set during matching by its
    + attendant `set_number_at' (inserted next),
    + because `re_compile_fastmap' needs to know.
    + Jump to the `jump_n' we might insert below. */
    + INSERT_JUMP2 (succeed_n, laststart,
    + b + 5 + (upper_bound > 1) * 5,
    + lower_bound);
    + b += 5;
    +
    + /* Code to initialize the lower bound. Insert
    + before the `succeed_n'. The `5' is the last two
    + bytes of this `set_number_at', plus 3 bytes of
    + the following `succeed_n'. */
    + insert_op2 (set_number_at, laststart, 5, lower_bound, b);
    + b += 5;
    +
    + if (upper_bound > 1)
    + { /* More than one repetition is allowed, so
    + append a backward jump to the `succeed_n'
    + that starts this interval.
    +
    + When we've reached this during matching,
    + we'll have matched the interval once, so
    + jump back only `upper_bound - 1' times. */
    + STORE_JUMP2 (jump_n, b, laststart + 5,
    + upper_bound - 1);
    + b += 5;
    +
    + /* The location we want to set is the second
    + parameter of the `jump_n'; that is `b-2' as
    + an absolute address. `laststart' will be
    + the `set_number_at' we're about to insert;
    + `laststart+3' the number to set, the source
    + for the relative address. But we are
    + inserting into the middle of the pattern --
    + so everything is getting moved up by 5.
    + Conclusion: (b - 2) - (laststart + 3) + 5,
    + i.e., b - laststart.
    +
    + We insert this at the beginning of the loop
    + so that if we fail during matching, we'll
    + reinitialize the bounds. */
    + insert_op2 (set_number_at, laststart, b - laststart,
    + upper_bound - 1, b);
    + b += 5;
    + }
    + }
    + pending_exact = 0;
    + beg_interval = NULL;
    + }
    + break;
    +
    + unfetch_interval:
    + /* If an invalid interval, match the characters as literals. */
    + assert (beg_interval);
    + p = beg_interval;
    + beg_interval = NULL;
    +
    + /* normal_char and normal_backslash need `c'. */
    + PATFETCH (c);
    +
    + if (!(syntax & RE_NO_BK_BRACES))
    + {
    + if (p > pattern && p[-1] == '\\')
    + goto normal_backslash;
    + }
    + goto normal_char;
    +
    +#ifdef emacs
    + /* There is no way to specify the before_dot and after_dot
    + operators. rms says this is ok. --karl */
    + case '=':
    + BUF_PUSH (at_dot);
    + break;
    +
    + case 's':
    + laststart = b;
    + PATFETCH (c);
    + BUF_PUSH_2 (syntaxspec, syntax_spec_code[c]);
    + break;
    +
    + case 'S':
    + laststart = b;
    + PATFETCH (c);
    + BUF_PUSH_2 (notsyntaxspec, syntax_spec_code[c]);
    + break;
    +#endif /* emacs */
    +
    +
    + case 'w':
    + laststart = b;
    + BUF_PUSH (wordchar);
    + break;
    +
    +
    + case 'W':
    + laststart = b;
    + BUF_PUSH (notwordchar);
    + break;
    +
    +
    + case '<':
    + BUF_PUSH (wordbeg);
    + break;
    +
    + case '>':
    + BUF_PUSH (wordend);
    + break;
    +
    + case 'b':
    + BUF_PUSH (wordbound);
    + break;
    +
    + case 'B':
    + BUF_PUSH (notwordbound);
    + break;
    +
    + case '`':
    + BUF_PUSH (begbuf);
    + break;
    +
    + case '\'':
    + BUF_PUSH (endbuf);
    + break;
    +
    + case '1': case '2': case '3': case '4': case '5':
    + case '6': case '7': case '8': case '9':
    + if (syntax & RE_NO_BK_REFS)
    + goto normal_char;
    +
    + c1 = c - '0';
    +
    + if (c1 > regnum)
    + return REG_ESUBREG;
    +
    + /* Can't back reference to a subexpression if inside of it. */
    + if (group_in_compile_stack (compile_stack, c1))
    + goto normal_char;
    +
    + laststart = b;
    + BUF_PUSH_2 (duplicate, c1);
    + break;
    +
    +
    + case '+':
    + case '?':
    + if (syntax & RE_BK_PLUS_QM)
    + goto handle_plus;
    + else
    + goto normal_backslash;
    +
    + default:
    + normal_backslash:
    + /* You might think it would be useful for \ to mean
    + not to translate; but if we don't translate it
    + it will never match anything. */
    + c = TRANSLATE (c);
    + goto normal_char;
    + }
    + break;
    +
    +
    + default:
    + /* Expects the character in `c'. */
    + normal_char:
    + /* If no exactn currently being built. */
    + if (!pending_exact
    +
    + /* If last exactn not at current position. */
    + || pending_exact + *pending_exact + 1 != b
    +
    + /* We have only one byte following the exactn for the count. */
    + || *pending_exact == (1 << BYTEWIDTH) - 1
    +
    + /* If followed by a repetition operator. */
    + || *p == '*' || *p == '^'
    + || ((syntax & RE_BK_PLUS_QM)
    + ? *p == '\\' && (p[1] == '+' || p[1] == '?')
    + : (*p == '+' || *p == '?'))
    + || ((syntax & RE_INTERVALS)
    + && ((syntax & RE_NO_BK_BRACES)
    + ? *p == '{'
    + : (p[0] == '\\' && p[1] == '{'))))
    + {
    + /* Start building a new exactn. */
    +
    + laststart = b;
    +
    + BUF_PUSH_2 (exactn, 0);
    + pending_exact = b - 1;
    + }
    +
    + BUF_PUSH (c);
    + (*pending_exact)++;
    + break;
    + } /* switch (c) */
    + } /* while p != pend */
    +
    +
    + /* Through the pattern now. */
    +
    + if (fixup_alt_jump)
    + STORE_JUMP (jump_past_alt, fixup_alt_jump, b);
    +
    + if (!COMPILE_STACK_EMPTY)
    + return REG_EPAREN;
    +
    + free (compile_stack.stack);
    +
    + /* We have succeeded; set the length of the buffer. */
    + bufp->used = b - bufp->buffer;
    +
    +#ifdef DEBUG
    + if (debug)
    + {
    + DEBUG_PRINT1 ("\nCompiled pattern: ");
    + print_compiled_pattern (bufp);
    + }
    +#endif /* DEBUG */
    +
    + return REG_NOERROR;
    +} /* regex_compile */
    +
    +/* Subroutines for `regex_compile'. */
    +
    +/* Store OP at LOC followed by two-byte integer parameter ARG. */
    +
    +static void
    +store_op1 (op, loc, arg)
    + re_opcode_t op;
    + unsigned char *loc;
    + int arg;
    +{
    + *loc = (unsigned char) op;
    + STORE_NUMBER (loc + 1, arg);
    +}
    +
    +
    +/* Like `store_op1', but for two two-byte parameters ARG1 and ARG2. */
    +
    +static void
    +store_op2 (op, loc, arg1, arg2)
    + re_opcode_t op;
    + unsigned char *loc;
    + int arg1, arg2;
    +{
    + *loc = (unsigned char) op;
    + STORE_NUMBER (loc + 1, arg1);
    + STORE_NUMBER (loc + 3, arg2);
    +}
    +
    +
    +/* Copy the bytes from LOC to END to open up three bytes of space at LOC
    + for OP followed by two-byte integer parameter ARG. */
    +
    +static void
    +insert_op1 (op, loc, arg, end)
    + re_opcode_t op;
    + unsigned char *loc;
    + int arg;
    + unsigned char *end;
    +{
    + register unsigned char *pfrom = end;
    + register unsigned char *pto = end + 3;
    +
    + while (pfrom != loc)
    + *--pto = *--pfrom;
    +
    + store_op1 (op, loc, arg);
    +}
    +
    +
    +/* Like `insert_op1', but for two two-byte parameters ARG1 and ARG2. */
    +
    +static void
    +insert_op2 (op, loc, arg1, arg2, end)
    + re_opcode_t op;
    + unsigned char *loc;
    + int arg1, arg2;
    + unsigned char *end;
    +{
    + register unsigned char *pfrom = end;
    + register unsigned char *pto = end + 5;
    +
    + while (pfrom != loc)
    + *--pto = *--pfrom;
    +
    + store_op2 (op, loc, arg1, arg2);
    +}
    +
    +
    +/* P points to just after a ^ in PATTERN. Return true if that ^ comes
    + after an alternative or a begin-subexpression. We assume there is at
    + least one character before the ^. */
    +
    +static boolean
    +at_begline_loc_p (pattern, p, syntax)
    + const char *pattern, *p;
    + reg_syntax_t syntax;
    +{
    + const char *prev = p - 2;
    + boolean prev_prev_backslash = prev > pattern && prev[-1] == '\\';
    +
    + return
    + /* After a subexpression? */
    + (*prev == '(' && (syntax & RE_NO_BK_PARENS || prev_prev_backslash))
    + /* After an alternative? */
    + || (*prev == '|' && (syntax & RE_NO_BK_VBAR || prev_prev_backslash));
    +}
    +
    +
    +/* The dual of at_begline_loc_p. This one is for $. We assume there is
    + at least one character after the $, i.e., `P < PEND'. */
    +
    +static boolean
    +at_endline_loc_p (p, pend, syntax)
    + const char *p, *pend;
    + int syntax;
    +{
    + const char *next = p;
    + boolean next_backslash = *next == '\\';
    + const char *next_next = p + 1 < pend ? p + 1 : NULL;
    +
    + return
    + /* Before a subexpression? */
    + (syntax & RE_NO_BK_PARENS ? *next == ')'
    + : next_backslash && next_next && *next_next == ')')
    + /* Before an alternative? */
    + || (syntax & RE_NO_BK_VBAR ? *next == '|'
    + : next_backslash && next_next && *next_next == '|');
    +}
    +
    +
    +/* Returns true if REGNUM is in one of COMPILE_STACK's elements and
    + false if it's not. */
    +
    +static boolean
    +group_in_compile_stack (compile_stack, regnum)
    + compile_stack_type compile_stack;
    + regnum_t regnum;
    +{
    + int this_element;
    +
    + for (this_element = compile_stack.avail - 1;
    + this_element >= 0;
    + this_element--)
    + if (compile_stack.stack[this_element].regnum == regnum)
    + return true;
    +
    + return false;
    +}
    +
    +
    +/* Read the ending character of a range (in a bracket expression) from the
    + uncompiled pattern *P_PTR (which ends at PEND). We assume the
    + starting character is in `P[-2]'. (`P[-1]' is the character `-'.)
    + Then we set the translation of all bits between the starting and
    + ending characters (inclusive) in the compiled pattern B.
    +
    + Return an error code.
    +
    + We use these short variable names so we can use the same macros as
    + `regex_compile' itself. */
    +
    +static reg_errcode_t
    +compile_range (p_ptr, pend, translate, syntax, b)
    + const char **p_ptr, *pend;
    + char *translate;
    + reg_syntax_t syntax;
    + unsigned char *b;
    +{
    + unsigned this_char;
    +
    + const char *p = *p_ptr;
    + int range_start, range_end;
    +
    + if (p == pend)
    + return REG_ERANGE;
    +
    + /* Even though the pattern is a signed `char *', we need to fetch
    + with unsigned char *'s; if the high bit of the pattern character
    + is set, the range endpoints will be negative if we fetch using a
    + signed char *.
    +
    + We also want to fetch the endpoints without translating them; the
    + appropriate translation is done in the bit-setting loop below. */
    + range_start = ((unsigned char *) p)[-2];
    + range_end = ((unsigned char *) p)[0];
    +
    + /* Have to increment the pointer into the pattern string, so the
    + caller isn't still at the ending character. */
    + (*p_ptr)++;
    +
    + /* If the start is after the end, the range is empty. */
    + if (range_start > range_end)
    + return syntax & RE_NO_EMPTY_RANGES ? REG_ERANGE : REG_NOERROR;
    +
    + /* Here we see why `this_char' has to be larger than an `unsigned
    + char' -- the range is inclusive, so if `range_end' == 0xff
    + (assuming 8-bit characters), we would otherwise go into an infinite
    + loop, since all characters <= 0xff. */
    + for (this_char = range_start; this_char <= range_end; this_char++)
    + {
    + SET_LIST_BIT (TRANSLATE (this_char));
    + }
    +
    + return REG_NOERROR;
    +}
    +
    +/* Failure stack declarations and macros; both re_compile_fastmap and
    + re_match_2 use a failure stack. These have to be macros because of
    + REGEX_ALLOCATE. */
    +
    +
    +/* Number of failure points for which to initially allocate space
    + when matching. If this number is exceeded, we allocate more
    + space, so it is not a hard limit. */
    +#ifndef INIT_FAILURE_ALLOC
    +#define INIT_FAILURE_ALLOC 5
    +#endif
    +
    +/* Roughly the maximum number of failure points on the stack. Would be
    + exactly that if always used MAX_FAILURE_SPACE each time we failed.
    + This is a variable only so users of regex can assign to it; we never
    + change it ourselves. */
    +int re_max_failures = 2000;
    +
    +typedef const unsigned char *fail_stack_elt_t;
    +
    +typedef struct
    +{
    + fail_stack_elt_t *stack;
    + unsigned size;
    + unsigned avail; /* Offset of next open position. */
    +} fail_stack_type;
    +
    +#define FAIL_STACK_EMPTY() (fail_stack.avail == 0)
    +#define FAIL_STACK_PTR_EMPTY() (fail_stack_ptr->avail == 0)
    +#define FAIL_STACK_FULL() (fail_stack.avail == fail_stack.size)
    +#define FAIL_STACK_TOP() (fail_stack.stack[fail_stack.avail])
    +
    +
    +/* Initialize `fail_stack'. Do `return -2' if the alloc fails. */
    +
    +#define INIT_FAIL_STACK() \
    + do { \
    + fail_stack.stack = (fail_stack_elt_t *) \
    + REGEX_ALLOCATE (INIT_FAILURE_ALLOC * sizeof (fail_stack_elt_t)); \
    + \
    + if (fail_stack.stack == NULL) \
    + return -2; \
    + \
    + fail_stack.size = INIT_FAILURE_ALLOC; \
    + fail_stack.avail = 0; \
    + } while (0)
    +
    +
    +/* Double the size of FAIL_STACK, up to approximately `re_max_failures' items.
    +
    + Return 1 if succeeds, and 0 if either ran out of memory
    + allocating space for it or it was already too large.
    +
    + REGEX_REALLOCATE requires `destination' be declared. */
    +
    +#define DOUBLE_FAIL_STACK(fail_stack) \
    + ((fail_stack).size > re_max_failures * MAX_FAILURE_ITEMS \
    + ? 0 \
    + : ((fail_stack).stack = (fail_stack_elt_t *) \
    + REGEX_REALLOCATE ((fail_stack).stack, \
    + (fail_stack).size * sizeof (fail_stack_elt_t), \
    + ((fail_stack).size << 1) * sizeof (fail_stack_elt_t)), \
    + \
    + (fail_stack).stack == NULL \
    + ? 0 \
    + : ((fail_stack).size <<= 1, \
    + 1)))
    +
    +
    +/* Push PATTERN_OP on FAIL_STACK.
    +
    + Return 1 if was able to do so and 0 if ran out of memory allocating
    + space to do so. */
    +#define PUSH_PATTERN_OP(pattern_op, fail_stack) \
    + ((FAIL_STACK_FULL () \
    + && !DOUBLE_FAIL_STACK (fail_stack)) \
    + ? 0 \
    + : ((fail_stack).stack[(fail_stack).avail++] = pattern_op, \
    + 1))
    +
    +/* This pushes an item onto the failure stack. Must be a four-byte
    + value. Assumes the variable `fail_stack'. Probably should only
    + be called from within `PUSH_FAILURE_POINT'. */
    +#define PUSH_FAILURE_ITEM(item) \
    + fail_stack.stack[fail_stack.avail++] = (fail_stack_elt_t) item
    +
    +/* The complement operation. Assumes `fail_stack' is nonempty. */
    +#define POP_FAILURE_ITEM() fail_stack.stack[--fail_stack.avail]
    +
    +/* Used to omit pushing failure point id's when we're not debugging. */
    +#ifdef DEBUG
    +#define DEBUG_PUSH PUSH_FAILURE_ITEM
    +#define DEBUG_POP(item_addr) *(item_addr) = POP_FAILURE_ITEM ()
    +#else
    +#define DEBUG_PUSH(item)
    +#define DEBUG_POP(item_addr)
    +#endif
    +
    +
    +/* Push the information about the state we will need
    + if we ever fail back to it.
    +
    + Requires variables fail_stack, regstart, regend, reg_info, and
    + num_regs be declared. DOUBLE_FAIL_STACK requires `destination' be
    + declared.
    +
    + Does `return FAILURE_CODE' if runs out of memory. */
    +
    +#define PUSH_FAILURE_POINT(pattern_place, string_place, failure_code) \
    + do { \
    + char *destination; \
    + /* Must be int, so when we don't save any registers, the arithmetic \
    + of 0 + -1 isn't done as unsigned. */ \
    + int this_reg; \
    + \
    + DEBUG_STATEMENT (failure_id++); \
    + DEBUG_STATEMENT (nfailure_points_pushed++); \
    + DEBUG_PRINT2 ("\nPUSH_FAILURE_POINT #%u:\n", failure_id); \
    + DEBUG_PRINT2 (" Before push, next avail: %d\n", (fail_stack).avail);\
    + DEBUG_PRINT2 (" size: %d\n", (fail_stack).size);\
    + \
    + DEBUG_PRINT2 (" slots needed: %d\n", NUM_FAILURE_ITEMS); \
    + DEBUG_PRINT2 (" available: %d\n", REMAINING_AVAIL_SLOTS); \
    + \
    + /* Ensure we have enough space allocated for what we will push. */ \
    + while (REMAINING_AVAIL_SLOTS < NUM_FAILURE_ITEMS) \
    + { \
    + if (!DOUBLE_FAIL_STACK (fail_stack)) \
    + return failure_code; \
    + \
    + DEBUG_PRINT2 ("\n Doubled stack; size now: %d\n", \
    + (fail_stack).size); \
    + DEBUG_PRINT2 (" slots available: %d\n", REMAINING_AVAIL_SLOTS);\
    + } \
    + \
    + /* Push the info, starting with the registers. */ \
    + DEBUG_PRINT1 ("\n"); \
    + \
    + for (this_reg = lowest_active_reg; this_reg <= highest_active_reg; \
    + this_reg++) \
    + { \
    + DEBUG_PRINT2 (" Pushing reg: %d\n", this_reg); \
    + DEBUG_STATEMENT (num_regs_pushed++); \
    + \
    + DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \
    + PUSH_FAILURE_ITEM (regstart[this_reg]); \
    + \
    + DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \
    + PUSH_FAILURE_ITEM (regend[this_reg]); \
    + \
    + DEBUG_PRINT2 (" info: 0x%x\n ", reg_info[this_reg]); \
    + DEBUG_PRINT2 (" match_null=%d", \
    + REG_MATCH_NULL_STRING_P (reg_info[this_reg])); \
    + DEBUG_PRINT2 (" active=%d", IS_ACTIVE (reg_info[this_reg])); \
    + DEBUG_PRINT2 (" matched_something=%d", \
    + MATCHED_SOMETHING (reg_info[this_reg])); \
    + DEBUG_PRINT2 (" ever_matched=%d", \
    + EVER_MATCHED_SOMETHING (reg_info[this_reg])); \
    + DEBUG_PRINT1 ("\n"); \
    + PUSH_FAILURE_ITEM (reg_info[this_reg].word); \
    + } \
    + \
    + DEBUG_PRINT2 (" Pushing low active reg: %d\n", lowest_active_reg);\
    + PUSH_FAILURE_ITEM (lowest_active_reg); \
    + \
    + DEBUG_PRINT2 (" Pushing high active reg: %d\n", highest_active_reg);\
    + PUSH_FAILURE_ITEM (highest_active_reg); \
    + \
    + DEBUG_PRINT2 (" Pushing pattern 0x%x: ", pattern_place); \
    + DEBUG_PRINT_COMPILED_PATTERN (bufp, pattern_place, pend); \
    + PUSH_FAILURE_ITEM (pattern_place); \
    + \
    + DEBUG_PRINT2 (" Pushing string 0x%x: `", string_place); \
    + DEBUG_PRINT_DOUBLE_STRING (string_place, string1, size1, string2, \
    + size2); \
    + DEBUG_PRINT1 ("'\n"); \
    + PUSH_FAILURE_ITEM (string_place); \
    + \
    + DEBUG_PRINT2 (" Pushing failure id: %u\n", failure_id); \
    + DEBUG_PUSH (failure_id); \
    + } while (0)
    +
    +/* This is the number of items that are pushed and popped on the stack
    + for each register. */
    +#define NUM_REG_ITEMS 3
    +
    +/* Individual items aside from the registers. */
    +#ifdef DEBUG
    +#define NUM_NONREG_ITEMS 5 /* Includes failure point id. */
    +#else
    +#define NUM_NONREG_ITEMS 4
    +#endif
    +
    +/* We push at most this many items on the stack. */
    +#define MAX_FAILURE_ITEMS ((num_regs - 1) * NUM_REG_ITEMS + NUM_NONREG_ITEMS)
    +
    +/* We actually push this many items. */
    +#define NUM_FAILURE_ITEMS \
    + ((highest_active_reg - lowest_active_reg + 1) * NUM_REG_ITEMS \
    + + NUM_NONREG_ITEMS)
    +
    +/* How many items can still be added to the stack without overflowing it. */
    +#define REMAINING_AVAIL_SLOTS ((fail_stack).size - (fail_stack).avail)
    +
    +
    +/* Pops what PUSH_FAIL_STACK pushes.
    +
    + We restore into the parameters, all of which should be lvalues:
    + STR -- the saved data position.
    + PAT -- the saved pattern position.
    + LOW_REG, HIGH_REG -- the highest and lowest active registers.
    + REGSTART, REGEND -- arrays of string positions.
    + REG_INFO -- array of information about each subexpression.
    +
    + Also assumes the variables `fail_stack' and (if debugging), `bufp',
    + `pend', `string1', `size1', `string2', and `size2'. */
    +
    +#define POP_FAILURE_POINT(str, pat, low_reg, high_reg, regstart, regend, reg_info)\
    +{ \
    + DEBUG_STATEMENT (fail_stack_elt_t failure_id;) \
    + int this_reg; \
    + const unsigned char *string_temp; \
    + \
    + assert (!FAIL_STACK_EMPTY ()); \
    + \
    + /* Remove failure points and point to how many regs pushed. */ \
    + DEBUG_PRINT1 ("POP_FAILURE_POINT:\n"); \
    + DEBUG_PRINT2 (" Before pop, next avail: %d\n", fail_stack.avail); \
    + DEBUG_PRINT2 (" size: %d\n", fail_stack.size); \
    + \
    + assert (fail_stack.avail >= NUM_NONREG_ITEMS); \
    + \
    + DEBUG_POP (&failure_id); \
    + DEBUG_PRINT2 (" Popping failure id: %u\n", failure_id); \
    + \
    + /* If the saved string location is NULL, it came from an \
    + on_failure_keep_string_jump opcode, and we want to throw away the \
    + saved NULL, thus retaining our current position in the string. */ \
    + string_temp = POP_FAILURE_ITEM (); \
    + if (string_temp != NULL) \
    + str = (const char *) string_temp; \
    + \
    + DEBUG_PRINT2 (" Popping string 0x%x: `", str); \
    + DEBUG_PRINT_DOUBLE_STRING (str, string1, size1, string2, size2); \
    + DEBUG_PRINT1 ("'\n"); \
    + \
    + pat = (unsigned char *) POP_FAILURE_ITEM (); \
    + DEBUG_PRINT2 (" Popping pattern 0x%x: ", pat); \
    + DEBUG_PRINT_COMPILED_PATTERN (bufp, pat, pend); \
    + \
    + /* Restore register info. */ \
    + high_reg = (unsigned) POP_FAILURE_ITEM (); \
    + DEBUG_PRINT2 (" Popping high active reg: %d\n", high_reg); \
    + \
    + low_reg = (unsigned) POP_FAILURE_ITEM (); \
    + DEBUG_PRINT2 (" Popping low active reg: %d\n", low_reg); \
    + \
    + for (this_reg = high_reg; this_reg >= low_reg; this_reg--) \
    + { \
    + DEBUG_PRINT2 (" Popping reg: %d\n", this_reg); \
    + \
    + reg_info[this_reg].word = POP_FAILURE_ITEM (); \
    + DEBUG_PRINT2 (" info: 0x%x\n", reg_info[this_reg]); \
    + \
    + regend[this_reg] = (const char *) POP_FAILURE_ITEM (); \
    + DEBUG_PRINT2 (" end: 0x%x\n", regend[this_reg]); \
    + \
    + regstart[this_reg] = (const char *) POP_FAILURE_ITEM (); \
    + DEBUG_PRINT2 (" start: 0x%x\n", regstart[this_reg]); \
    + } \
    + \
    + DEBUG_STATEMENT (nfailure_points_popped++); \
    +} /* POP_FAILURE_POINT */
    +
    +/* re_compile_fastmap computes a ``fastmap'' for the compiled pattern in
    + BUFP. A fastmap records which of the (1 << BYTEWIDTH) possible
    + characters can start a string that matches the pattern. This fastmap
    + is used by re_search to skip quickly over impossible starting points.
    +
    + The caller must supply the address of a (1 << BYTEWIDTH)-byte data
    + area as BUFP->fastmap.
    +
    + We set the `fastmap', `fastmap_accurate', and `can_be_null' fields in
    + the pattern buffer.
    +
    + Returns 0 if we succeed, -2 if an internal error. */
    +
    +int
    +re_compile_fastmap (bufp)
    + struct re_pattern_buffer *bufp;
    +{
    + int j, k;
    + fail_stack_type fail_stack;
    +#ifndef REGEX_MALLOC
    + char *destination;
    +#endif
    + /* We don't push any register information onto the failure stack. */
    + unsigned num_regs = 0;
    +
    + register char *fastmap = bufp->fastmap;
    + unsigned char *pattern = bufp->buffer;
    + unsigned long size = bufp->used;
    + const unsigned char *p = pattern;
    + register unsigned char *pend = pattern + size;
    +
    + /* Assume that each path through the pattern can be null until
    + proven otherwise. We set this false at the bottom of switch
    + statement, to which we get only if a particular path doesn't
    + match the empty string. */
    + boolean path_can_be_null = true;
    +
    + /* We aren't doing a `succeed_n' to begin with. */
    + boolean succeed_n_p = false;
    +
    + assert (fastmap != NULL && p != NULL);
    +
    + INIT_FAIL_STACK ();
    + bzero (fastmap, 1 << BYTEWIDTH); /* Assume nothing's valid. */
    + bufp->fastmap_accurate = 1; /* It will be when we're done. */
    + bufp->can_be_null = 0;
    +
    + while (p != pend || !FAIL_STACK_EMPTY ())
    + {
    + if (p == pend)
    + {
    + bufp->can_be_null |= path_can_be_null;
    +
    + /* Reset for next path. */
    + path_can_be_null = true;
    +
    + p = fail_stack.stack[--fail_stack.avail];
    + }
    +
    + /* We should never be about to go beyond the end of the pattern. */
    + assert (p < pend);
    +
    +#ifdef SWITCH_ENUM_BUG
    + switch ((int) ((re_opcode_t) *p++))
    +#else
    + switch ((re_opcode_t) *p++)
    +#endif
    + {
    +
    + /* I guess the idea here is to simply not bother with a fastmap
    + if a backreference is used, since it's too hard to figure out
    + the fastmap for the corresponding group. Setting
    + `can_be_null' stops `re_search_2' from using the fastmap, so
    + that is all we do. */
    + case duplicate:
    + bufp->can_be_null = 1;
    + return 0;
    +
    +
    + /* Following are the cases which match a character. These end
    + with `break'. */
    +
    + case exactn:
    + fastmap[p[1]] = 1;
    + break;
    +
    +
    + case charset:
    + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
    + if (p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH)))
    + fastmap[j] = 1;
    + break;
    +
    +
    + case charset_not:
    + /* Chars beyond end of map must be allowed. */
    + for (j = *p * BYTEWIDTH; j < (1 << BYTEWIDTH); j++)
    + fastmap[j] = 1;
    +
    + for (j = *p++ * BYTEWIDTH - 1; j >= 0; j--)
    + if (!(p[j / BYTEWIDTH] & (1 << (j % BYTEWIDTH))))
    + fastmap[j] = 1;
    + break;
    +
    +
    + case wordchar:
    + for (j = 0; j < (1 << BYTEWIDTH); j++)
    + if (SYNTAX (j) == Sword)
    + fastmap[j] = 1;
    + break;
    +
    +
    + case notwordchar:
    + for (j = 0; j < (1 << BYTEWIDTH); j++)
    + if (SYNTAX (j) != Sword)
    + fastmap[j] = 1;
    + break;
    +
    +
    + case anychar:
    + /* `.' matches anything ... */
    + for (j = 0; j < (1 << BYTEWIDTH); j++)
    + fastmap[j] = 1;
    +
    + /* ... except perhaps newline. */
    + if (!(bufp->syntax & RE_DOT_NEWLINE))
    + fastmap['\n'] = 0;
    +
    + /* Return if we have already set `can_be_null'; if we have,
    + then the fastmap is irrelevant. Something's wrong here. */
    + else if (bufp->can_be_null)
    + return 0;
    +
    + /* Otherwise, have to check alternative paths. */
    + break;
    +
    +
    +#ifdef emacs
    + case syntaxspec:
    + k = *p++;
    + for (j = 0; j < (1 << BYTEWIDTH); j++)
    + if (SYNTAX (j) == (enum syntaxcode) k)
    + fastmap[j] = 1;
    + break;
    +
    +
    + case notsyntaxspec:
    + k = *p++;
    + for (j = 0; j < (1 << BYTEWIDTH); j++)
    + if (SYNTAX (j) != (enum syntaxcode) k)
    + fastmap[j] = 1;
    + break;
    +
    +
    + /* All cases after this match the empty string. These end with
    + `continue'. */
    +
    +
    + case before_dot:
    + case at_dot:
    + case after_dot:
    + continue;
    +#endif /* not emacs */
    +
    +
    + case no_op:
    + case begline:
    + case endline:
    + case begbuf:
    + case endbuf:
    + case wordbound:
    + case notwordbound:
    + case wordbeg:
    + case wordend:
    + case push_dummy_failure:
    + continue;
    +
    +
    + case jump_n:
    + case pop_failure_jump:
    + case maybe_pop_jump:
    + case jump:
    + case jump_past_alt:
    + case dummy_failure_jump:
    + EXTRACT_NUMBER_AND_INCR (j, p);
    + p += j;
    + if (j > 0)
    + continue;
    +
    + /* Jump backward implies we just went through the body of a
    + loop and matched nothing. Opcode jumped to should be
    + `on_failure_jump' or `succeed_n'. Just treat it like an
    + ordinary jump. For a * loop, it has pushed its failure
    + point already; if so, discard that as redundant. */
    + if ((re_opcode_t) *p != on_failure_jump
    + && (re_opcode_t) *p != succeed_n)
    + continue;
    +
    + p++;
    + EXTRACT_NUMBER_AND_INCR (j, p);
    + p += j;
    +
    + /* If what's on the stack is where we are now, pop it. */
    + if (!FAIL_STACK_EMPTY ()
    + && fail_stack.stack[fail_stack.avail - 1] == p)
    + fail_stack.avail--;
    +
    + continue;
    +
    +
    + case on_failure_jump:
    + case on_failure_keep_string_jump:
    + handle_on_failure_jump:
    + EXTRACT_NUMBER_AND_INCR (j, p);
    +
    + /* For some patterns, e.g., `(a?)?', `p+j' here points to the
    + end of the pattern. We don't want to push such a point,
    + since when we restore it above, entering the switch will
    + increment `p' past the end of the pattern. We don't need
    + to push such a point since we obviously won't find any more
    + fastmap entries beyond `pend'. Such a pattern can match
    + the null string, though. */
    + if (p + j < pend)
    + {
    + if (!PUSH_PATTERN_OP (p + j, fail_stack))
    + return -2;
    + }
    + else
    + bufp->can_be_null = 1;
    +
    + if (succeed_n_p)
    + {
    + EXTRACT_NUMBER_AND_INCR (k, p); /* Skip the n. */
    + succeed_n_p = false;
    + }
    +
    + continue;
    +
    +
    + case succeed_n:
    + /* Get to the number of times to succeed. */
    + p += 2;
    +
    + /* Increment p past the n for when k != 0. */
    + EXTRACT_NUMBER_AND_INCR (k, p);
    + if (k == 0)
    + {
    + p -= 4;
    + succeed_n_p = true; /* Spaghetti code alert. */
    + goto handle_on_failure_jump;
    + }
    + continue;
    +
    +
    + case set_number_at:
    + p += 4;
    + continue;
    +
    +
    + case start_memory:
    + case stop_memory:
    + p += 2;
    + continue;
    +
    +
    + default:
    + abort (); /* We have listed all the cases. */
    + } /* switch *p++ */
    +
    + /* Getting here means we have found the possible starting
    + characters for one path of the pattern -- and that the empty
    + string does not match. We need not follow this path further.
    + Instead, look at the next alternative (remembered on the
    + stack), or quit if no more. The test at the top of the loop
    + does these things. */
    + path_can_be_null = false;
    + p = pend;
    + } /* while p */
    +
    + /* Set `can_be_null' for the last path (also the first path, if the
    + pattern is empty). */
    + bufp->can_be_null |= path_can_be_null;
    + return 0;
    +} /* re_compile_fastmap */
    +
    +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
    + ENDS. Subsequent matches using PATTERN_BUFFER and REGS will use
    + this memory for recording register information. STARTS and ENDS
    + must be allocated using the malloc library routine, and must each
    + be at least NUM_REGS * sizeof (regoff_t) bytes long.
    +
    + If NUM_REGS == 0, then subsequent matches should allocate their own
    + register data.
    +
    + Unless this function is called, the first search or match using
    + PATTERN_BUFFER will allocate its own register data, without
    + freeing the old data. */
    +
    +void
    +re_set_registers (bufp, regs, num_regs, starts, ends)
    + struct re_pattern_buffer *bufp;
    + struct re_registers *regs;
    + unsigned num_regs;
    + regoff_t *starts, *ends;
    +{
    + if (num_regs)
    + {
    + bufp->regs_allocated = REGS_REALLOCATE;
    + regs->num_regs = num_regs;
    + regs->start = starts;
    + regs->end = ends;
    + }
    + else
    + {
    + bufp->regs_allocated = REGS_UNALLOCATED;
    + regs->num_regs = 0;
    + regs->start = regs->end = (regoff_t) 0;
    + }
    +}
    +
    +/* Searching routines. */
    +
    +/* Like re_search_2, below, but only one string is specified, and
    + doesn't let you say where to stop matching. */
    +
    +int
    +re_search (bufp, string, size, startpos, range, regs)
    + struct re_pattern_buffer *bufp;
    + const char *string;
    + int size, startpos, range;
    + struct re_registers *regs;
    +{
    + return re_search_2 (bufp, NULL, 0, string, size, startpos, range,
    + regs, size);
    +}
    +
    +
    +/* Using the compiled pattern in BUFP->buffer, first tries to match the
    + virtual concatenation of STRING1 and STRING2, starting first at index
    + STARTPOS, then at STARTPOS + 1, and so on.
    +
    + STRING1 and STRING2 have length SIZE1 and SIZE2, respectively.
    +
    + RANGE is how far to scan while trying to match. RANGE = 0 means try
    + only at STARTPOS; in general, the last start tried is STARTPOS +
    + RANGE.
    +
    + In REGS, return the indices of the virtual concatenation of STRING1
    + and STRING2 that matched the entire BUFP->buffer and its contained
    + subexpressions.
    +
    + Do not consider matching one past the index STOP in the virtual
    + concatenation of STRING1 and STRING2.
    +
    + We return either the position in the strings at which the match was
    + found, -1 if no match, or -2 if error (such as failure
    + stack overflow). */
    +
    +int
    +re_search_2 (bufp, string1, size1, string2, size2, startpos, range, regs, stop)
    + struct re_pattern_buffer *bufp;
    + const char *string1, *string2;
    + int size1, size2;
    + int startpos;
    + int range;
    + struct re_registers *regs;
    + int stop;
    +{
    + int val;
    + register char *fastmap = bufp->fastmap;
    + register char *translate = bufp->translate;
    + int total_size = size1 + size2;
    + int endpos = startpos + range;
    +
    + /* Check for out-of-range STARTPOS. */
    + if (startpos < 0 || startpos > total_size)
    + return -1;
    +
    + /* Fix up RANGE if it might eventually take us outside
    + the virtual concatenation of STRING1 and STRING2. */
    + if (endpos < -1)
    + range = -1 - startpos;
    + else if (endpos > total_size)
    + range = total_size - startpos;
    +
    + /* If the search isn't to be a backwards one, don't waste time in a
    + search for a pattern that must be anchored. */
    + if (bufp->used > 0 && (re_opcode_t) bufp->buffer[0] == begbuf && range > 0)
    + {
    + if (startpos > 0)
    + return -1;
    + else
    + range = 1;
    + }
    +
    + /* Update the fastmap now if not correct already. */
    + if (fastmap && !bufp->fastmap_accurate)
    + if (re_compile_fastmap (bufp) == -2)
    + return -2;
    +
    + /* Loop through the string, looking for a place to start matching. */
    + for (;;)
    + {
    + /* If a fastmap is supplied, skip quickly over characters that
    + cannot be the start of a match. If the pattern can match the
    + null string, however, we don't need to skip characters; we want
    + the first null string. */
    + if (fastmap && startpos < total_size && !bufp->can_be_null)
    + {
    + if (range > 0) /* Searching forwards. */
    + {
    + register const char *d;
    + register int lim = 0;
    + int irange = range;
    +
    + if (startpos < size1 && startpos + range >= size1)
    + lim = range - (size1 - startpos);
    +
    + d = (startpos >= size1 ? string2 - size1 : string1) + startpos;
    +
    + /* Written out as an if-else to avoid testing `translate'
    + inside the loop. */
    + if (translate)
    + while (range > lim
    + && !fastmap[(unsigned char)
    + translate[(unsigned char) *d++]])
    + range--;
    + else
    + while (range > lim && !fastmap[(unsigned char) *d++])
    + range--;
    +
    + startpos += irange - range;
    + }
    + else /* Searching backwards. */
    + {
    + register char c = (size1 == 0 || startpos >= size1
    + ? string2[startpos - size1]
    + : string1[startpos]);
    +
    + if (!fastmap[(unsigned char) TRANSLATE (c)])
    + goto advance;
    + }
    + }
    +
    + /* If can't match the null string, and that's all we have left, fail. */
    + if (range >= 0 && startpos == total_size && fastmap
    + && !bufp->can_be_null)
    + return -1;
    +
    + val = re_match_2 (bufp, string1, size1, string2, size2,
    + startpos, regs, stop);
    + if (val >= 0)
    + return startpos;
    +
    + if (val == -2)
    + return -2;
    +
    + advance:
    + if (!range)
    + break;
    + else if (range > 0)
    + {
    + range--;
    + startpos++;
    + }
    + else
    + {
    + range++;
    + startpos--;
    + }
    + }
    + return -1;
    +} /* re_search_2 */
    +
    +/* Declarations and macros for re_match_2. */
    +
    +static int bcmp_translate ();
    +static boolean alt_match_null_string_p (),
    + common_op_match_null_string_p (),
    + group_match_null_string_p ();
    +
    +/* Structure for per-register (a.k.a. per-group) information.
    + This must not be longer than one word, because we push this value
    + onto the failure stack. Other register information, such as the
    + starting and ending positions (which are addresses), and the list of
    + inner groups (which is a bits list) are maintained in separate
    + variables.
    +
    + We are making a (strictly speaking) nonportable assumption here: that
    + the compiler will pack our bit fields into something that fits into
    + the type of `word', i.e., is something that fits into one item on the
    + failure stack. */
    +typedef union
    +{
    + fail_stack_elt_t word;
    + struct
    + {
    + /* This field is one if this group can match the empty string,
    + zero if not. If not yet determined, `MATCH_NULL_UNSET_VALUE'. */
    +#define MATCH_NULL_UNSET_VALUE 3
    + unsigned match_null_string_p : 2;
    + unsigned is_active : 1;
    + unsigned matched_something : 1;
    + unsigned ever_matched_something : 1;
    + } bits;
    +} register_info_type;
    +
    +#define REG_MATCH_NULL_STRING_P(R) ((R).bits.match_null_string_p)
    +#define IS_ACTIVE(R) ((R).bits.is_active)
    +#define MATCHED_SOMETHING(R) ((R).bits.matched_something)
    +#define EVER_MATCHED_SOMETHING(R) ((R).bits.ever_matched_something)
    +
    +
    +/* Call this when have matched a real character; it sets `matched' flags
    + for the subexpressions which we are currently inside. Also records
    + that those subexprs have matched. */
    +#define SET_REGS_MATCHED() \
    + do \
    + { \
    + unsigned r; \
    + for (r = lowest_active_reg; r <= highest_active_reg; r++) \
    + { \
    + MATCHED_SOMETHING (reg_info[r]) \
    + = EVER_MATCHED_SOMETHING (reg_info[r]) \
    + = 1; \
    + } \
    + } \
    + while (0)
    +
    +
    +/* This converts PTR, a pointer into one of the search strings `string1'
    + and `string2' into an offset from the beginning of that string. */
    +#define POINTER_TO_OFFSET(ptr) \
    + (FIRST_STRING_P (ptr) ? (ptr) - string1 : (ptr) - string2 + size1)
    +
    +/* Registers are set to a sentinel when they haven't yet matched. */
    +#define REG_UNSET_VALUE ((char *) -1)
    +#define REG_UNSET(e) ((e) == REG_UNSET_VALUE)
    +
    +
    +/* Macros for dealing with the split strings in re_match_2. */
    +
    +#define MATCHING_IN_FIRST_STRING (dend == end_match_1)
    +
    +/* Call before fetching a character with *d. This switches over to
    + string2 if necessary. */
    +#define PREFETCH() \
    + while (d == dend) \
    + { \
    + /* End of string2 => fail. */ \
    + if (dend == end_match_2) \
    + goto fail; \
    + /* End of string1 => advance to string2. */ \
    + d = string2; \
    + dend = end_match_2; \
    + }
    +
    +
    +/* Test if at very beginning or at very end of the virtual concatenation
    + of `string1' and `string2'. If only one string, it's `string2'. */
    +#define AT_STRINGS_BEG(d) ((d) == (size1 ? string1 : string2) || !size2)
    +#define AT_STRINGS_END(d) ((d) == end2)
    +
    +
    +/* Test if D points to a character which is word-constituent. We have
    + two special cases to check for: if past the end of string1, look at
    + the first character in string2; and if before the beginning of
    + string2, look at the last character in string1. */
    +#define WORDCHAR_P(d) \
    + (SYNTAX ((d) == end1 ? *string2 \
    + : (d) == string2 - 1 ? *(end1 - 1) : *(d)) \
    + == Sword)
    +
    +/* Test if the character before D and the one at D differ with respect
    + to being word-constituent. */
    +#define AT_WORD_BOUNDARY(d) \
    + (AT_STRINGS_BEG (d) || AT_STRINGS_END (d) \
    + || WORDCHAR_P (d - 1) != WORDCHAR_P (d))
    +
    +
    +/* Free everything we malloc. */
    +#ifdef REGEX_MALLOC
    +#define FREE_VAR(var) if (var) free (var); var = NULL
    +#define FREE_VARIABLES() \
    + do { \
    + FREE_VAR (fail_stack.stack); \
    + FREE_VAR (regstart); \
    + FREE_VAR (regend); \
    + FREE_VAR (old_regstart); \
    + FREE_VAR (old_regend); \
    + FREE_VAR (best_regstart); \
    + FREE_VAR (best_regend); \
    + FREE_VAR (reg_info); \
    + FREE_VAR (reg_dummy); \
    + FREE_VAR (reg_info_dummy); \
    + } while (0)
    +#else /* not REGEX_MALLOC */
    +/* Some MIPS systems (at least) want this to free alloca'd storage. */
    +#define FREE_VARIABLES() alloca (0)
    +#endif /* not REGEX_MALLOC */
    +
    +
    +/* These values must meet several constraints. They must not be valid
    + register values; since we have a limit of 255 registers (because
    + we use only one byte in the pattern for the register number), we can
    + use numbers larger than 255. They must differ by 1, because of
    + NUM_FAILURE_ITEMS above. And the value for the lowest register must
    + be larger than the value for the highest register, so we do not try
    + to actually save any registers when none are active. */
    +#define NO_HIGHEST_ACTIVE_REG (1 << BYTEWIDTH)
    +#define NO_LOWEST_ACTIVE_REG (NO_HIGHEST_ACTIVE_REG + 1)
    +
    +/* Matching routines. */
    +
    +#ifndef emacs /* Emacs never uses this. */
    +/* re_match is like re_match_2 except it takes only a single string. */
    +
    +int
    +re_match (bufp, string, size, pos, regs)
    + struct re_pattern_buffer *bufp;
    + const char *string;
    + int size, pos;
    + struct re_registers *regs;
    + {
    + return re_match_2 (bufp, NULL, 0, string, size, pos, regs, size);
    +}
    +#endif /* not emacs */
    +
    +
    +/* re_match_2 matches the compiled pattern in BUFP against the
    + the (virtual) concatenation of STRING1 and STRING2 (of length SIZE1
    + and SIZE2, respectively). We start matching at POS, and stop
    + matching at STOP.
    +
    + If REGS is non-null and the `no_sub' field of BUFP is nonzero, we
    + store offsets for the substring each group matched in REGS. See the
    + documentation for exactly how many groups we fill.
    +
    + We return -1 if no match, -2 if an internal error (such as the
    + failure stack overflowing). Otherwise, we return the length of the
    + matched substring. */
    +
    +int
    +re_match_2 (bufp, string1, size1, string2, size2, pos, regs, stop)
    + struct re_pattern_buffer *bufp;
    + const char *string1, *string2;
    + int size1, size2;
    + int pos;
    + struct re_registers *regs;
    + int stop;
    +{
    + /* General temporaries. */
    + int mcnt;
    + unsigned char *p1;
    +
    + /* Just past the end of the corresponding string. */
    + const char *end1, *end2;
    +
    + /* Pointers into string1 and string2, just past the last characters in
    + each to consider matching. */
    + const char *end_match_1, *end_match_2;
    +
    + /* Where we are in the data, and the end of the current string. */
    + const char *d, *dend;
    +
    + /* Where we are in the pattern, and the end of the pattern. */
    + unsigned char *p = bufp->buffer;
    + register unsigned char *pend = p + bufp->used;
    +
    + /* We use this to map every character in the string. */
    + char *translate = bufp->translate;
    +
    + /* Failure point stack. Each place that can handle a failure further
    + down the line pushes a failure point on this stack. It consists of
    + restart, regend, and reg_info for all registers corresponding to
    + the subexpressions we're currently inside, plus the number of such
    + registers, and, finally, two char *'s. The first char * is where
    + to resume scanning the pattern; the second one is where to resume
    + scanning the strings. If the latter is zero, the failure point is
    + a ``dummy''; if a failure happens and the failure point is a dummy,
    + it gets discarded and the next next one is tried. */
    + fail_stack_type fail_stack;
    +#ifdef DEBUG
    + static unsigned failure_id = 0;
    + unsigned nfailure_points_pushed = 0, nfailure_points_popped = 0;
    +#endif
    +
    + /* We fill all the registers internally, independent of what we
    + return, for use in backreferences. The number here includes
    + an element for register zero. */
    + unsigned num_regs = bufp->re_nsub + 1;
    +
    + /* The currently active registers. */
    + unsigned lowest_active_reg = NO_LOWEST_ACTIVE_REG;
    + unsigned highest_active_reg = NO_HIGHEST_ACTIVE_REG;
    +
    + /* Information on the contents of registers. These are pointers into
    + the input strings; they record just what was matched (on this
    + attempt) by a subexpression part of the pattern, that is, the
    + regnum-th regstart pointer points to where in the pattern we began
    + matching and the regnum-th regend points to right after where we
    + stopped matching the regnum-th subexpression. (The zeroth register
    + keeps track of what the whole pattern matches.) */
    + const char **regstart=NULL, **regend=NULL;
    +
    + /* If a group that's operated upon by a repetition operator fails to
    + match anything, then the register for its start will need to be
    + restored because it will have been set to wherever in the string we
    + are when we last see its open-group operator. Similarly for a
    + register's end. */
    + const char **old_regstart=NULL, **old_regend=NULL;
    +
    + /* The is_active field of reg_info helps us keep track of which (possibly
    + nested) subexpressions we are currently in. The matched_something
    + field of reg_info[reg_num] helps us tell whether or not we have
    + matched any of the pattern so far this time through the reg_num-th
    + subexpression. These two fields get reset each time through any
    + loop their register is in. */
    + register_info_type *reg_info=NULL;
    +
    + /* The following record the register info as found in the above
    + variables when we find a match better than any we've seen before.
    + This happens as we backtrack through the failure points, which in
    + turn happens only if we have not yet matched the entire string. */
    + unsigned best_regs_set = false;
    + const char **best_regstart=NULL, **best_regend=NULL;
    +
    + /* Logically, this is `best_regend[0]'. But we don't want to have to
    + allocate space for that if we're not allocating space for anything
    + else (see below). Also, we never need info about register 0 for
    + any of the other register vectors, and it seems rather a kludge to
    + treat `best_regend' differently than the rest. So we keep track of
    + the end of the best match so far in a separate variable. We
    + initialize this to NULL so that when we backtrack the first time
    + and need to test it, it's not garbage. */
    + const char *match_end = NULL;
    +
    + /* Used when we pop values we don't care about. */
    + const char **reg_dummy=NULL;
    + register_info_type *reg_info_dummy=NULL;
    +
    +#ifdef DEBUG
    + /* Counts the total number of registers pushed. */
    + unsigned num_regs_pushed = 0;
    +#endif
    +
    + DEBUG_PRINT1 ("\n\nEntering re_match_2.\n");
    +
    + INIT_FAIL_STACK ();
    +
    + /* Do not bother to initialize all the register variables if there are
    + no groups in the pattern, as it takes a fair amount of time. If
    + there are groups, we include space for register 0 (the whole
    + pattern), even though we never use it, since it simplifies the
    + array indexing. We should fix this. */
    + if (bufp->re_nsub)
    + {
    + regstart = REGEX_TALLOC (num_regs, const char *);
    + regend = REGEX_TALLOC (num_regs, const char *);
    + old_regstart = REGEX_TALLOC (num_regs, const char *);
    + old_regend = REGEX_TALLOC (num_regs, const char *);
    + best_regstart = REGEX_TALLOC (num_regs, const char *);
    + best_regend = REGEX_TALLOC (num_regs, const char *);
    + reg_info = REGEX_TALLOC (num_regs, register_info_type);
    + reg_dummy = REGEX_TALLOC (num_regs, const char *);
    + reg_info_dummy = REGEX_TALLOC (num_regs, register_info_type);
    +
    + if (!(regstart && regend && old_regstart && old_regend && reg_info
    + && best_regstart && best_regend && reg_dummy && reg_info_dummy))
    + {
    + FREE_VARIABLES ();
    + return -2;
    + }
    + }
    +#ifdef REGEX_MALLOC
    + else
    + {
    + /* We must initialize all our variables to NULL, so that
    + `FREE_VARIABLES' doesn't try to free them. */
    + regstart = regend = old_regstart = old_regend = best_regstart
    + = best_regend = reg_dummy = NULL;
    + reg_info = reg_info_dummy = (register_info_type *) NULL;
    + }
    +#endif /* REGEX_MALLOC */
    +
    + /* The starting position is bogus. */
    + if (pos < 0 || pos > size1 + size2)
    + {
    + FREE_VARIABLES ();
    + return -1;
    + }
    +
    + /* Initialize subexpression text positions to -1 to mark ones that no
    + start_memory/stop_memory has been seen for. Also initialize the
    + register information struct. */
    + for (mcnt = 1; mcnt < num_regs; mcnt++)
    + {
    + regstart[mcnt] = regend[mcnt]
    + = old_regstart[mcnt] = old_regend[mcnt] = REG_UNSET_VALUE;
    +
    + REG_MATCH_NULL_STRING_P (reg_info[mcnt]) = MATCH_NULL_UNSET_VALUE;
    + IS_ACTIVE (reg_info[mcnt]) = 0;
    + MATCHED_SOMETHING (reg_info[mcnt]) = 0;
    + EVER_MATCHED_SOMETHING (reg_info[mcnt]) = 0;
    + }
    +
    + /* We move `string1' into `string2' if the latter's empty -- but not if
    + `string1' is null. */
    + if (size2 == 0 && string1 != NULL)
    + {
    + string2 = string1;
    + size2 = size1;
    + string1 = 0;
    + size1 = 0;
    + }
    + end1 = string1 + size1;
    + end2 = string2 + size2;
    +
    + /* Compute where to stop matching, within the two strings. */
    + if (stop <= size1)
    + {
    + end_match_1 = string1 + stop;
    + end_match_2 = string2;
    + }
    + else
    + {
    + end_match_1 = end1;
    + end_match_2 = string2 + stop - size1;
    + }
    +
    + /* `p' scans through the pattern as `d' scans through the data.
    + `dend' is the end of the input string that `d' points within. `d'
    + is advanced into the following input string whenever necessary, but
    + this happens before fetching; therefore, at the beginning of the
    + loop, `d' can be pointing at the end of a string, but it cannot
    + equal `string2'. */
    + if (size1 > 0 && pos <= size1)
    + {
    + d = string1 + pos;
    + dend = end_match_1;
    + }
    + else
    + {
    + d = string2 + pos - size1;
    + dend = end_match_2;
    + }
    +
    + DEBUG_PRINT1 ("The compiled pattern is: ");
    + DEBUG_PRINT_COMPILED_PATTERN (bufp, p, pend);
    + DEBUG_PRINT1 ("The string to match is: `");
    + DEBUG_PRINT_DOUBLE_STRING (d, string1, size1, string2, size2);
    + DEBUG_PRINT1 ("'\n");
    +
    + /* This loops over pattern commands. It exits by returning from the
    + function if the match is complete, or it drops through if the match
    + fails at this starting point in the input data. */
    + for (;;)
    + {
    + DEBUG_PRINT2 ("\n0x%x: ", p);
    +
    + if (p == pend)
    + { /* End of pattern means we might have succeeded. */
    + DEBUG_PRINT1 ("end of pattern ... ");
    +
    + /* If we haven't matched the entire string, and we want the
    + longest match, try backtracking. */
    + if (d != end_match_2)
    + {
    + DEBUG_PRINT1 ("backtracking.\n");
    +
    + if (!FAIL_STACK_EMPTY ())
    + { /* More failure points to try. */
    + boolean same_str_p = (FIRST_STRING_P (match_end)
    + == MATCHING_IN_FIRST_STRING);
    +
    + /* If exceeds best match so far, save it. */
    + if (!best_regs_set
    + || (same_str_p && d > match_end)
    + || (!same_str_p && !MATCHING_IN_FIRST_STRING))
    + {
    + best_regs_set = true;
    + match_end = d;
    +
    + DEBUG_PRINT1 ("\nSAVING match as best so far.\n");
    +
    + for (mcnt = 1; mcnt < num_regs; mcnt++)
    + {
    + best_regstart[mcnt] = regstart[mcnt];
    + best_regend[mcnt] = regend[mcnt];
    + }
    + }
    + goto fail;
    + }
    +
    + /* If no failure points, don't restore garbage. */
    + else if (best_regs_set)
    + {
    + restore_best_regs:
    + /* Restore best match. It may happen that `dend ==
    + end_match_1' while the restored d is in string2.
    + For example, the pattern `x.*y.*z' against the
    + strings `x-' and `y-z-', if the two strings are
    + not consecutive in memory. */
    + DEBUG_PRINT1 ("Restoring best registers.\n");
    +
    + d = match_end;
    + dend = ((d >= string1 && d <= end1)
    + ? end_match_1 : end_match_2);
    +
    + for (mcnt = 1; mcnt < num_regs; mcnt++)
    + {
    + regstart[mcnt] = best_regstart[mcnt];
    + regend[mcnt] = best_regend[mcnt];
    + }
    + }
    + } /* d != end_match_2 */
    +
    + DEBUG_PRINT1 ("Accepting match.\n");
    +
    + /* If caller wants register contents data back, do it. */
    + if (regs && !bufp->no_sub)
    + {
    + /* Have the register data arrays been allocated? */
    + if (bufp->regs_allocated == REGS_UNALLOCATED)
    + { /* No. So allocate them with malloc. We need one
    + extra element beyond `num_regs' for the `-1' marker
    + GNU code uses. */
    + regs->num_regs = MAX (RE_NREGS, num_regs + 1);
    + regs->start = TALLOC (regs->num_regs, regoff_t);
    + regs->end = TALLOC (regs->num_regs, regoff_t);
    + if (regs->start == NULL || regs->end == NULL)
    + return -2;
    + bufp->regs_allocated = REGS_REALLOCATE;
    + }
    + else if (bufp->regs_allocated == REGS_REALLOCATE)
    + { /* Yes. If we need more elements than were already
    + allocated, reallocate them. If we need fewer, just
    + leave it alone. */
    + if (regs->num_regs < num_regs + 1)
    + {
    + regs->num_regs = num_regs + 1;
    + RETALLOC (regs->start, regs->num_regs, regoff_t);
    + RETALLOC (regs->end, regs->num_regs, regoff_t);
    + if (regs->start == NULL || regs->end == NULL)
    + return -2;
    + }
    + }
    + else
    + assert (bufp->regs_allocated == REGS_FIXED);
    +
    + /* Convert the pointer data in `regstart' and `regend' to
    + indices. Register zero has to be set differently,
    + since we haven't kept track of any info for it. */
    + if (regs->num_regs > 0)
    + {
    + regs->start[0] = pos;
    + regs->end[0] = (MATCHING_IN_FIRST_STRING ? d - string1
    + : d - string2 + size1);
    + }
    +
    + /* Go through the first `min (num_regs, regs->num_regs)'
    + registers, since that is all we initialized. */
    + for (mcnt = 1; mcnt < MIN (num_regs, regs->num_regs); mcnt++)
    + {
    + if (REG_UNSET (regstart[mcnt]) || REG_UNSET (regend[mcnt]))
    + regs->start[mcnt] = regs->end[mcnt] = -1;
    + else
    + {
    + regs->start[mcnt] = POINTER_TO_OFFSET (regstart[mcnt]);
    + regs->end[mcnt] = POINTER_TO_OFFSET (regend[mcnt]);
    + }
    + }
    +
    + /* If the regs structure we return has more elements than
    + were in the pattern, set the extra elements to -1. If
    + we (re)allocated the registers, this is the case,
    + because we always allocate enough to have at least one
    + -1 at the end. */
    + for (mcnt = num_regs; mcnt < regs->num_regs; mcnt++)
    + regs->start[mcnt] = regs->end[mcnt] = -1;
    + } /* regs && !bufp->no_sub */
    +
    + FREE_VARIABLES ();
    + DEBUG_PRINT4 ("%u failure points pushed, %u popped (%u remain).\n",
    + nfailure_points_pushed, nfailure_points_popped,
    + nfailure_points_pushed - nfailure_points_popped);
    + DEBUG_PRINT2 ("%u registers pushed.\n", num_regs_pushed);
    +
    + mcnt = d - pos - (MATCHING_IN_FIRST_STRING
    + ? string1
    + : string2 - size1);
    +
    + DEBUG_PRINT2 ("Returning %d from re_match_2.\n", mcnt);
    +
    + return mcnt;
    + }
    +
    + /* Otherwise match next pattern command. */
    +#ifdef SWITCH_ENUM_BUG
    + switch ((int) ((re_opcode_t) *p++))
    +#else
    + switch ((re_opcode_t) *p++)
    +#endif
    + {
    + /* Ignore these. Used to ignore the n of succeed_n's which
    + currently have n == 0. */
    + case no_op:
    + DEBUG_PRINT1 ("EXECUTING no_op.\n");
    + break;
    +
    +
    + /* Match the next n pattern characters exactly. The following
    + byte in the pattern defines n, and the n bytes after that
    + are the characters to match. */
    + case exactn:
    + mcnt = *p++;
    + DEBUG_PRINT2 ("EXECUTING exactn %d.\n", mcnt);
    +
    + /* This is written out as an if-else so we don't waste time
    + testing `translate' inside the loop. */
    + if (translate)
    + {
    + do
    + {
    + PREFETCH ();
    + if (translate[(unsigned char) *d++] != (char) *p++)
    + goto fail;
    + }
    + while (--mcnt);
    + }
    + else
    + {
    + do
    + {
    + PREFETCH ();
    + if (*d++ != (char) *p++) goto fail;
    + }
    + while (--mcnt);
    + }
    + SET_REGS_MATCHED ();
    + break;
    +
    +
    + /* Match any character except possibly a newline or a null. */
    + case anychar:
    + DEBUG_PRINT1 ("EXECUTING anychar.\n");
    +
    + PREFETCH ();
    +
    + if ((!(bufp->syntax & RE_DOT_NEWLINE) && TRANSLATE (*d) == '\n')
    + || (bufp->syntax & RE_DOT_NOT_NULL && TRANSLATE (*d) == '\000'))
    + goto fail;
    +
    + SET_REGS_MATCHED ();
    + DEBUG_PRINT2 (" Matched `%d'.\n", *d);
    + d++;
    + break;
    +
    +
    + case charset:
    + case charset_not:
    + {
    + register unsigned char c;
    + boolean not = (re_opcode_t) *(p - 1) == charset_not;
    +
    + DEBUG_PRINT2 ("EXECUTING charset%s.\n", not ? "_not" : "");
    +
    + PREFETCH ();
    + c = TRANSLATE (*d); /* The character to match. */
    +
    + /* Cast to `unsigned' instead of `unsigned char' in case the
    + bit list is a full 32 bytes long. */
    + if (c < (unsigned) (*p * BYTEWIDTH)
    + && p[1 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
    + not = !not;
    +
    + p += 1 + *p;
    +
    + if (!not) goto fail;
    +
    + SET_REGS_MATCHED ();
    + d++;
    + break;
    + }
    +
    +
    + /* The beginning of a group is represented by start_memory.
    + The arguments are the register number in the next byte, and the
    + number of groups inner to this one in the next. The text
    + matched within the group is recorded (in the internal
    + registers data structure) under the register number. */
    + case start_memory:
    + DEBUG_PRINT3 ("EXECUTING start_memory %d (%d):\n", *p, p[1]);
    +
    + /* Find out if this group can match the empty string. */
    + p1 = p; /* To send to group_match_null_string_p. */
    +
    + if (REG_MATCH_NULL_STRING_P (reg_info[*p]) == MATCH_NULL_UNSET_VALUE)
    + REG_MATCH_NULL_STRING_P (reg_info[*p])
    + = group_match_null_string_p (&p1, pend, reg_info);
    +
    + /* Save the position in the string where we were the last time
    + we were at this open-group operator in case the group is
    + operated upon by a repetition operator, e.g., with `(a*)*b'
    + against `ab'; then we want to ignore where we are now in
    + the string in case this attempt to match fails. */
    + old_regstart[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
    + ? REG_UNSET (regstart[*p]) ? d : regstart[*p]
    + : regstart[*p];
    + DEBUG_PRINT2 (" old_regstart: %d\n",
    + POINTER_TO_OFFSET (old_regstart[*p]));
    +
    + regstart[*p] = d;
    + DEBUG_PRINT2 (" regstart: %d\n", POINTER_TO_OFFSET (regstart[*p]));
    +
    + IS_ACTIVE (reg_info[*p]) = 1;
    + MATCHED_SOMETHING (reg_info[*p]) = 0;
    +
    + /* This is the new highest active register. */
    + highest_active_reg = *p;
    +
    + /* If nothing was active before, this is the new lowest active
    + register. */
    + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
    + lowest_active_reg = *p;
    +
    + /* Move past the register number and inner group count. */
    + p += 2;
    + break;
    +
    +
    + /* The stop_memory opcode represents the end of a group. Its
    + arguments are the same as start_memory's: the register
    + number, and the number of inner groups. */
    + case stop_memory:
    + DEBUG_PRINT3 ("EXECUTING stop_memory %d (%d):\n", *p, p[1]);
    +
    + /* We need to save the string position the last time we were at
    + this close-group operator in case the group is operated
    + upon by a repetition operator, e.g., with `((a*)*(b*)*)*'
    + against `aba'; then we want to ignore where we are now in
    + the string in case this attempt to match fails. */
    + old_regend[*p] = REG_MATCH_NULL_STRING_P (reg_info[*p])
    + ? REG_UNSET (regend[*p]) ? d : regend[*p]
    + : regend[*p];
    + DEBUG_PRINT2 (" old_regend: %d\n",
    + POINTER_TO_OFFSET (old_regend[*p]));
    +
    + regend[*p] = d;
    + DEBUG_PRINT2 (" regend: %d\n", POINTER_TO_OFFSET (regend[*p]));
    +
    + /* This register isn't active anymore. */
    + IS_ACTIVE (reg_info[*p]) = 0;
    +
    + /* If this was the only register active, nothing is active
    + anymore. */
    + if (lowest_active_reg == highest_active_reg)
    + {
    + lowest_active_reg = NO_LOWEST_ACTIVE_REG;
    + highest_active_reg = NO_HIGHEST_ACTIVE_REG;
    + }
    + else
    + { /* We must scan for the new highest active register, since
    + it isn't necessarily one less than now: consider
    + (a(b)c(d(e)f)g). When group 3 ends, after the f), the
    + new highest active register is 1. */
    + unsigned char r = *p - 1;
    + while (r > 0 && !IS_ACTIVE (reg_info[r]))
    + r--;
    +
    + /* If we end up at register zero, that means that we saved
    + the registers as the result of an `on_failure_jump', not
    + a `start_memory', and we jumped to past the innermost
    + `stop_memory'. For example, in ((.)*) we save
    + registers 1 and 2 as a result of the *, but when we pop
    + back to the second ), we are at the stop_memory 1.
    + Thus, nothing is active. */
    + if (r == 0)
    + {
    + lowest_active_reg = NO_LOWEST_ACTIVE_REG;
    + highest_active_reg = NO_HIGHEST_ACTIVE_REG;
    + }
    + else
    + highest_active_reg = r;
    + }
    +
    + /* If just failed to match something this time around with a
    + group that's operated on by a repetition operator, try to
    + force exit from the ``loop'', and restore the register
    + information for this group that we had before trying this
    + last match. */
    + if ((!MATCHED_SOMETHING (reg_info[*p])
    + || (re_opcode_t) p[-3] == start_memory)
    + && (p + 2) < pend)
    + {
    + boolean is_a_jump_n = false;
    +
    + p1 = p + 2;
    + mcnt = 0;
    + switch ((re_opcode_t) *p1++)
    + {
    + case jump_n:
    + is_a_jump_n = true;
    + case pop_failure_jump:
    + case maybe_pop_jump:
    + case jump:
    + case dummy_failure_jump:
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + if (is_a_jump_n)
    + p1 += 2;
    + break;
    +
    + default:
    + /* do nothing */ ;
    + }
    + p1 += mcnt;
    +
    + /* If the next operation is a jump backwards in the pattern
    + to an on_failure_jump right before the start_memory
    + corresponding to this stop_memory, exit from the loop
    + by forcing a failure after pushing on the stack the
    + on_failure_jump's jump in the pattern, and d. */
    + if (mcnt < 0 && (re_opcode_t) *p1 == on_failure_jump
    + && (re_opcode_t) p1[3] == start_memory && p1[4] == *p)
    + {
    + /* If this group ever matched anything, then restore
    + what its registers were before trying this last
    + failed match, e.g., with `(a*)*b' against `ab' for
    + regstart[1], and, e.g., with `((a*)*(b*)*)*'
    + against `aba' for regend[3].
    +
    + Also restore the registers for inner groups for,
    + e.g., `((a*)(b*))*' against `aba' (register 3 would
    + otherwise get trashed). */
    +
    + if (EVER_MATCHED_SOMETHING (reg_info[*p]))
    + {
    + unsigned r;
    +
    + EVER_MATCHED_SOMETHING (reg_info[*p]) = 0;
    +
    + /* Restore this and inner groups' (if any) registers. */
    + for (r = *p; r < *p + *(p + 1); r++)
    + {
    + regstart[r] = old_regstart[r];
    +
    + /* xx why this test? */
    + if ((int) old_regend[r] >= (int) regstart[r])
    + regend[r] = old_regend[r];
    + }
    + }
    + p1++;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + PUSH_FAILURE_POINT (p1 + mcnt, d, -2);
    +
    + goto fail;
    + }
    + }
    +
    + /* Move past the register number and the inner group count. */
    + p += 2;
    + break;
    +
    +
    + /* \<digit> has been turned into a `duplicate' command which is
    + followed by the numeric value of <digit> as the register number. */
    + case duplicate:
    + {
    + register const char *d2, *dend2;
    + int regno = *p++; /* Get which register to match against. */
    + DEBUG_PRINT2 ("EXECUTING duplicate %d.\n", regno);
    +
    + /* Can't back reference a group which we've never matched. */
    + if (REG_UNSET (regstart[regno]) || REG_UNSET (regend[regno]))
    + goto fail;
    +
    + /* Where in input to try to start matching. */
    + d2 = regstart[regno];
    +
    + /* Where to stop matching; if both the place to start and
    + the place to stop matching are in the same string, then
    + set to the place to stop, otherwise, for now have to use
    + the end of the first string. */
    +
    + dend2 = ((FIRST_STRING_P (regstart[regno])
    + == FIRST_STRING_P (regend[regno]))
    + ? regend[regno] : end_match_1);
    + for (;;)
    + {
    + /* If necessary, advance to next segment in register
    + contents. */
    + while (d2 == dend2)
    + {
    + if (dend2 == end_match_2) break;
    + if (dend2 == regend[regno]) break;
    +
    + /* End of string1 => advance to string2. */
    + d2 = string2;
    + dend2 = regend[regno];
    + }
    + /* At end of register contents => success */
    + if (d2 == dend2) break;
    +
    + /* If necessary, advance to next segment in data. */
    + PREFETCH ();
    +
    + /* How many characters left in this segment to match. */
    + mcnt = dend - d;
    +
    + /* Want how many consecutive characters we can match in
    + one shot, so, if necessary, adjust the count. */
    + if (mcnt > dend2 - d2)
    + mcnt = dend2 - d2;
    +
    + /* Compare that many; failure if mismatch, else move
    + past them. */
    + if (translate
    + ? bcmp_translate (d, d2, mcnt, translate)
    + : bcmp (d, d2, mcnt))
    + goto fail;
    + d += mcnt, d2 += mcnt;
    + }
    + }
    + break;
    +
    +
    + /* begline matches the empty string at the beginning of the string
    + (unless `not_bol' is set in `bufp'), and, if
    + `newline_anchor' is set, after newlines. */
    + case begline:
    + DEBUG_PRINT1 ("EXECUTING begline.\n");
    +
    + if (AT_STRINGS_BEG (d))
    + {
    + if (!bufp->not_bol) break;
    + }
    + else if (d[-1] == '\n' && bufp->newline_anchor)
    + {
    + break;
    + }
    + /* In all other cases, we fail. */
    + goto fail;
    +
    +
    + /* endline is the dual of begline. */
    + case endline:
    + DEBUG_PRINT1 ("EXECUTING endline.\n");
    +
    + if (AT_STRINGS_END (d))
    + {
    + if (!bufp->not_eol) break;
    + }
    +
    + /* We have to ``prefetch'' the next character. */
    + else if ((d == end1 ? *string2 : *d) == '\n'
    + && bufp->newline_anchor)
    + {
    + break;
    + }
    + goto fail;
    +
    +
    + /* Match at the very beginning of the data. */
    + case begbuf:
    + DEBUG_PRINT1 ("EXECUTING begbuf.\n");
    + if (AT_STRINGS_BEG (d))
    + break;
    + goto fail;
    +
    +
    + /* Match at the very end of the data. */
    + case endbuf:
    + DEBUG_PRINT1 ("EXECUTING endbuf.\n");
    + if (AT_STRINGS_END (d))
    + break;
    + goto fail;
    +
    +
    + /* on_failure_keep_string_jump is used to optimize `.*\n'. It
    + pushes NULL as the value for the string on the stack. Then
    + `pop_failure_point' will keep the current value for the
    + string, instead of restoring it. To see why, consider
    + matching `foo\nbar' against `.*\n'. The .* matches the foo;
    + then the . fails against the \n. But the next thing we want
    + to do is match the \n against the \n; if we restored the
    + string value, we would be back at the foo.
    +
    + Because this is used only in specific cases, we don't need to
    + check all the things that `on_failure_jump' does, to make
    + sure the right things get saved on the stack. Hence we don't
    + share its code. The only reason to push anything on the
    + stack at all is that otherwise we would have to change
    + `anychar's code to do something besides goto fail in this
    + case; that seems worse than this. */
    + case on_failure_keep_string_jump:
    + DEBUG_PRINT1 ("EXECUTING on_failure_keep_string_jump");
    +
    + EXTRACT_NUMBER_AND_INCR (mcnt, p);
    + DEBUG_PRINT3 (" %d (to 0x%x):\n", mcnt, p + mcnt);
    +
    + PUSH_FAILURE_POINT (p + mcnt, NULL, -2);
    + break;
    +
    +
    + /* Uses of on_failure_jump:
    +
    + Each alternative starts with an on_failure_jump that points
    + to the beginning of the next alternative. Each alternative
    + except the last ends with a jump that in effect jumps past
    + the rest of the alternatives. (They really jump to the
    + ending jump of the following alternative, because tensioning
    + these jumps is a hassle.)
    +
    + Repeats start with an on_failure_jump that points past both
    + the repetition text and either the following jump or
    + pop_failure_jump back to this on_failure_jump. */
    + case on_failure_jump:
    + on_failure:
    + DEBUG_PRINT1 ("EXECUTING on_failure_jump");
    +
    + EXTRACT_NUMBER_AND_INCR (mcnt, p);
    + DEBUG_PRINT3 (" %d (to 0x%x)", mcnt, p + mcnt);
    +
    + /* If this on_failure_jump comes right before a group (i.e.,
    + the original * applied to a group), save the information
    + for that group and all inner ones, so that if we fail back
    + to this point, the group's information will be correct.
    + For example, in \(a*\)*\1, we need the preceding group,
    + and in \(\(a*\)b*\)\2, we need the inner group. */
    +
    + /* We can't use `p' to check ahead because we push
    + a failure point to `p + mcnt' after we do this. */
    + p1 = p;
    +
    + /* We need to skip no_op's before we look for the
    + start_memory in case this on_failure_jump is happening as
    + the result of a completed succeed_n, as in \(a\)\{1,3\}b\1
    + against aba. */
    + while (p1 < pend && (re_opcode_t) *p1 == no_op)
    + p1++;
    +
    + if (p1 < pend && (re_opcode_t) *p1 == start_memory)
    + {
    + /* We have a new highest active register now. This will
    + get reset at the start_memory we are about to get to,
    + but we will have saved all the registers relevant to
    + this repetition op, as described above. */
    + highest_active_reg = *(p1 + 1) + *(p1 + 2);
    + if (lowest_active_reg == NO_LOWEST_ACTIVE_REG)
    + lowest_active_reg = *(p1 + 1);
    + }
    +
    + DEBUG_PRINT1 (":\n");
    + PUSH_FAILURE_POINT (p + mcnt, d, -2);
    + break;
    +
    +
    + /* A smart repeat ends with `maybe_pop_jump'.
    + We change it to either `pop_failure_jump' or `jump'. */
    + case maybe_pop_jump:
    + EXTRACT_NUMBER_AND_INCR (mcnt, p);
    + DEBUG_PRINT2 ("EXECUTING maybe_pop_jump %d.\n", mcnt);
    + {
    + register unsigned char *p2 = p;
    +
    + /* Compare the beginning of the repeat with what in the
    + pattern follows its end. If we can establish that there
    + is nothing that they would both match, i.e., that we
    + would have to backtrack because of (as in, e.g., `a*a')
    + then we can change to pop_failure_jump, because we'll
    + never have to backtrack.
    +
    + This is not true in the case of alternatives: in
    + `(a|ab)*' we do need to backtrack to the `ab' alternative
    + (e.g., if the string was `ab'). But instead of trying to
    + detect that here, the alternative has put on a dummy
    + failure point which is what we will end up popping. */
    +
    + /* Skip over open/close-group commands. */
    + while (p2 + 2 < pend
    + && ((re_opcode_t) *p2 == stop_memory
    + || (re_opcode_t) *p2 == start_memory))
    + p2 += 3; /* Skip over args, too. */
    +
    + /* If we're at the end of the pattern, we can change. */
    + if (p2 == pend)
    + {
    + /* Consider what happens when matching ":\(.*\)"
    + against ":/". I don't really understand this code
    + yet. */
    + p[-3] = (unsigned char) pop_failure_jump;
    + DEBUG_PRINT1
    + (" End of pattern: change to `pop_failure_jump'.\n");
    + }
    +
    + else if ((re_opcode_t) *p2 == exactn
    + || (bufp->newline_anchor && (re_opcode_t) *p2 == endline))
    + {
    + register unsigned char c
    + = *p2 == (unsigned char) endline ? '\n' : p2[2];
    + p1 = p + mcnt;
    +
    + /* p1[0] ... p1[2] are the `on_failure_jump' corresponding
    + to the `maybe_finalize_jump' of this case. Examine what
    + follows. */
    + if ((re_opcode_t) p1[3] == exactn && p1[5] != c)
    + {
    + p[-3] = (unsigned char) pop_failure_jump;
    + DEBUG_PRINT3 (" %c != %c => pop_failure_jump.\n",
    + c, p1[5]);
    + }
    +
    + else if ((re_opcode_t) p1[3] == charset
    + || (re_opcode_t) p1[3] == charset_not)
    + {
    + int not = (re_opcode_t) p1[3] == charset_not;
    +
    + if (c < (unsigned char) (p1[4] * BYTEWIDTH)
    + && p1[5 + c / BYTEWIDTH] & (1 << (c % BYTEWIDTH)))
    + not = !not;
    +
    + /* `not' is equal to 1 if c would match, which means
    + that we can't change to pop_failure_jump. */
    + if (!not)
    + {
    + p[-3] = (unsigned char) pop_failure_jump;
    + DEBUG_PRINT1 (" No match => pop_failure_jump.\n");
    + }
    + }
    + }
    + }
    + p -= 2; /* Point at relative address again. */
    + if ((re_opcode_t) p[-1] != pop_failure_jump)
    + {
    + p[-1] = (unsigned char) jump;
    + DEBUG_PRINT1 (" Match => jump.\n");
    + goto unconditional_jump;
    + }
    + /* Note fall through. */
    +
    +
    + /* The end of a simple repeat has a pop_failure_jump back to
    + its matching on_failure_jump, where the latter will push a
    + failure point. The pop_failure_jump takes off failure
    + points put on by this pop_failure_jump's matching
    + on_failure_jump; we got through the pattern to here from the
    + matching on_failure_jump, so didn't fail. */
    + case pop_failure_jump:
    + {
    + /* We need to pass separate storage for the lowest and
    + highest registers, even though we don't care about the
    + actual values. Otherwise, we will restore only one
    + register from the stack, since lowest will == highest in
    + `pop_failure_point'. */
    + unsigned dummy_low_reg, dummy_high_reg;
    + unsigned char *pdummy;
    + const char *sdummy;
    +
    + DEBUG_PRINT1 ("EXECUTING pop_failure_jump.\n");
    + POP_FAILURE_POINT (sdummy, pdummy,
    + dummy_low_reg, dummy_high_reg,
    + reg_dummy, reg_dummy, reg_info_dummy);
    + }
    + /* Note fall through. */
    +
    +
    + /* Unconditionally jump (without popping any failure points). */
    + case jump:
    + unconditional_jump:
    + EXTRACT_NUMBER_AND_INCR (mcnt, p); /* Get the amount to jump. */
    + DEBUG_PRINT2 ("EXECUTING jump %d ", mcnt);
    + p += mcnt; /* Do the jump. */
    + DEBUG_PRINT2 ("(to 0x%x).\n", p);
    + break;
    +
    +
    + /* We need this opcode so we can detect where alternatives end
    + in `group_match_null_string_p' et al. */
    + case jump_past_alt:
    + DEBUG_PRINT1 ("EXECUTING jump_past_alt.\n");
    + goto unconditional_jump;
    +
    +
    + /* Normally, the on_failure_jump pushes a failure point, which
    + then gets popped at pop_failure_jump. We will end up at
    + pop_failure_jump, also, and with a pattern of, say, `a+', we
    + are skipping over the on_failure_jump, so we have to push
    + something meaningless for pop_failure_jump to pop. */
    + case dummy_failure_jump:
    + DEBUG_PRINT1 ("EXECUTING dummy_failure_jump.\n");
    + /* It doesn't matter what we push for the string here. What
    + the code at `fail' tests is the value for the pattern. */
    + PUSH_FAILURE_POINT (0, 0, -2);
    + goto unconditional_jump;
    +
    +
    + /* At the end of an alternative, we need to push a dummy failure
    + point in case we are followed by a `pop_failure_jump', because
    + we don't want the failure point for the alternative to be
    + popped. For example, matching `(a|ab)*' against `aab'
    + requires that we match the `ab' alternative. */
    + case push_dummy_failure:
    + DEBUG_PRINT1 ("EXECUTING push_dummy_failure.\n");
    + /* See comments just above at `dummy_failure_jump' about the
    + two zeroes. */
    + PUSH_FAILURE_POINT (0, 0, -2);
    + break;
    +
    + /* Have to succeed matching what follows at least n times.
    + After that, handle like `on_failure_jump'. */
    + case succeed_n:
    + EXTRACT_NUMBER (mcnt, p + 2);
    + DEBUG_PRINT2 ("EXECUTING succeed_n %d.\n", mcnt);
    +
    + assert (mcnt >= 0);
    + /* Originally, this is how many times we HAVE to succeed. */
    + if (mcnt > 0)
    + {
    + mcnt--;
    + p += 2;
    + STORE_NUMBER_AND_INCR (p, mcnt);
    + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p, mcnt);
    + }
    + else if (mcnt == 0)
    + {
    + DEBUG_PRINT2 (" Setting two bytes from 0x%x to no_op.\n", p+2);
    + p[2] = (unsigned char) no_op;
    + p[3] = (unsigned char) no_op;
    + goto on_failure;
    + }
    + break;
    +
    + case jump_n:
    + EXTRACT_NUMBER (mcnt, p + 2);
    + DEBUG_PRINT2 ("EXECUTING jump_n %d.\n", mcnt);
    +
    + /* Originally, this is how many times we CAN jump. */
    + if (mcnt)
    + {
    + mcnt--;
    + STORE_NUMBER (p + 2, mcnt);
    + goto unconditional_jump;
    + }
    + /* If don't have to jump any more, skip over the rest of command. */
    + else
    + p += 4;
    + break;
    +
    + case set_number_at:
    + {
    + DEBUG_PRINT1 ("EXECUTING set_number_at.\n");
    +
    + EXTRACT_NUMBER_AND_INCR (mcnt, p);
    + p1 = p + mcnt;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p);
    + DEBUG_PRINT3 (" Setting 0x%x to %d.\n", p1, mcnt);
    + STORE_NUMBER (p1, mcnt);
    + break;
    + }
    +
    + case wordbound:
    + DEBUG_PRINT1 ("EXECUTING wordbound.\n");
    + if (AT_WORD_BOUNDARY (d))
    + break;
    + goto fail;
    +
    + case notwordbound:
    + DEBUG_PRINT1 ("EXECUTING notwordbound.\n");
    + if (AT_WORD_BOUNDARY (d))
    + goto fail;
    + break;
    +
    + case wordbeg:
    + DEBUG_PRINT1 ("EXECUTING wordbeg.\n");
    + if (WORDCHAR_P (d) && (AT_STRINGS_BEG (d) || !WORDCHAR_P (d - 1)))
    + break;
    + goto fail;
    +
    + case wordend:
    + DEBUG_PRINT1 ("EXECUTING wordend.\n");
    + if (!AT_STRINGS_BEG (d) && WORDCHAR_P (d - 1)
    + && (!WORDCHAR_P (d) || AT_STRINGS_END (d)))
    + break;
    + goto fail;
    +
    +#ifdef emacs
    +#ifdef emacs19
    + case before_dot:
    + DEBUG_PRINT1 ("EXECUTING before_dot.\n");
    + if (PTR_CHAR_POS ((unsigned char *) d) >= point)
    + goto fail;
    + break;
    +
    + case at_dot:
    + DEBUG_PRINT1 ("EXECUTING at_dot.\n");
    + if (PTR_CHAR_POS ((unsigned char *) d) != point)
    + goto fail;
    + break;
    +
    + case after_dot:
    + DEBUG_PRINT1 ("EXECUTING after_dot.\n");
    + if (PTR_CHAR_POS ((unsigned char *) d) <= point)
    + goto fail;
    + break;
    +#else /* not emacs19 */
    + case at_dot:
    + DEBUG_PRINT1 ("EXECUTING at_dot.\n");
    + if (PTR_CHAR_POS ((unsigned char *) d) + 1 != point)
    + goto fail;
    + break;
    +#endif /* not emacs19 */
    +
    + case syntaxspec:
    + DEBUG_PRINT2 ("EXECUTING syntaxspec %d.\n", mcnt);
    + mcnt = *p++;
    + goto matchsyntax;
    +
    + case wordchar:
    + DEBUG_PRINT1 ("EXECUTING Emacs wordchar.\n");
    + mcnt = (int) Sword;
    + matchsyntax:
    + PREFETCH ();
    + if (SYNTAX (*d++) != (enum syntaxcode) mcnt)
    + goto fail;
    + SET_REGS_MATCHED ();
    + break;
    +
    + case notsyntaxspec:
    + DEBUG_PRINT2 ("EXECUTING notsyntaxspec %d.\n", mcnt);
    + mcnt = *p++;
    + goto matchnotsyntax;
    +
    + case notwordchar:
    + DEBUG_PRINT1 ("EXECUTING Emacs notwordchar.\n");
    + mcnt = (int) Sword;
    + matchnotsyntax:
    + PREFETCH ();
    + if (SYNTAX (*d++) == (enum syntaxcode) mcnt)
    + goto fail;
    + SET_REGS_MATCHED ();
    + break;
    +
    +#else /* not emacs */
    + case wordchar:
    + DEBUG_PRINT1 ("EXECUTING non-Emacs wordchar.\n");
    + PREFETCH ();
    + if (!WORDCHAR_P (d))
    + goto fail;
    + SET_REGS_MATCHED ();
    + d++;
    + break;
    +
    + case notwordchar:
    + DEBUG_PRINT1 ("EXECUTING non-Emacs notwordchar.\n");
    + PREFETCH ();
    + if (WORDCHAR_P (d))
    + goto fail;
    + SET_REGS_MATCHED ();
    + d++;
    + break;
    +#endif /* not emacs */
    +
    + default:
    + abort ();
    + }
    + continue; /* Successfully executed one pattern command; keep going. */
    +
    +
    + /* We goto here if a matching operation fails. */
    + fail:
    + if (!FAIL_STACK_EMPTY ())
    + { /* A restart point is known. Restore to that state. */
    + DEBUG_PRINT1 ("\nFAIL:\n");
    + POP_FAILURE_POINT (d, p,
    + lowest_active_reg, highest_active_reg,
    + regstart, regend, reg_info);
    +
    + /* If this failure point is a dummy, try the next one. */
    + if (!p)
    + goto fail;
    +
    + /* If we failed to the end of the pattern, don't examine *p. */
    + assert (p <= pend);
    + if (p < pend)
    + {
    + boolean is_a_jump_n = false;
    +
    + /* If failed to a backwards jump that's part of a repetition
    + loop, need to pop this failure point and use the next one. */
    + switch ((re_opcode_t) *p)
    + {
    + case jump_n:
    + is_a_jump_n = true;
    + case maybe_pop_jump:
    + case pop_failure_jump:
    + case jump:
    + p1 = p + 1;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + p1 += mcnt;
    +
    + if ((is_a_jump_n && (re_opcode_t) *p1 == succeed_n)
    + || (!is_a_jump_n
    + && (re_opcode_t) *p1 == on_failure_jump))
    + goto fail;
    + break;
    + default:
    + /* do nothing */ ;
    + }
    + }
    +
    + if (d >= string1 && d <= end1)
    + dend = end_match_1;
    + }
    + else
    + break; /* Matching at this starting point really fails. */
    + } /* for (;;) */
    +
    + if (best_regs_set)
    + goto restore_best_regs;
    +
    + FREE_VARIABLES ();
    +
    + return -1; /* Failure to match. */
    +} /* re_match_2 */
    +
    +/* Subroutine definitions for re_match_2. */
    +
    +
    +/* We are passed P pointing to a register number after a start_memory.
    +
    + Return true if the pattern up to the corresponding stop_memory can
    + match the empty string, and false otherwise.
    +
    + If we find the matching stop_memory, sets P to point to one past its number.
    + Otherwise, sets P to an undefined byte less than or equal to END.
    +
    + We don't handle duplicates properly (yet). */
    +
    +static boolean
    +group_match_null_string_p (p, end, reg_info)
    + unsigned char **p, *end;
    + register_info_type *reg_info;
    +{
    + int mcnt;
    + /* Point to after the args to the start_memory. */
    + unsigned char *p1 = *p + 2;
    +
    + while (p1 < end)
    + {
    + /* Skip over opcodes that can match nothing, and return true or
    + false, as appropriate, when we get to one that can't, or to the
    + matching stop_memory. */
    +
    + switch ((re_opcode_t) *p1)
    + {
    + /* Could be either a loop or a series of alternatives. */
    + case on_failure_jump:
    + p1++;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    +
    + /* If the next operation is not a jump backwards in the
    + pattern. */
    +
    + if (mcnt >= 0)
    + {
    + /* Go through the on_failure_jumps of the alternatives,
    + seeing if any of the alternatives cannot match nothing.
    + The last alternative starts with only a jump,
    + whereas the rest start with on_failure_jump and end
    + with a jump, e.g., here is the pattern for `a|b|c':
    +
    + /on_failure_jump/0/6/exactn/1/a/jump_past_alt/0/6
    + /on_failure_jump/0/6/exactn/1/b/jump_past_alt/0/3
    + /exactn/1/c
    +
    + So, we have to first go through the first (n-1)
    + alternatives and then deal with the last one separately. */
    +
    +
    + /* Deal with the first (n-1) alternatives, which start
    + with an on_failure_jump (see above) that jumps to right
    + past a jump_past_alt. */
    +
    + while ((re_opcode_t) p1[mcnt-3] == jump_past_alt)
    + {
    + /* `mcnt' holds how many bytes long the alternative
    + is, including the ending `jump_past_alt' and
    + its number. */
    +
    + if (!alt_match_null_string_p (p1, p1 + mcnt - 3,
    + reg_info))
    + return false;
    +
    + /* Move to right after this alternative, including the
    + jump_past_alt. */
    + p1 += mcnt;
    +
    + /* Break if it's the beginning of an n-th alternative
    + that doesn't begin with an on_failure_jump. */
    + if ((re_opcode_t) *p1 != on_failure_jump)
    + break;
    +
    + /* Still have to check that it's not an n-th
    + alternative that starts with an on_failure_jump. */
    + p1++;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + if ((re_opcode_t) p1[mcnt-3] != jump_past_alt)
    + {
    + /* Get to the beginning of the n-th alternative. */
    + p1 -= 3;
    + break;
    + }
    + }
    +
    + /* Deal with the last alternative: go back and get number
    + of the `jump_past_alt' just before it. `mcnt' contains
    + the length of the alternative. */
    + EXTRACT_NUMBER (mcnt, p1 - 2);
    +
    + if (!alt_match_null_string_p (p1, p1 + mcnt, reg_info))
    + return false;
    +
    + p1 += mcnt; /* Get past the n-th alternative. */
    + } /* if mcnt > 0 */
    + break;
    +
    +
    + case stop_memory:
    + assert (p1[1] == **p);
    + *p = p1 + 2;
    + return true;
    +
    +
    + default:
    + if (!common_op_match_null_string_p (&p1, end, reg_info))
    + return false;
    + }
    + } /* while p1 < end */
    +
    + return false;
    +} /* group_match_null_string_p */
    +
    +
    +/* Similar to group_match_null_string_p, but doesn't deal with alternatives:
    + It expects P to be the first byte of a single alternative and END one
    + byte past the last. The alternative can contain groups. */
    +
    +static boolean
    +alt_match_null_string_p (p, end, reg_info)
    + unsigned char *p, *end;
    + register_info_type *reg_info;
    +{
    + int mcnt;
    + unsigned char *p1 = p;
    +
    + while (p1 < end)
    + {
    + /* Skip over opcodes that can match nothing, and break when we get
    + to one that can't. */
    +
    + switch ((re_opcode_t) *p1)
    + {
    + /* It's a loop. */
    + case on_failure_jump:
    + p1++;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + p1 += mcnt;
    + break;
    +
    + default:
    + if (!common_op_match_null_string_p (&p1, end, reg_info))
    + return false;
    + }
    + } /* while p1 < end */
    +
    + return true;
    +} /* alt_match_null_string_p */
    +
    +
    +/* Deals with the ops common to group_match_null_string_p and
    + alt_match_null_string_p.
    +
    + Sets P to one after the op and its arguments, if any. */
    +
    +static boolean
    +common_op_match_null_string_p (p, end, reg_info)
    + unsigned char **p, *end;
    + register_info_type *reg_info;
    +{
    + int mcnt;
    + boolean ret;
    + int reg_no;
    + unsigned char *p1 = *p;
    +
    + switch ((re_opcode_t) *p1++)
    + {
    + case no_op:
    + case begline:
    + case endline:
    + case begbuf:
    + case endbuf:
    + case wordbeg:
    + case wordend:
    + case wordbound:
    + case notwordbound:
    +#ifdef emacs
    + case before_dot:
    + case at_dot:
    + case after_dot:
    +#endif
    + break;
    +
    + case start_memory:
    + reg_no = *p1;
    + assert (reg_no > 0 && reg_no <= MAX_REGNUM);
    + ret = group_match_null_string_p (&p1, end, reg_info);
    +
    + /* Have to set this here in case we're checking a group which
    + contains a group and a back reference to it. */
    +
    + if (REG_MATCH_NULL_STRING_P (reg_info[reg_no]) == MATCH_NULL_UNSET_VALUE)
    + REG_MATCH_NULL_STRING_P (reg_info[reg_no]) = ret;
    +
    + if (!ret)
    + return false;
    + break;
    +
    + /* If this is an optimized succeed_n for zero times, make the jump. */
    + case jump:
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + if (mcnt >= 0)
    + p1 += mcnt;
    + else
    + return false;
    + break;
    +
    + case succeed_n:
    + /* Get to the number of times to succeed. */
    + p1 += 2;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    +
    + if (mcnt == 0)
    + {
    + p1 -= 4;
    + EXTRACT_NUMBER_AND_INCR (mcnt, p1);
    + p1 += mcnt;
    + }
    + else
    + return false;
    + break;
    +
    + case duplicate:
    + if (!REG_MATCH_NULL_STRING_P (reg_info[*p1]))
    + return false;
    + break;
    +
    + case set_number_at:
    + p1 += 4;
    +
    + default:
    + /* All other opcodes mean we cannot match the empty string. */
    + return false;
    + }
    +
    + *p = p1;
    + return true;
    +} /* common_op_match_null_string_p */
    +
    +
    +/* Return zero if TRANSLATE[S1] and TRANSLATE[S2] are identical for LEN
    + bytes; nonzero otherwise. */
    +
    +static int
    +bcmp_translate (s1, s2, len, translate)
    + unsigned char *s1, *s2;
    + register int len;
    + char *translate;
    +{
    + register unsigned char *p1 = s1, *p2 = s2;
    + while (len)
    + {
    + if (translate[*p1++] != translate[*p2++]) return 1;
    + len--;
    + }
    + return 0;
    +}
    +
    +/* Entry points for GNU code. */
    +
    +/* re_compile_pattern is the GNU regular expression compiler: it
    + compiles PATTERN (of length SIZE) and puts the result in BUFP.
    + Returns 0 if the pattern was valid, otherwise an error string.
    +
    + Assumes the `allocated' (and perhaps `buffer') and `translate' fields
    + are set in BUFP on entry.
    +
    + We call regex_compile to do the actual compilation. */
    +
    +const char *
    +re_compile_pattern (pattern, length, bufp)
    + const char *pattern;
    + int length;
    + struct re_pattern_buffer *bufp;
    +{
    + reg_errcode_t ret;
    +
    + /* GNU code is written to assume at least RE_NREGS registers will be set
    + (and at least one extra will be -1). */
    + bufp->regs_allocated = REGS_UNALLOCATED;
    +
    + /* And GNU code determines whether or not to get register information
    + by passing null for the REGS argument to re_match, etc., not by
    + setting no_sub. */
    + bufp->no_sub = 0;
    +
    + /* Match anchors at newline. */
    + bufp->newline_anchor = 1;
    +
    + ret = regex_compile (pattern, length, re_syntax_options, bufp);
    +
    + return re_error_msg[(int) ret];
    +}
    +
    +/* Entry points compatible with 4.2 BSD regex library. We don't define
    + them if this is an Emacs or POSIX compilation. */
    +
    +#if !defined (emacs) && !defined (_POSIX_SOURCE)
    +
    +/* BSD has one and only one pattern buffer. */
    +static struct re_pattern_buffer re_comp_buf;
    +
    +char *
    +re_comp (s)
    + const char *s;
    +{
    + reg_errcode_t ret;
    +
    + if (!s)
    + {
    + if (!re_comp_buf.buffer)
    + return "No previous regular expression";
    + return 0;
    + }
    +
    + if (!re_comp_buf.buffer)
    + {
    + re_comp_buf.buffer = (unsigned char *) malloc (200);
    + if (re_comp_buf.buffer == NULL)
    + return "Memory exhausted";
    + re_comp_buf.allocated = 200;
    +
    + re_comp_buf.fastmap = (char *) malloc (1 << BYTEWIDTH);
    + if (re_comp_buf.fastmap == NULL)
    + return "Memory exhausted";
    + }
    +
    + /* Since `re_exec' always passes NULL for the `regs' argument, we
    + don't need to initialize the pattern buffer fields which affect it. */
    +
    + /* Match anchors at newlines. */
    + re_comp_buf.newline_anchor = 1;
    +
    + ret = regex_compile (s, strlen (s), re_syntax_options, &re_comp_buf);
    +
    + /* Yes, we're discarding `const' here. */
    + return (char *) re_error_msg[(int) ret];
    +}
    +
    +
    +int
    +re_exec (s)
    + const char *s;
    +{
    + const int len = strlen (s);
    + return
    + 0 <= re_search (&re_comp_buf, s, len, 0, len, (struct re_registers *) 0);
    +}
    +#endif /* not emacs and not _POSIX_SOURCE */
    +
    +/* POSIX.2 functions. Don't define these for Emacs. */
    +
    +#ifndef emacs
    +
    +/* regcomp takes a regular expression as a string and compiles it.
    +
    + PREG is a regex_t *. We do not expect any fields to be initialized,
    + since POSIX says we shouldn't. Thus, we set
    +
    + `buffer' to the compiled pattern;
    + `used' to the length of the compiled pattern;
    + `syntax' to RE_SYNTAX_POSIX_EXTENDED if the
    + REG_EXTENDED bit in CFLAGS is set; otherwise, to
    + RE_SYNTAX_POSIX_BASIC;
    + `newline_anchor' to REG_NEWLINE being set in CFLAGS;
    + `fastmap' and `fastmap_accurate' to zero;
    + `re_nsub' to the number of subexpressions in PATTERN.
    +
    + PATTERN is the address of the pattern string.
    +
    + CFLAGS is a series of bits which affect compilation.
    +
    + If REG_EXTENDED is set, we use POSIX extended syntax; otherwise, we
    + use POSIX basic syntax.
    +
    + If REG_NEWLINE is set, then . and [^...] don't match newline.
    + Also, regexec will try a match beginning after every newline.
    +
    + If REG_ICASE is set, then we considers upper- and lowercase
    + versions of letters to be equivalent when matching.
    +
    + If REG_NOSUB is set, then when PREG is passed to regexec, that
    + routine will report only success or failure, and nothing about the
    + registers.
    +
    + It returns 0 if it succeeds, nonzero if it doesn't. (See regex.h for
    + the return codes and their meanings.) */
    +
    +int
    +regcomp (preg, pattern, cflags)
    + regex_t *preg;
    + const char *pattern;
    + int cflags;
    +{
    + reg_errcode_t ret;
    + unsigned syntax
    + = (cflags & REG_EXTENDED) ?
    + RE_SYNTAX_POSIX_EXTENDED : RE_SYNTAX_POSIX_BASIC;
    +
    + /* regex_compile will allocate the space for the compiled pattern. */
    + preg->buffer = 0;
    + preg->allocated = 0;
    +
    + /* Don't bother to use a fastmap when searching. This simplifies the
    + REG_NEWLINE case: if we used a fastmap, we'd have to put all the
    + characters after newlines into the fastmap. This way, we just try
    + every character. */
    + preg->fastmap = 0;
    +
    + if (cflags & REG_ICASE)
    + {
    + unsigned i;
    +
    + preg->translate = (char *) malloc (CHAR_SET_SIZE);
    + if (preg->translate == NULL)
    + return (int) REG_ESPACE;
    +
    + /* Map uppercase characters to corresponding lowercase ones. */
    + for (i = 0; i < CHAR_SET_SIZE; i++)
    + preg->translate[i] = ISUPPER (i) ? tolower (i) : i;
    + }
    + else
    + preg->translate = NULL;
    +
    + /* If REG_NEWLINE is set, newlines are treated differently. */
    + if (cflags & REG_NEWLINE)
    + { /* REG_NEWLINE implies neither . nor [^...] match newline. */
    + syntax &= ~RE_DOT_NEWLINE;
    + syntax |= RE_HAT_LISTS_NOT_NEWLINE;
    + /* It also changes the matching behavior. */
    + preg->newline_anchor = 1;
    + }
    + else
    + preg->newline_anchor = 0;
    +
    + preg->no_sub = !!(cflags & REG_NOSUB);
    +
    + /* POSIX says a null character in the pattern terminates it, so we
    + can use strlen here in compiling the pattern. */
    + ret = regex_compile (pattern, strlen (pattern), syntax, preg);
    +
    + /* POSIX doesn't distinguish between an unmatched open-group and an
    + unmatched close-group: both are REG_EPAREN. */
    + if (ret == REG_ERPAREN) ret = REG_EPAREN;
    +
    + return (int) ret;
    +}
    +
    +
    +/* regexec searches for a given pattern, specified by PREG, in the
    + string STRING.
    +
    + If NMATCH is zero or REG_NOSUB was set in the cflags argument to
    + `regcomp', we ignore PMATCH. Otherwise, we assume PMATCH has at
    + least NMATCH elements, and we set them to the offsets of the
    + corresponding matched substrings.
    +
    + EFLAGS specifies `execution flags' which affect matching: if
    + REG_NOTBOL is set, then ^ does not match at the beginning of the
    + string; if REG_NOTEOL is set, then $ does not match at the end.
    +
    + We return 0 if we find a match and REG_NOMATCH if not. */
    +
    +int
    +regexec (preg, string, nmatch, pmatch, eflags)
    + const regex_t *preg;
    + const char *string;
    + size_t nmatch;
    + regmatch_t pmatch[];
    + int eflags;
    +{
    + int ret;
    + struct re_registers regs;
    + regex_t private_preg;
    + int len = strlen (string);
    + boolean want_reg_info = !preg->no_sub && nmatch > 0;
    +
    + private_preg = *preg;
    +
    + private_preg.not_bol = !!(eflags & REG_NOTBOL);
    + private_preg.not_eol = !!(eflags & REG_NOTEOL);
    +
    + /* The user has told us exactly how many registers to return
    + information about, via `nmatch'. We have to pass that on to the
    + matching routines. */
    + private_preg.regs_allocated = REGS_FIXED;
    +
    + if (want_reg_info)
    + {
    + regs.num_regs = nmatch;
    + regs.start = TALLOC (nmatch, regoff_t);
    + regs.end = TALLOC (nmatch, regoff_t);
    + if (regs.start == NULL || regs.end == NULL)
    + return (int) REG_NOMATCH;
    + }
    +
    + /* Perform the searching operation. */
    + ret = re_search (&private_preg, string, len,
    + /* start: */ 0, /* range: */ len,
    + want_reg_info ? &regs : (struct re_registers *) 0);
    +
    + /* Copy the register information to the POSIX structure. */
    + if (want_reg_info)
    + {
    + if (ret >= 0)
    + {
    + unsigned r;
    +
    + for (r = 0; r < nmatch; r++)
    + {
    + pmatch[r].rm_so = regs.start[r];
    + pmatch[r].rm_eo = regs.end[r];
    + }
    + }
    +
    + /* If we needed the temporary register info, free the space now. */
    + free (regs.start);
    + free (regs.end);
    + }
    +
    + /* We want zero return to mean success, unlike `re_search'. */
    + return ret >= 0 ? (int) REG_NOERROR : (int) REG_NOMATCH;
    +}
    +
    +
    +/* Returns a message corresponding to an error code, ERRCODE, returned
    + from either regcomp or regexec. We don't use PREG here. */
    +
    +size_t
    +regerror (errcode, preg, errbuf, errbuf_size)
    + int errcode;
    + const regex_t *preg;
    + char *errbuf;
    + size_t errbuf_size;
    +{
    + const char *msg;
    + size_t msg_size;
    +
    + if (errcode < 0
    + || errcode >= (sizeof (re_error_msg) / sizeof (re_error_msg[0])))
    + /* Only error codes returned by the rest of the code should be passed
    + to this routine. If we are given anything else, or if other regex
    + code generates an invalid error code, then the program has a bug.
    + Dump core so we can fix it. */
    + abort ();
    +
    + msg = re_error_msg[errcode];
    +
    + /* POSIX doesn't require that we do anything in this case, but why
    + not be nice. */
    + if (! msg)
    + msg = "Success";
    +
    + msg_size = strlen (msg) + 1; /* Includes the null. */
    +
    + if (errbuf_size != 0)
    + {
    + if (msg_size > errbuf_size)
    + {
    + strncpy (errbuf, msg, errbuf_size - 1);
    + errbuf[errbuf_size - 1] = 0;
    + }
    + else
    + strcpy (errbuf, msg);
    + }
    +
    + return msg_size;
    +}
    +
    +
    +/* Free dynamically allocated space used by PREG. */
    +
    +void
    +regfree (preg)
    + regex_t *preg;
    +{
    + if (preg->buffer != NULL)
    + free (preg->buffer);
    + preg->buffer = NULL;
    +
    + preg->allocated = 0;
    + preg->used = 0;
    +
    + if (preg->fastmap != NULL)
    + free (preg->fastmap);
    + preg->fastmap = NULL;
    + preg->fastmap_accurate = 0;
    +
    + if (preg->translate != NULL)
    + free (preg->translate);
    + preg->translate = NULL;
    +}
    +
    +#endif /* not emacs */
    +
    +/*
    +Local variables:
    +make-backup-files: t
    +version-control: t
    +trim-versions-without-asking: nil
    +End:
    +*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/regex.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,490 @@
    +/* Definitions for data structures and routines for the regular
    + expression library, version 0.12.
    +
    + Copyright (C) 1985, 1989, 1990, 1991, 1992, 1993 Free Software Foundation, Inc.
    +
    + 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. */
    +
    +#ifndef __REGEXP_LIBRARY_H__
    +#define __REGEXP_LIBRARY_H__
    +
    +/* POSIX says that <sys/types.h> must be included (by the caller) before
    + <regex.h>. */
    +
    +#ifdef VMS
    +/* VMS doesn't have `size_t' in <sys/types.h>, even though POSIX says it
    + should be there. */
    +#include <stddef.h>
    +#endif
    +
    +
    +/* The following bits are used to determine the regexp syntax we
    + recognize. The set/not-set meanings are chosen so that Emacs syntax
    + remains the value 0. The bits are given in alphabetical order, and
    + the definitions shifted by one from the previous bit; thus, when we
    + add or remove a bit, only one other definition need change. */
    +typedef unsigned reg_syntax_t;
    +
    +/* If this bit is not set, then \ inside a bracket expression is literal.
    + If set, then such a \ quotes the following character. */
    +#define RE_BACKSLASH_ESCAPE_IN_LISTS (1)
    +
    +/* If this bit is not set, then + and ? are operators, and \+ and \? are
    + literals.
    + If set, then \+ and \? are operators and + and ? are literals. */
    +#define RE_BK_PLUS_QM (RE_BACKSLASH_ESCAPE_IN_LISTS << 1)
    +
    +/* If this bit is set, then character classes are supported. They are:
    + [:alpha:], [:upper:], [:lower:], [:digit:], [:alnum:], [:xdigit:],
    + [:space:], [:print:], [:punct:], [:graph:], and [:cntrl:].
    + If not set, then character classes are not supported. */
    +#define RE_CHAR_CLASSES (RE_BK_PLUS_QM << 1)
    +
    +/* If this bit is set, then ^ and $ are always anchors (outside bracket
    + expressions, of course).
    + If this bit is not set, then it depends:
    + ^ is an anchor if it is at the beginning of a regular
    + expression or after an open-group or an alternation operator;
    + $ is an anchor if it is at the end of a regular expression, or
    + before a close-group or an alternation operator.
    +
    + This bit could be (re)combined with RE_CONTEXT_INDEP_OPS, because
    + POSIX draft 11.2 says that * etc. in leading positions is undefined.
    + We already implemented a previous draft which made those constructs
    + invalid, though, so we haven't changed the code back. */
    +#define RE_CONTEXT_INDEP_ANCHORS (RE_CHAR_CLASSES << 1)
    +
    +/* If this bit is set, then special characters are always special
    + regardless of where they are in the pattern.
    + If this bit is not set, then special characters are special only in
    + some contexts; otherwise they are ordinary. Specifically,
    + * + ? and intervals are only special when not after the beginning,
    + open-group, or alternation operator. */
    +#define RE_CONTEXT_INDEP_OPS (RE_CONTEXT_INDEP_ANCHORS << 1)
    +
    +/* If this bit is set, then *, +, ?, and { cannot be first in an re or
    + immediately after an alternation or begin-group operator. */
    +#define RE_CONTEXT_INVALID_OPS (RE_CONTEXT_INDEP_OPS << 1)
    +
    +/* If this bit is set, then . matches newline.
    + If not set, then it doesn't. */
    +#define RE_DOT_NEWLINE (RE_CONTEXT_INVALID_OPS << 1)
    +
    +/* If this bit is set, then . doesn't match NUL.
    + If not set, then it does. */
    +#define RE_DOT_NOT_NULL (RE_DOT_NEWLINE << 1)
    +
    +/* If this bit is set, nonmatching lists [^...] do not match newline.
    + If not set, they do. */
    +#define RE_HAT_LISTS_NOT_NEWLINE (RE_DOT_NOT_NULL << 1)
    +
    +/* If this bit is set, either \{...\} or {...} defines an
    + interval, depending on RE_NO_BK_BRACES.
    + If not set, \{, \}, {, and } are literals. */
    +#define RE_INTERVALS (RE_HAT_LISTS_NOT_NEWLINE << 1)
    +
    +/* If this bit is set, +, ? and | aren't recognized as operators.
    + If not set, they are. */
    +#define RE_LIMITED_OPS (RE_INTERVALS << 1)
    +
    +/* If this bit is set, newline is an alternation operator.
    + If not set, newline is literal. */
    +#define RE_NEWLINE_ALT (RE_LIMITED_OPS << 1)
    +
    +/* If this bit is set, then `{...}' defines an interval, and \{ and \}
    + are literals.
    + If not set, then `\{...\}' defines an interval. */
    +#define RE_NO_BK_BRACES (RE_NEWLINE_ALT << 1)
    +
    +/* If this bit is set, (...) defines a group, and \( and \) are literals.
    + If not set, \(...\) defines a group, and ( and ) are literals. */
    +#define RE_NO_BK_PARENS (RE_NO_BK_BRACES << 1)
    +
    +/* If this bit is set, then \<digit> matches <digit>.
    + If not set, then \<digit> is a back-reference. */
    +#define RE_NO_BK_REFS (RE_NO_BK_PARENS << 1)
    +
    +/* If this bit is set, then | is an alternation operator, and \| is literal.
    + If not set, then \| is an alternation operator, and | is literal. */
    +#define RE_NO_BK_VBAR (RE_NO_BK_REFS << 1)
    +
    +/* If this bit is set, then an ending range point collating higher
    + than the starting range point, as in [z-a], is invalid.
    + If not set, then when ending range point collates higher than the
    + starting range point, the range is ignored. */
    +#define RE_NO_EMPTY_RANGES (RE_NO_BK_VBAR << 1)
    +
    +/* If this bit is set, then an unmatched ) is ordinary.
    + If not set, then an unmatched ) is invalid. */
    +#define RE_UNMATCHED_RIGHT_PAREN_ORD (RE_NO_EMPTY_RANGES << 1)
    +
    +/* This global variable defines the particular regexp syntax to use (for
    + some interfaces). When a regexp is compiled, the syntax used is
    + stored in the pattern buffer, so changing this does not affect
    + already-compiled regexps. */
    +extern reg_syntax_t re_syntax_options;
    +
    +/* Define combinations of the above bits for the standard possibilities.
    + (The [[[ comments delimit what gets put into the Texinfo file, so
    + don't delete them!) */
    +/* [[[begin syntaxes]]] */
    +#define RE_SYNTAX_EMACS 0
    +
    +#define RE_SYNTAX_AWK \
    + (RE_BACKSLASH_ESCAPE_IN_LISTS | RE_DOT_NOT_NULL \
    + | RE_NO_BK_PARENS | RE_NO_BK_REFS \
    + | RE_NO_BK_VBAR | RE_NO_EMPTY_RANGES \
    + | RE_UNMATCHED_RIGHT_PAREN_ORD)
    +
    +#define RE_SYNTAX_POSIX_AWK \
    + (RE_SYNTAX_POSIX_EXTENDED | RE_BACKSLASH_ESCAPE_IN_LISTS)
    +
    +#define RE_SYNTAX_GREP \
    + (RE_BK_PLUS_QM | RE_CHAR_CLASSES \
    + | RE_HAT_LISTS_NOT_NEWLINE | RE_INTERVALS \
    + | RE_NEWLINE_ALT)
    +
    +#define RE_SYNTAX_EGREP \
    + (RE_CHAR_CLASSES | RE_CONTEXT_INDEP_ANCHORS \
    + | RE_CONTEXT_INDEP_OPS | RE_HAT_LISTS_NOT_NEWLINE \
    + | RE_NEWLINE_ALT | RE_NO_BK_PARENS \
    + | RE_NO_BK_VBAR)
    +
    +#define RE_SYNTAX_POSIX_EGREP \
    + (RE_SYNTAX_EGREP | RE_INTERVALS | RE_NO_BK_BRACES)
    +
    +/* P1003.2/D11.2, section 4.20.7.1, lines 5078ff. */
    +#define RE_SYNTAX_ED RE_SYNTAX_POSIX_BASIC
    +
    +#define RE_SYNTAX_SED RE_SYNTAX_POSIX_BASIC
    +
    +/* Syntax bits common to both basic and extended POSIX regex syntax. */
    +#define _RE_SYNTAX_POSIX_COMMON \
    + (RE_CHAR_CLASSES | RE_DOT_NEWLINE | RE_DOT_NOT_NULL \
    + | RE_INTERVALS | RE_NO_EMPTY_RANGES)
    +
    +#define RE_SYNTAX_POSIX_BASIC \
    + (_RE_SYNTAX_POSIX_COMMON | RE_BK_PLUS_QM)
    +
    +/* Differs from ..._POSIX_BASIC only in that RE_BK_PLUS_QM becomes
    + RE_LIMITED_OPS, i.e., \? \+ \| are not recognized. Actually, this
    + isn't minimal, since other operators, such as \`, aren't disabled. */
    +#define RE_SYNTAX_POSIX_MINIMAL_BASIC \
    + (_RE_SYNTAX_POSIX_COMMON | RE_LIMITED_OPS)
    +
    +#define RE_SYNTAX_POSIX_EXTENDED \
    + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
    + | RE_CONTEXT_INDEP_OPS | RE_NO_BK_BRACES \
    + | RE_NO_BK_PARENS | RE_NO_BK_VBAR \
    + | RE_UNMATCHED_RIGHT_PAREN_ORD)
    +
    +/* Differs from ..._POSIX_EXTENDED in that RE_CONTEXT_INVALID_OPS
    + replaces RE_CONTEXT_INDEP_OPS and RE_NO_BK_REFS is added. */
    +#define RE_SYNTAX_POSIX_MINIMAL_EXTENDED \
    + (_RE_SYNTAX_POSIX_COMMON | RE_CONTEXT_INDEP_ANCHORS \
    + | RE_CONTEXT_INVALID_OPS | RE_NO_BK_BRACES \
    + | RE_NO_BK_PARENS | RE_NO_BK_REFS \
    + | RE_NO_BK_VBAR | RE_UNMATCHED_RIGHT_PAREN_ORD)
    +/* [[[end syntaxes]]] */
    +
    +/* Maximum number of duplicates an interval can allow. Some systems
    + (erroneously) define this in other header files, but we want our
    + value, so remove any previous define. */
    +#ifdef RE_DUP_MAX
    +#undef RE_DUP_MAX
    +#endif
    +#define RE_DUP_MAX ((1 << 15) - 1)
    +
    +
    +/* POSIX `cflags' bits (i.e., information for `regcomp'). */
    +
    +/* If this bit is set, then use extended regular expression syntax.
    + If not set, then use basic regular expression syntax. */
    +#define REG_EXTENDED 1
    +
    +/* If this bit is set, then ignore case when matching.
    + If not set, then case is significant. */
    +#define REG_ICASE (REG_EXTENDED << 1)
    +
    +/* If this bit is set, then anchors do not match at newline
    + characters in the string.
    + If not set, then anchors do match at newlines. */
    +#define REG_NEWLINE (REG_ICASE << 1)
    +
    +/* If this bit is set, then report only success or fail in regexec.
    + If not set, then returns differ between not matching and errors. */
    +#define REG_NOSUB (REG_NEWLINE << 1)
    +
    +
    +/* POSIX `eflags' bits (i.e., information for regexec). */
    +
    +/* If this bit is set, then the beginning-of-line operator doesn't match
    + the beginning of the string (presumably because it's not the
    + beginning of a line).
    + If not set, then the beginning-of-line operator does match the
    + beginning of the string. */
    +#define REG_NOTBOL 1
    +
    +/* Like REG_NOTBOL, except for the end-of-line. */
    +#define REG_NOTEOL (1 << 1)
    +
    +
    +/* If any error codes are removed, changed, or added, update the
    + `re_error_msg' table in regex.c. */
    +typedef enum
    +{
    + REG_NOERROR = 0, /* Success. */
    + REG_NOMATCH, /* Didn't find a match (for regexec). */
    +
    + /* POSIX regcomp return error codes. (In the order listed in the
    + standard.) */
    + REG_BADPAT, /* Invalid pattern. */
    + REG_ECOLLATE, /* Not implemented. */
    + REG_ECTYPE, /* Invalid character class name. */
    + REG_EESCAPE, /* Trailing backslash. */
    + REG_ESUBREG, /* Invalid back reference. */
    + REG_EBRACK, /* Unmatched left bracket. */
    + REG_EPAREN, /* Parenthesis imbalance. */
    + REG_EBRACE, /* Unmatched \{. */
    + REG_BADBR, /* Invalid contents of \{\}. */
    + REG_ERANGE, /* Invalid range end. */
    + REG_ESPACE, /* Ran out of memory. */
    + REG_BADRPT, /* No preceding re for repetition op. */
    +
    + /* Error codes we've added. */
    + REG_EEND, /* Premature end. */
    + REG_ESIZE, /* Compiled pattern bigger than 2^16 bytes. */
    + REG_ERPAREN /* Unmatched ) or \); not returned from regcomp. */
    +} reg_errcode_t;
    +
    +/* This data structure represents a compiled pattern. Before calling
    + the pattern compiler, the fields `buffer', `allocated', `fastmap',
    + `translate', and `no_sub' can be set. After the pattern has been
    + compiled, the `re_nsub' field is available. All other fields are
    + private to the regex routines. */
    +
    +struct re_pattern_buffer
    +{
    +/* [[[begin pattern_buffer]]] */
    + /* Space that holds the compiled pattern. It is declared as
    + `unsigned char *' because its elements are
    + sometimes used as array indexes. */
    + unsigned char *buffer;
    +
    + /* Number of bytes to which `buffer' points. */
    + unsigned long allocated;
    +
    + /* Number of bytes actually used in `buffer'. */
    + unsigned long used;
    +
    + /* Syntax setting with which the pattern was compiled. */
    + reg_syntax_t syntax;
    +
    + /* Pointer to a fastmap, if any, otherwise zero. re_search uses
    + the fastmap, if there is one, to skip over impossible
    + starting points for matches. */
    + char *fastmap;
    +
    + /* Either a translate table to apply to all characters before
    + comparing them, or zero for no translation. The translation
    + is applied to a pattern when it is compiled and to a string
    + when it is matched. */
    + char *translate;
    +
    + /* Number of subexpressions found by the compiler. */
    + size_t re_nsub;
    +
    + /* Zero if this pattern cannot match the empty string, one else.
    + Well, in truth it's used only in `re_search_2', to see
    + whether or not we should use the fastmap, so we don't set
    + this absolutely perfectly; see `re_compile_fastmap' (the
    + `duplicate' case). */
    + unsigned can_be_null : 1;
    +
    + /* If REGS_UNALLOCATED, allocate space in the `regs' structure
    + for `max (RE_NREGS, re_nsub + 1)' groups.
    + If REGS_REALLOCATE, reallocate space if necessary.
    + If REGS_FIXED, use what's there. */
    +#define REGS_UNALLOCATED 0
    +#define REGS_REALLOCATE 1
    +#define REGS_FIXED 2
    + unsigned regs_allocated : 2;
    +
    + /* Set to zero when `regex_compile' compiles a pattern; set to one
    + by `re_compile_fastmap' if it updates the fastmap. */
    + unsigned fastmap_accurate : 1;
    +
    + /* If set, `re_match_2' does not return information about
    + subexpressions. */
    + unsigned no_sub : 1;
    +
    + /* If set, a beginning-of-line anchor doesn't match at the
    + beginning of the string. */
    + unsigned not_bol : 1;
    +
    + /* Similarly for an end-of-line anchor. */
    + unsigned not_eol : 1;
    +
    + /* If true, an anchor at a newline matches. */
    + unsigned newline_anchor : 1;
    +
    +/* [[[end pattern_buffer]]] */
    +};
    +
    +typedef struct re_pattern_buffer regex_t;
    +
    +
    +/* search.c (search_buffer) in Emacs needs this one opcode value. It is
    + defined both in `regex.c' and here. */
    +#define RE_EXACTN_VALUE 1
    +
    +/* Type for byte offsets within the string. POSIX mandates this. */
    +typedef int regoff_t;
    +
    +
    +/* This is the structure we store register match data in. See
    + regex.texinfo for a full description of what registers match. */
    +struct re_registers
    +{
    + unsigned num_regs;
    + regoff_t *start;
    + regoff_t *end;
    +};
    +
    +
    +/* If `regs_allocated' is REGS_UNALLOCATED in the pattern buffer,
    + `re_match_2' returns information about at least this many registers
    + the first time a `regs' structure is passed. */
    +#ifndef RE_NREGS
    +#define RE_NREGS 30
    +#endif
    +
    +
    +/* POSIX specification for registers. Aside from the different names than
    + `re_registers', POSIX uses an array of structures, instead of a
    + structure of arrays. */
    +typedef struct
    +{
    + regoff_t rm_so; /* Byte offset from string's start to substring's start. */
    + regoff_t rm_eo; /* Byte offset from string's start to substring's end. */
    +} regmatch_t;
    +
    +/* Declarations for routines. */
    +
    +/* To avoid duplicating every routine declaration -- once with a
    + prototype (if we are ANSI), and once without (if we aren't) -- we
    + use the following macro to declare argument types. This
    + unfortunately clutters up the declarations a bit, but I think it's
    + worth it. */
    +
    +#if __STDC__
    +
    +#define _RE_ARGS(args) args
    +
    +#else /* not __STDC__ */
    +
    +#define _RE_ARGS(args) ()
    +
    +#endif /* not __STDC__ */
    +
    +/* Sets the current default syntax to SYNTAX, and return the old syntax.
    + You can also simply assign to the `re_syntax_options' variable. */
    +extern reg_syntax_t re_set_syntax _RE_ARGS ((reg_syntax_t syntax));
    +
    +/* Compile the regular expression PATTERN, with length LENGTH
    + and syntax given by the global `re_syntax_options', into the buffer
    + BUFFER. Return NULL if successful, and an error string if not. */
    +extern const char *re_compile_pattern
    + _RE_ARGS ((const char *pattern, int length,
    + struct re_pattern_buffer *buffer));
    +
    +
    +/* Compile a fastmap for the compiled pattern in BUFFER; used to
    + accelerate searches. Return 0 if successful and -2 if was an
    + internal error. */
    +extern int re_compile_fastmap _RE_ARGS ((struct re_pattern_buffer *buffer));
    +
    +
    +/* Search in the string STRING (with length LENGTH) for the pattern
    + compiled into BUFFER. Start searching at position START, for RANGE
    + characters. Return the starting position of the match, -1 for no
    + match, or -2 for an internal error. Also return register
    + information in REGS (if REGS and BUFFER->no_sub are nonzero). */
    +extern int re_search
    + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
    + int length, int start, int range, struct re_registers *regs));
    +
    +
    +/* Like `re_search', but search in the concatenation of STRING1 and
    + STRING2. Also, stop searching at index START + STOP. */
    +extern int re_search_2
    + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
    + int length1, const char *string2, int length2,
    + int start, int range, struct re_registers *regs, int stop));
    +
    +
    +/* Like `re_search', but return how many characters in STRING the regexp
    + in BUFFER matched, starting at position START. */
    +extern int re_match
    + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string,
    + int length, int start, struct re_registers *regs));
    +
    +
    +/* Relates to `re_match' as `re_search_2' relates to `re_search'. */
    +extern int re_match_2
    + _RE_ARGS ((struct re_pattern_buffer *buffer, const char *string1,
    + int length1, const char *string2, int length2,
    + int start, struct re_registers *regs, int stop));
    +
    +
    +/* Set REGS to hold NUM_REGS registers, storing them in STARTS and
    + ENDS. Subsequent matches using BUFFER and REGS will use this memory
    + for recording register information. STARTS and ENDS must be
    + allocated with malloc, and must each be at least `NUM_REGS * sizeof
    + (regoff_t)' bytes long.
    +
    + If NUM_REGS == 0, then subsequent matches should allocate their own
    + register data.
    +
    + Unless this function is called, the first search or match using
    + PATTERN_BUFFER will allocate its own register data, without
    + freeing the old data. */
    +extern void re_set_registers
    + _RE_ARGS ((struct re_pattern_buffer *buffer, struct re_registers *regs,
    + unsigned num_regs, regoff_t *starts, regoff_t *ends));
    +
    +/* 4.2 bsd compatibility. */
    +extern char *re_comp _RE_ARGS ((const char *));
    +extern int re_exec _RE_ARGS ((const char *));
    +
    +/* POSIX compatibility. */
    +extern int regcomp _RE_ARGS ((regex_t *preg, const char *pattern, int cflags));
    +extern int regexec
    + _RE_ARGS ((const regex_t *preg, const char *string, size_t nmatch,
    + regmatch_t pmatch[], int eflags));
    +extern size_t regerror
    + _RE_ARGS ((int errcode, const regex_t *preg, char *errbuf,
    + size_t errbuf_size));
    +extern void regfree _RE_ARGS ((regex_t *preg));
    +
    +#endif /* not __REGEXP_LIBRARY_H__ */
    +
    +/*
    +Local variables:
    +make-backup-files: t
    +version-control: t
    +trim-versions-without-asking: nil
    +End:
    +*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/support.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,158 @@
    +/*
    + * 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.
    + */
    +
    +#ifdef HAVE_CONFIG_H
    +# include "../pp_config.h"
    +#endif
    +
    +#include "../common/pp_internal.h"
    +
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <unistd.h>
    +#include <string.h>
    +#include <stdio.h>
    +
    +#include <gtk/gtk.h>
    +
    +#include "ignorance_internal.h"
    +#include "support.h"
    +
    +GtkWidget*
    +lookup_widget (GtkWidget *widget,
    + const gchar *widget_name)
    +{
    + GtkWidget *parent, *found_widget;
    +
    + for (;;)
    + {
    + if (GTK_IS_MENU (widget))
    + parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
    + else
    + parent = widget->parent;
    + if (!parent)
    + parent = (GtkWidget*) g_object_get_data (G_OBJECT (widget), "GladeParentKey");
    + if (parent == NULL)
    + break;
    + widget = parent;
    + }
    +
    + found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget),
    + widget_name);
    + if (!found_widget)
    + g_warning ("Widget not found: %s", widget_name);
    + return found_widget;
    +}
    +
    +static GList *pixmaps_directories = NULL;
    +
    +/* Use this function to set the directory containing installed pixmaps. */
    +void
    +add_pixmap_directory (const gchar *directory)
    +{
    + pixmaps_directories = g_list_prepend (pixmaps_directories,
    + g_strdup (directory));
    +}
    +
    +/* This is an internally used function to find pixmap files. */
    +static gchar*
    +find_pixmap_file (const gchar *filename)
    +{
    + GList *elem;
    +
    + /* We step through each of the pixmaps directory to find it. */
    + elem = pixmaps_directories;
    + while (elem)
    + {
    + gchar *pathname = g_strdup_printf ("%s%s%s", (gchar*)elem->data,
    + G_DIR_SEPARATOR_S, filename);
    + if (g_file_test (pathname, G_FILE_TEST_EXISTS))
    + return pathname;
    + g_free (pathname);
    + elem = elem->next;
    + }
    + return NULL;
    +}
    +
    +/* This is an internally used function to create pixmaps. */
    +GtkWidget*
    +create_pixmap (GtkWidget *widget,
    + const gchar *filename)
    +{
    + gchar *pathname = NULL;
    + GtkWidget *pixmap;
    +
    + if (!filename || !filename[0])
    + return gtk_image_new ();
    +
    + pathname = find_pixmap_file (filename);
    +
    + if (!pathname)
    + {
    + g_warning (_("Couldn't find pixmap file: %s"), filename);
    + return gtk_image_new ();
    + }
    +
    + pixmap = gtk_image_new_from_file (pathname);
    + g_free (pathname);
    + return pixmap;
    +}
    +
    +/* This is an internally used function to create pixmaps. */
    +GdkPixbuf*
    +create_pixbuf (const gchar *filename)
    +{
    + gchar *pathname = NULL;
    + GdkPixbuf *pixbuf;
    + GError *error = NULL;
    +
    + if (!filename || !filename[0])
    + return NULL;
    +
    + pathname = find_pixmap_file (filename);
    +
    + if (!pathname)
    + {
    + g_warning (_("Couldn't find pixmap file: %s"), filename);
    + return NULL;
    + }
    +
    + pixbuf = gdk_pixbuf_new_from_file (pathname, &error);
    + if (!pixbuf)
    + {
    + fprintf (stderr, "Failed to load pixbuf file: %s: %s\n",
    + pathname, error->message);
    + g_error_free (error);
    + }
    + g_free (pathname);
    + return pixbuf;
    +}
    +
    +/* This is used to set ATK action descriptions. */
    +void
    +glade_set_atk_action_description (AtkAction *action,
    + const gchar *action_name,
    + const gchar *description)
    +{
    + gint n_actions, i;
    +
    + n_actions = atk_action_get_n_actions (action);
    + for (i = 0; i < n_actions; i++)
    + {
    + if (!strcmp (atk_action_get_name (action, i), action_name))
    + atk_action_set_description (action, i, description);
    + }
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignorance/support.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,58 @@
    +/*
    + * 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.
    + */
    +
    +#ifndef IGNORANCE_SUPPORT_H
    +#define IGNORANCE_SUPPORT_H
    +
    +#include <gtk/gtk.h>
    +
    +
    +
    +/*
    + * Public Functions.
    + */
    +
    +/*
    + * This function returns a widget in a component created by Glade.
    + * Call it with the toplevel widget in the component (i.e. a window/dialog),
    + * or alternatively any widget in the component, and the name of the widget
    + * you want returned.
    + */
    +GtkWidget* lookup_widget (GtkWidget *widget,
    + const gchar *widget_name);
    +
    +
    +/* Use this function to set the directory containing installed pixmaps. */
    +void add_pixmap_directory (const gchar *directory);
    +
    +
    +/*
    + * Private Functions.
    + */
    +
    +/* This is used to create the pixmaps used in the interface. */
    +GtkWidget* create_pixmap (GtkWidget *widget,
    + const gchar *filename);
    +
    +/* This is used to create the pixbufs used in the interface. */
    +GdkPixbuf* create_pixbuf (const gchar *filename);
    +
    +/* This is used to set ATK action descriptions. */
    +void glade_set_atk_action_description (AtkAction *action,
    + const gchar *action_name,
    + const gchar *description);
    +
    +#endif
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignore/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +ignoredir = $(PURPLE_LIBDIR)
    +
    +ignore_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +ignore_LTLIBRARIES = ignore.la
    +
    +ignore_la_SOURCES = \
    + ignore.c
    +
    +ignore_la_LIBADD = \
    + $(PURPLE_LIBS) \
    + $(GLIB_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(GLIB_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignore/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for bit plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = ignore
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignore/ignore.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,311 @@
    +/**
    + * @file ignore.c Ignore people.
    + *
    + * 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
    + * 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 <account.h>
    +#include <blist.h>
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <plugin.h>
    +#include <util.h>
    +
    +#include <string.h>
    +
    +#define PREF_ROOT "/plugins/ignore"
    +
    +static PurpleCmdId cmd;
    +
    +typedef enum
    +{
    + IGNORE_ALL = 1 << 0,
    + IGNORE_CHAT = 1 << 1
    +} IgnoreContext;
    +
    +static const char *
    +rule_key(PurpleAccount *account, const char *name)
    +{
    + static char key[1024];
    + char *k = key;
    +
    + k += snprintf(key, sizeof(key), PREF_ROOT "/%s/%s/",
    + purple_account_get_protocol_id(account),
    + purple_normalize(account, purple_account_get_username(account)));
    + snprintf(k, sizeof(key) - (k - key) - 1, "%s", purple_normalize(account, name));
    +
    + return key;
    +}
    +
    +static void
    +add_ignore_rule(IgnoreContext context, const char *name, PurpleAccount *account)
    +{
    + GString *string;
    + char *pref;
    + char *lower_case_username;
    +
    + string = g_string_new(PREF_ROOT);
    + string = g_string_append_c(string, '/');
    + string = g_string_append(string, purple_account_get_protocol_id(account));
    + if (!purple_prefs_exists(string->str))
    + purple_prefs_add_none(string->str);
    + string = g_string_append_c(string, '/');
    + string = g_string_append(string, purple_normalize(account, purple_account_get_username(account)));
    + lower_case_username = g_ascii_strdown(string->str, string->len);
    + if (!purple_prefs_exists(lower_case_username))
    + purple_prefs_add_none(lower_case_username);
    + g_free(lower_case_username);
    + string = g_string_append_c(string, '/');
    + string = g_string_append(string, purple_normalize(account, name));
    + pref = g_ascii_strdown(string->str, string->len);
    + if (!purple_prefs_exists(pref)) {
    + GList *list = purple_prefs_get_string_list(PREF_ROOT "/rules");
    + purple_prefs_add_string(pref, context == IGNORE_ALL ? "all" : "chat");
    + if (!g_list_find_custom(list, pref, (GCompareFunc)g_utf8_collate)) {
    + list = g_list_prepend(list, g_strdup(pref));
    + purple_prefs_set_string_list(PREF_ROOT "/rules", list);
    + g_list_foreach(list, (GFunc)g_free, NULL);
    + g_list_free(list);
    + }
    + } else {
    + purple_prefs_set_string(pref, context == IGNORE_ALL ? "all" : "chat");
    + }
    + g_string_free(string, TRUE);
    + g_free(pref);
    +}
    +
    +static void
    +remove_ignore_rule_d(const char *name, PurpleAccount *account)
    +{
    + char *key = g_ascii_strdown(rule_key(account, name), -1);
    + if (purple_prefs_exists(key))
    + purple_prefs_set_string(key, "none");
    + g_free(key);
    +}
    +
    +static void
    +list_ignore_rules()
    +{
    + GString *string;
    + GList *list = purple_prefs_get_string_list(PREF_ROOT "/rules");
    + char *last = NULL;
    +
    + string = g_string_new(NULL);
    + list = g_list_sort(list, (GCompareFunc)g_utf8_collate);
    +
    + while (list) {
    + char *pref = list->data;
    + const char *rule = purple_prefs_get_string(pref);
    + char *split = strrchr(pref, '/');
    + *split++ = '\0';
    +
    + if (rule && *rule != 'n') {
    + if (last == NULL || g_strcasecmp(last, pref)) {
    + g_free(last);
    + last = g_strdup(pref);
    + g_string_append_printf(string, "Ignore rules for %s<br>", last);
    + }
    + g_string_append_printf(string, "\t%s: %s<br>", split, rule);
    + }
    + g_free(list->data);
    + list = g_list_delete_link(list, list);
    + }
    + purple_notify_formatted(NULL, _("Ignore Rules"), _("The following are the current ignore rules"),
    + NULL, *string->str ? string->str : _("(Dear God! You are not ignoring any one!)"), NULL, NULL);
    + g_string_free(string, TRUE);
    + g_free(last);
    +}
    +
    +static PurpleCmdRet
    +ignore_cmd(PurpleConversation *conv, const char *cmd, char **arguments, char **error, gpointer data)
    +{
    + int nargs = 0;
    + IgnoreContext context = IGNORE_ALL;
    + PurpleAccount *account;
    + const char *who;
    + char **args = g_strsplit(arguments[0], " ", -1);
    +
    + if (args == NULL) {
    + list_ignore_rules();
    + goto end;
    + }
    +
    + if (strcmp(args[nargs], "-c") == 0) {
    + nargs++;
    + context = IGNORE_CHAT;
    + }
    +
    + if (args[nargs] == NULL) {
    + goto end;
    + }
    +#if 0
    + if (isdigit(args[nargs][0])) {
    + if (sscanf(args[nargs], "%d", &timer) == 1)
    + nargs++;
    + }
    +#endif
    + account = purple_conversation_get_account(conv);
    +
    + who = args[nargs][1] ? (args[nargs] + 1) : purple_conversation_get_name(conv);
    + while (args[nargs]) {
    + switch(args[nargs][0]) {
    + case '+':
    + add_ignore_rule(context, who, account);
    + break;
    + case '-':
    + remove_ignore_rule_d(who, account);
    + break;
    + default:
    + purple_debug_warning("ignore", "invalid command %s\n", args[nargs]);
    + break;
    + }
    +
    + nargs++;
    + }
    +end:
    + g_strfreev(args);
    + return PURPLE_CMD_STATUS_OK;
    +}
    +
    +static gboolean
    +is_ignored(const char *name, PurpleAccount *account, PurpleConversationType type)
    +{
    + char *key = g_ascii_strdown(rule_key(account, name), -1);
    + const char *pref = NULL;
    +
    + if (!purple_prefs_exists(key)) {
    + g_free(key);
    + return FALSE;
    + }
    + pref = purple_prefs_get_string(key);
    + g_free(key);
    + if (!pref)
    + return FALSE;
    +
    + if (*pref == 'a') {
    + purple_debug_info("ignore", "ignoring %s\n", name);
    + return TRUE;
    + }
    +
    + if (*pref == 'c' && type == PURPLE_CONV_TYPE_CHAT) {
    + purple_debug_info("ignore", "ignoring %s\n", name);
    + return TRUE;
    + }
    +
    + return FALSE;
    +}
    +
    +static gboolean
    +receiving_msg(PurpleAccount *account, const char **who, const char **message,
    + PurpleConversation *conv, PurpleMessageFlags *flags, PurpleConversationType type)
    +{
    + return is_ignored(*who, account, type);
    +}
    +
    +static gboolean
    +chat_activity_cb(PurpleConversation *conv, const char *user, PurpleConvChatBuddyFlags flags)
    +{
    + return is_ignored(user, purple_conversation_get_account(conv), PURPLE_CONV_TYPE_CHAT);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + cmd = purple_cmd_register("ignore", "s", PURPLE_CMD_P_DEFAULT,
    + PURPLE_CMD_FLAG_CHAT | PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS, NULL,
    + ignore_cmd, _("ignore [-c] [+&lt;ignore&gt; -&lt;unignore&gt;]<br>\
    +Examples:<br>\
    + 'ignore +StupidBot -NotABot' \t - (in a chat) Starts ignoring StupidBot, and removes NotABot from ignore list.<br>\
    + 'ignore -c +AnotherBot' \t - (in a chat) Starts ignoring AnotherBot, but only in chats.<br>\
    + 'ignore +' \t - (in an IM) Starts ignoring this person.<br>\
    + 'ignore -' \t - (in an IM) Starts unignoring this person.<br>\
    + 'ignore' \t - Lists the current ignore rules."), NULL);
    +
    + purple_signal_connect(purple_conversations_get_handle(), "chat-buddy-leaving", plugin,
    + G_CALLBACK(chat_activity_cb), NULL);
    + purple_signal_connect(purple_conversations_get_handle(), "chat-buddy-joining", plugin,
    + G_CALLBACK(chat_activity_cb), NULL);
    + purple_signal_connect(purple_conversations_get_handle(), "receiving-chat-msg", plugin,
    + G_CALLBACK(receiving_msg), GINT_TO_POINTER(PURPLE_CONV_TYPE_CHAT));
    + purple_signal_connect(purple_conversations_get_handle(), "receiving-im-msg", plugin,
    + G_CALLBACK(receiving_msg), GINT_TO_POINTER(PURPLE_CONV_TYPE_IM));
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + purple_cmd_unregister(cmd);
    + cmd = -1;
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + "core-plugin_pack-ignore",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
    + PP_WEBSITE,
    + plugin_load,
    + plugin_unload,
    + 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 */
    +
    + purple_prefs_add_none(PREF_ROOT);
    + purple_prefs_add_string_list(PREF_ROOT "/rules", NULL);
    +
    + info.name = _("Ignore");
    + info.summary =
    + _("Flexible plugin to selectively ignore people. Please do not use if you have amnesia.");
    + info.description =
    + _("Flexible plugin to selectively ignore people. See '/help ignore' for more help.\nPlease do not use if you have amnesia.");
    +}
    +
    +PURPLE_INIT_PLUGIN(ignore, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/ignore/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Ignore]
    +type=default
    +depends=purple
    +provides=ignore
    +summary=Flexible plugin to selectively ignore people
    +description=%(summary)s
    +authors=Sadrul Habib Chowdhury
    +introduced=2.0.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/infopane/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +infopanedir = $(PIDGIN_LIBDIR)
    +
    +infopane_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +infopane_LTLIBRARIES = infopane.la
    +
    +infopane_la_SOURCES = \
    + infopane.c
    +
    +infopane_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/infopane/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for infopane plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = infopane
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/infopane/infopane.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,261 @@
    +/*
    + * Infopane - Use different views for the details information in conversation windows.
    + * 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 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 "pidgin.h"
    +
    +#include "conversation.h"
    +#include "debug.h"
    +#include "log.h"
    +#include "notify.h"
    +#include "prefs.h"
    +#include "signals.h"
    +#include "util.h"
    +
    +#include "gtkconv.h"
    +#include "gtkimhtml.h"
    +#include "gtkplugin.h"
    +
    +#define PLUGIN_ID "gtk-plugin_pack-infopane"
    +
    +#define PREF_PREFIX "/plugins/gtk/infopane"
    +#define PREF_POSITION PREF_PREFIX "/position"
    +#define PREF_DRAG PREF_PREFIX "/drag"
    +#define PREF_SINGLE PREF_PREFIX "/single"
    +#define PREF_ICON PREF_PREFIX "/icon"
    +
    +static gboolean ensure_tabs_are_showing(PurpleConversation *conv)
    +{
    + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
    + PidginWindow *win = gtkconv->win;
    + if (win && win->gtkconvs && win->gtkconvs->next)
    + return FALSE;
    + if (purple_prefs_get_bool(PREF_SINGLE)) {
    + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), TRUE);
    + } else if (win->gtkconvs->next == NULL) {
    + gtk_notebook_set_show_tabs(GTK_NOTEBOOK(win->notebook), FALSE);
    + }
    + return FALSE;
    +}
    +
    +static void set_conv_window_prefs(PidginConversation *gtkconv)
    +{
    + GtkWidget *paned, *vbox;
    + GList *list;
    + char pref;
    +
    + pref = purple_prefs_get_string(PREF_POSITION)[0];
    +
    + if (pref == 't')
    + goto end_position;
    +
    + if (pref == 'n') {
    + gtk_widget_hide_all(gtkconv->infopane_hbox->parent);
    + goto end_position;
    + }
    +
    + list = gtk_container_get_children(GTK_CONTAINER(gtkconv->tab_cont));
    + paned = list->data;
    + vbox = gtk_paned_get_child1(GTK_PANED(paned));
    +
    + g_object_ref(G_OBJECT(gtkconv->infopane_hbox->parent));
    + gtk_container_remove(GTK_CONTAINER(vbox), gtkconv->infopane_hbox->parent);
    + gtk_box_pack_end(GTK_BOX(vbox), gtkconv->infopane_hbox->parent, FALSE, FALSE, 0);
    + g_object_unref(G_OBJECT(gtkconv->infopane_hbox->parent));
    +
    +end_position:
    + /* PREF_DRAG */
    + /* To disable dragging, setup a listener for button_press_event and return TRUE if the
    + * click is not going to popup up the sendto or the context menu */
    +
    + /* PREF_SINGLE */
    + ensure_tabs_are_showing(gtkconv->active_conv);
    +
    + /* PREF_ICON */
    + if (purple_prefs_get_bool(PREF_ICON)) {
    + gtk_widget_show(gtkconv->icon);
    + } else {
    + gtk_widget_hide(gtkconv->icon);
    + }
    +
    + return;
    +}
    +
    +static void conversation_deleted(PurpleConversation *conv)
    +{
    + PidginConversation *gtkconv = PIDGIN_CONVERSATION(conv);
    + PidginWindow *win = gtkconv->win;
    + if (win->gtkconvs->next && !win->gtkconvs->next->next) { /* There are only two tabs in the window */
    + PidginConversation *p = win->gtkconvs->data;
    + int id;
    + if (p == gtkconv)
    + p = win->gtkconvs->next->data;
    + id = g_timeout_add(0, (GSourceFunc)ensure_tabs_are_showing, p->active_conv);
    + g_signal_connect_swapped(G_OBJECT(win->window), "destroy",
    + G_CALLBACK(g_source_remove), GINT_TO_POINTER(id));
    + }
    +}
    +
    +static void
    +pref_changed(gpointer data, ...)
    +{
    + GList *wins = pidgin_conv_windows_get_list();
    + for (; wins; wins = wins->next) {
    + GList *tabs = pidgin_conv_window_get_gtkconvs(wins->data);
    + for (; tabs; tabs = tabs->next) {
    + set_conv_window_prefs(tabs->data);
    + }
    + }
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + guint regsuccess = 0;
    +
    + regsuccess = purple_signal_connect(pidgin_conversations_get_handle(),
    + "conversation-displayed",
    + plugin, PURPLE_CALLBACK(set_conv_window_prefs), NULL);
    +
    + if(regsuccess == 0) {
    + purple_debug_error(PLUGIN_ID, "Libpurple and Pidgin are too old!\n");
    + purple_debug_error(PLUGIN_ID, _("Libpurple and Pidgin are too old!\n"));
    + purple_notify_error(plugin, _("Incompatible Plugin"),
    + _("You need to update Pidgin!"),
    + _("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."));
    + return FALSE;
    + }
    +
    + purple_signal_connect(purple_conversations_get_handle(),
    + "deleting-conversation",
    + plugin, PURPLE_CALLBACK(conversation_deleted), NULL);
    + purple_signal_connect(pidgin_conversations_get_handle(),
    + "conversation-switched",
    + plugin, PURPLE_CALLBACK(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);
    + purple_prefs_connect_callback(plugin, PREF_ICON, (PurplePrefCallback)pref_changed, NULL);
    + purple_prefs_connect_callback(plugin, PREF_SINGLE, (PurplePrefCallback)pref_changed, NULL);
    +
    + purple_prefs_trigger_callback(PREF_POSITION);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + /* XXX: Is there a better way than this? There really should be. */
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_POSITION, _("Position of the infopane ('top', 'bottom' or 'none')"));
    + purple_plugin_pref_frame_add(frame, pref);
    +#if 0
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_ICON,
    + _("Show icon in the tabs"));
    + purple_plugin_pref_frame_add(frame, pref);
    +#endif
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_SINGLE,
    + _("Always show the tab"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info =
    +{
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    +
    + /* padding */
    + 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,
    + PLUGIN_ID,
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Sadrul H Chowdhury <sadrul@pidgin.im>",
    + PP_WEBSITE,
    + plugin_load,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + &prefs_info,
    + NULL,
    +
    + /* padding */
    + 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 */
    +
    + if(purple_version_check(2,2,0) == NULL) {
    + info.name = _("Infopane Options");
    + info.summary = _("Allow customizing the details information in conversation windows.");
    + info.description = _("Allow customizing the details information in conversation windows.");
    +
    + purple_prefs_add_none(PREF_PREFIX);
    + purple_prefs_add_string(PREF_POSITION, "top");
    + purple_prefs_add_bool(PREF_DRAG, FALSE);
    + purple_prefs_add_bool(PREF_SINGLE, TRUE);
    + purple_prefs_add_bool(PREF_ICON, TRUE);
    + } else {
    + purple_debug_error(PLUGIN_ID, "Libpurple and Pidgin are too old!\n");
    + purple_debug_error(PLUGIN_ID, _("Libpurple and Pidgin are too old!\n"));
    +
    + info.name = _("Incompatible Plugin! - Check plugin details!");
    + info.summary = _("This plugin is NOT compatible with this version of Pidgin!");
    + info.description = _("This plugin is NOT compatible with this version of Pidgin!");
    + }
    +}
    +
    +PURPLE_INIT_PLUGIN(infopane, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/infopane/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Infopane Options]
    +type=default
    +depends=pidgin
    +provides=infopane
    +summary=Adds some options for the information pane in conversations
    +description=%(summary)s
    +authors=Sadrul Habib Chowdhury
    +introduced=2.2.0
    +notes=Requires Pidgin 2.1.0 or newer.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irc-more/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +irc_moredir = $(PURPLE_LIBDIR)
    +
    +irc_more_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +irc_more_LTLIBRARIES = irc-more.la
    +
    +irc_more_la_SOURCES = \
    + irc-more.c
    +
    +irc_more_la_LIBADD = \
    + $(PURPLE_LIBS) \
    + $(GLIB_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PURPLE_PIXMAPSDIR)\" \
    + $(GLIB_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irc-more/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for irc-more plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = irc-more
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irc-more/irc-more.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,355 @@
    +/**
    + * @file irc-more.c A couple of additional IRC features.
    + *
    + * 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
    + * 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 <accountopt.h>
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <plugin.h>
    +#include <prpl.h>
    +
    +#include <string.h>
    +
    +#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 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"
    +
    +#define PREF_PREFIX "/plugins/core/" PLUGIN_ID
    +#define PREF_DELAY PREF_PREFIX "/delay"
    +
    +#define MATCHES(string) !strncmp(*msg, string, sizeof(string) - 1)
    +
    +static PurpleCmdId notice_cmd_id = 0;
    +static PurplePluginProtocolInfo *irc_info = NULL;
    +
    +static gboolean
    +show_them(gpointer data)
    +{
    + /* So you think you can kick me? I'll show you! */
    + PurpleConversation *conv = data;
    + char *conv_name = NULL, *command = NULL, *markup = NULL, *error = NULL;
    +
    + if(conv_name) {
    + command = g_strdup_printf("join %s", conv_name);
    + markup = g_markup_escape_text(command, -1);
    + error = NULL;
    + purple_cmd_do_command(conv, command, markup, &error); /* Do anything with the return value? */
    + g_free(command);
    + g_free(markup);
    + g_free(error);
    + }
    +
    + return FALSE;
    +}
    +
    +static void
    +irc_receiving_text(PurpleConnection *gc, const char **incoming, gpointer null)
    +{
    + char **splits;
    +
    + if (!incoming || !*incoming || !**incoming) /* oh the fun .. I can do this all day! */
    + return;
    +
    + splits = g_strsplit(*incoming, " ", -1);
    + if (splits[1]) {
    + PurpleAccount *account = purple_connection_get_account(gc);
    + char *str = g_ascii_strdown(splits[1], -1);
    +
    + if (strcmp(str, "kick") == 0 && splits[2] && splits[3]) {
    + char *name = splits[2];
    + GList *chats = purple_get_chats();
    + while (chats) {
    + PurpleConversation *conv = chats->data;
    + chats = chats->next;
    + if (purple_conversation_get_account(conv) == account
    + && strcmp(purple_conversation_get_name(conv), name) == 0) {
    + g_timeout_add(1000 * MAX(10, purple_prefs_get_int(PREF_DELAY)), show_them, conv);
    + break;
    + }
    + }
    + }
    + g_free(str);
    + }
    + g_strfreev(splits);
    +}
    +
    +static void
    +signed_on_cb(PurpleConnection *gc)
    +{
    + /* should this be done on a timeout? */
    + PurpleAccount *account = NULL;
    + const gchar *nick = NULL, *setmodes = NULL, *unsetmodes = NULL;
    + gchar *msg = NULL, *msg2 = NULL;
    +
    + account = purple_connection_get_account(gc);
    +
    + /* hopefully prevent crashes related to non-IRC accounts signing on */
    + if(strcmp("prpl-irc", purple_account_get_protocol_id(account)))
    + return;
    +
    + nick = purple_connection_get_display_name(gc);
    + 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);
    +
    + 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)
    +{
    + gchar *tmp = NULL, *msg = NULL;
    + gint len = 0, arg0len = 0, arg1len = 0, maxlen = 0;
    + PurpleConnection *gc = NULL;
    +
    + if(!args && !args[0] && !args[1])
    + return PURPLE_CMD_RET_FAILED;
    +
    + gc = purple_conversation_get_gc(conv);
    +
    + /* convenience to make the next comparison make more sense */
    + arg0len = strlen(args[0]);
    + arg1len = strlen(args[1]);
    +
    + /* IRC messages are limited to 512 bytes. 2 are reserved for CRLF, 2 for
    + * the spaces needed after the command and target, 1 for the colon needed
    + * before the notice text starts, and 6 for NOTICE. Result is the length
    + * of the message needs to be limited to 501 bytes. We need to account
    + * for the length of the nick or channel name that is the target, too. */
    + maxlen = 501 - arg0len;
    +
    + if(arg1len > maxlen)
    + tmp = g_strndup(args[1], maxlen);
    +
    + /* if tmp is not NULL, the notice the user wants to send is too long so we
    + * truncated it. If tmp is NULL the notice is fine as-is. Either way,
    + * assign msg as appropriate. */
    + if(tmp)
    + msg = tmp;
    + else
    + msg = args[1];
    +
    + msg = g_strdup_printf("NOTICE %s :%s\r\n", args[0], msg);
    +
    + len = strlen(msg);
    +
    + irc_info->send_raw(gc, msg, len);
    +
    + /* avoid a possible double-free crash */
    + 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;
    +
    + if (MATCHES("QUIT ")) {
    + char *message = strchr(*msg, ':');
    + if (!message || !strcmp(message + 1, "Leaving.\r\n")) {
    + *msg = g_strdup_printf("QUIT :%s\r\n", QUIT_MESSAGE);
    + }
    + } else if (MATCHES("PART ")) {
    + char *message = strchr(*msg, ':');
    + if (message)
    + return; /* The user did give some part message. Do not use the default one. */
    + message = strchr(*msg, '\r');
    + *message = '\0';
    + *msg = g_strdup_printf("%s :%s\r\n", *msg, PART_MESSAGE);
    + } else if (MATCHES("NOTICE ")) {
    + char *version = strstr(*msg, ":\001VERSION ");
    + if (!version)
    + return;
    + *version = '\0';
    + *msg = g_strdup_printf("%s:\001VERSION %s\001\r\n", *msg, CTCP_REPLY);
    + }
    + if (*msg != old)
    + g_free(old);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + PurplePlugin *prpl = NULL;
    + PurpleAccountOption *option;
    + gchar *notice_help = NULL;
    + void *gc_handle = NULL;
    +
    + prpl = purple_find_prpl("prpl-irc");
    +
    + /* if we didn't find the prpl, bail */
    + if (!prpl)
    + return FALSE;
    +
    + /* 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();
    +
    + /* list signals in alphabetical order for consistency */
    + purple_signal_connect(prpl, "irc-sending-text", plugin,
    + G_CALLBACK(irc_sending_text), NULL);
    + purple_signal_connect(prpl, "irc-receiving-text", plugin,
    + G_CALLBACK(irc_receiving_text), NULL);
    + purple_signal_connect(gc_handle, "signed-on", plugin,
    + G_CALLBACK(signed_on_cb), NULL);
    +
    + irc_info = PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
    +
    + /* Alphabetize the option label strings */
    + option = purple_account_option_string_new(_("CTCP Version reply"), "ctcp-message", "Purple IRC");
    + irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
    +
    + option = purple_account_option_string_new(_("Default Quit Message"), "quit-message", "Leaving.");
    + irc_info->protocol_options = g_list_append(irc_info->protocol_options, option);
    +
    + 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(_("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;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + purple_cmd_unregister(notice_cmd_id);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_DELAY,
    + _("Seconds to wait before rejoining"));
    + purple_plugin_pref_set_bounds(pref, 3, 3600);
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    +
    + /* padding */
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + PLUGIN_ID,
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Sadrul H Chowdhury <sadrul@users.sourceforge.net>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + NULL,
    +
    + NULL,
    + NULL,
    + &prefs_info,
    + 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 = _("IRC More");
    + info.summary = _("Adds additional IRC features.");
    + info.description = _("Adds additional IRC features, including a "
    + "customizable quit message, a customizable CTCP VERSION reply, "
    + "and the /notice command for notices.");
    +
    + purple_prefs_add_none(PREF_PREFIX);
    + purple_prefs_add_int(PREF_DELAY, 30);
    +}
    +
    +PURPLE_INIT_PLUGIN(irc_more, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irc-more/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[IRC More]
    +type=default
    +depends=purple
    +provides=ircmore
    +summary=Adds additional IRC features
    +description=Adds additional IRC features, including a customizable quit message, a customizable CTCP VERSION reply, and the /notice command for notices where libpurple does not support it.
    +authors=Sadrul Habib Chowdhury,John Bailey
    +introduced=2.2.0
    +notes=Support for /notice only when built with libpurple older than 2.4.0.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/ChangeLog Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,131 @@
    +This ChangeLog documents changes prior to when this plugin was merged into the
    +Plugin Pack. See the ChangeLog file a directory up for post-merge changes.
    +
    +version 0.13
    + * Suppress the GameSurge message of the day.
    + * Suppress FreeNode's cloak confirmation message (Anthony Sofocleous)
    + * Allow AuthServ authentication with a different name than the
    + account's username. (Anthony Sofocleous)
    + * Updated release for Gaim 2.0.0
    + Things will still build on Gaim 1.x.y, but the Windows DLL shipped
    + with this version only works on Gaim 2.0.0. Likewise, the RPM .spec
    + file is designed for Gaim 2. If you need to build an RPM for Gaim 1,
    + use the .spec file from the Gaim IRC Helper 0.12 release.
    + * Suppress ChanServ join notices if they haven't changed since we last
    + saw one for that channel (if the channel is on the buddy list)
    + * Display ChanServ access list add/removal notices in the chat window
    + * Suppress the initial topic notice if the topic hasn't changed since
    + we last joined that channel (if the channel is on the buddy list).
    + * Suppress notices when ChanServ ops or voices you in the first 10
    + seconds after you join a room -- the idea is to suppress auto-op
    + and auto-voice notices
    + * Suppress duplicate auto-responses.
    +
    +version 0.12
    + * Added support for DALnet.
    +
    + * Backing out the -Wl,--as-needed LDFLAGS. It doesn't work for some
    + users. It's not really needed here. If you want it, you can still
    + specify it yourself with: make LDFLAGS=-Wl,--as-needed
    +
    + * Added RPM .spec file. (Chris Weyl)
    +
    + * Updated Undernet Support to use x@channels.undernet.org
    +
    +version 0.11
    + * Honor CFLAGS and LDFLAGS set in the environment.
    +
    + * When no LDFLAGS is set, set -Wl,--as-needed.
    +
    + * Specify GCC as the default compiler.
    +
    +version 0.10
    + * Suppress the FreeNode stats collection bot.
    +
    + * Added support for Jeux. (Thanks to BNI on irc.freenode.net.)
    +
    +version 0.9 (2005-05-06)
    + * Fixed the CFLAGS.
    +
    + * Moved the plugin to a new SourceForge project.
    +
    + * Fixed QuakeNet authentication.
    +
    + * Added support for GameSurge.
    +
    +version 0.8 (2005-04-24)
    + * Using masked account options on sufficiently new versions of Gaim
    +
    + * More properly uses the Gaim API. This means that once compiled, this
    + plugin should work properly with all future releases in the same
    + major series. For example, if you compile the plugin for 1.3.0, it
    + should work for all future versions of Gaim in the 1.x.x series.
    +
    + * Added the PHILOSOPHY and IDEAS files. If you want some insight into
    + my goals for this plugin, read away.
    +
    + TOO MUCH INFORMATION: Currently, if you compile for version 1.x.y
    + where x < 2, it'll work for anything in the 1.x.x series. Compiling
    + on 1.2.0 or greater will result in a binary that will work on any
    + 1.x.x version of Gaim greater than 1.2.0. If you compile for 2.0.0
    + or later (when 2.0.0 is released), it should work for any version of
    + Gaim in the 2.x.x series.
    +
    + TECHNICAL DETAILS: The reason for the issues with 1.2.0 is that 1.2.0
    + was the first version of Gaim to include the masked account options
    + patch I wrote. Thus, in 1.2.0 and above, this plugin uses those
    + functions to mask passwords. As these functions didn't exist in
    + versions of Gaim before 1.2.0, the compiled binary must require 1.2.0
    + or above. See http://sf.net/support/tracker.php?aid=1108846
    + for the patch.
    +
    + MORE TECHNICAL DETAILS: The only remaining hack is that I create my
    + own GaimConversation for use by the command API. If the ordering of
    + any members in GaimConversation used by the command API changes
    + (which shouldn't happen), this plugin will almost surely crash.
    + Also, the same thing will happen if new fields are added to
    + GaimConversation and used by the command API.
    +
    + WAY TOO MUCH INFORMATION: The aforementioned crash problem can
    + currently only affect you if you use the "Operator password" or
    + "Disconnect ghosts (Duplicate nicknames)" options.
    +
    +version 0.7 (2005-02-28)
    + * Version 0.6 didn't work on Windows. Fixing that.
    +
    +version 0.6 (2005-02-27)
    + * Expanding the "Highest Connection Count" blocking which already
    + existed for QuakeNet to cover a similar notice from Undernet. I
    + thought I had included this in the changes for version 0.5.
    +
    + * Support for Gaim 2.0.0cvs
    +
    + * Except on Windows, this plugin no longer requires access to the Gaim
    + source to build. (The headers are still required, of course.)
    +
    + * Many packaging improvements (mostly for Windows)
    +
    +version 0.5 (2005-02-25)
    + * Expanding a suppression which already existed for QuakeNet to cover a
    + similar cryptic notice from Undernet.
    +
    +version 0.4 (2005-02-13)
    + * Supressing UnrealIRCD IDENT notices.
    +
    + * Suppressing ChanServ "You do not have channel operator access" notice
    +
    +version 0.3 (2005-01-30)
    + * Support for Windows
    +
    +version 0.2 (2005-01-27)
    + * Improved documentation.
    +
    + * Support for QuakeNet L bot (similiar to ChanServ).
    +
    + * Removed the dependency on masked account options. As soon as that
    + patch is accepted, I'll enable the GAIM_VERSION_CHECK code so that
    + passwords will be masked for versions of Gaim with that ability.
    +
    +version 0.1 (2005-01-27)
    + * Initial Release
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/IDEAS Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,37 @@
    +This file is a list of ideas for future development on this plugin. See the
    +PHILOSOPHY file for information on what is considered within scope.
    +
    +
    +USER FEATURES:
    +
    +NickServ (on FreeNode at least) allows accounts to be linked. When adding an
    +account, this plugin should query NickServ for linked accounts and
    +automatically build a contact.
    +
    +It should be possible to interact with IRC services via the GUI (in the Account
    +Actions menu, for example).
    +
    +
    +OPER FEATURES:
    +
    +It would be nice to have a feature where a channel op could /kick someone
    +without being opped. The /kick command would be caught by this plugin which
    +would ask ChanServ to op the user, then the /kick command would be issued,
    +then the user would be deopped automatically after a timeout had expired. (The
    +same thing would apply to other commands which require a specific permission
    +level.)
    +
    + In cases where a user has multiple accounts and issues a command like
    + /kick from one that is not a channel op, it should still work if they
    + have another account that is an IRCop or has ChanServ op privileges.
    +
    + Since this will only be activated for networks where it's been tested
    + and known for certain it'll work, it should be possible to hide
    + operator commands when the plugin knows they won't succeed.
    +
    +It would be cool to show a faded icon in the chat user list when a given user
    +could +o (or +v) themself via ChanServ. This should work for both linked
    +usernames (even if there wasn't a contact created) and contacts. In other
    +words, if someone has username_a and username_b and they're not linked with
    +NickServ, but the user has them grouped into a contact, he/she should see a
    +faded op icon for both if either one has ChanServ op privileges.
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +irchelperdir = $(PURPLE_LIBDIR)
    +
    +irchelper_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +irchelper_LTLIBRARIES = irchelper.la
    +
    +irchelper_la_SOURCES = \
    + irchelper.c
    +
    +irchelper_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/irchelper/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for irchelper plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = irchelper
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/PHILOSOPHY Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,31 @@
    +Mission Statement:
    +
    + The IRC Helper plugin exists to enhance IRC support in Pidgin in ways
    + which are inappropriate for general inclusion in Pidgin.
    +
    +Enhancing IRC support in Pidgin involves two specific areas:
    +
    + First, wrapping IRC services and other protocol oddities so that from
    + the user's perspective, IRC reacts the same as the other protocols in
    + Pidgin. The most obvious example is with NickServ. For most
    + protocols, you set a password and Pidgin authenticates you and you're
    + done. With IRC, you have to manually authenticate to NickServ (or
    + setup a Buddy Pounce). By handling the NickServ authentication, this
    + plugin enhances the users's experience by providing consistency with
    + other protocols (as well as the obvious automation by saving the
    + NickServ password).
    +
    + Second,this plugin should offer novel new features. See the IDEAS file
    + for examples.
    +
    +Most of the existing code in this plugin is specific to popular IRC servers.
    +If this code was in Pidgin, users would expect it to work everywhere, which it
    +won't. The inconsitent behavior would result in a lot of complaints to the
    +Pidgin team.
    +
    +Also, if the servers ever changed their responses (even by a single character),
    +things would break. Pidgin needs to be more stable than that. IRC is a
    +(mostly) documented protocol. While it has its quirks, IRC is fairly
    +standardized and unchanging. On the other hand, IRC services and the like are
    +not standardized and can change without notice. Pidgin can focus on the
    +standard protocol and this plugin will focus on the non-standard services, etc.
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/README Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,91 @@
    +PURPOSE
    +
    +This plugin was developed to enhance the usability of the IRC protocol in
    +Pidgin. See the PHILOSOPHY file for more details.
    +
    +
    +COMPATIBILITY
    +
    +This plugin is designed for Pidgin 2.0.0 and later. It's developed as a plugin
    +for libpurple, so it should work in other libpurple-based clients such as Finch
    +and even Adium, but this has not been tested.
    +
    +NOTE: This plugin may not compile with old versions of GTK+ or glib.
    +If that's important to you, please file a feature request with a copy of the
    +compiler errors you get when attempting to build the plugin.
    +
    +This plugin has (at some point) been tested on the following networks:
    + DALnet
    + FreeNode
    + GameSurge
    + Jeux (by BNI on irc.freenode.net)
    + QuakeNet
    + SlashNET (by uilleann@users.sf.net)
    + Undernet
    +
    +If it doesn't work on these networks, please file that as a bug.
    +
    +It may work with other networks. Users are encouraged to report networks that
    +it works on and to request support for other networks.
    +
    +
    +BUG REPORTS & FEEDBACK
    +
    +Bug reports and patches are welcome: http://plugins.guifications.org
    +
    +
    +USAGE
    +
    +Once you have the plugin installed, activate it (Tools -> Plugins). All of the
    +options are customized on a per account basis (see Accounts -> [an IRC account]
    +-> Edit Account -> Advanced).
    +
    +The plugin suppresses lots of useless messages with no configuration. However,
    +a few features require configuration. The options currently available are:
    +
    +Auth name:
    + Set a username to use when authenticating to AuthServ.
    +
    + If an auth name is not set, the account's screen name (i.e. the IRC
    + nickname) is used instead. This is normally what you want anyway.
    + This option is only necessary if your nickname and AuthServ username
    + differ.
    +
    + NOTE: This only applies to networks that use an AuthServ. It does not
    + apply to those using a NickServ, as all currently supported
    + implementations of NickServ have no concept of a auth username.
    +
    +Nick password:
    + Set a password to use when authenticating to AuthServ, NickServ or
    + Q (for QuakeNet).
    +
    + SECURITY NOTE:
    + Do not set a nick password on any network (other than those
    + listed above as supported networks) that doesn't use NickServ
    + for authentication or you could expose your credentials to a
    + user impersonating NickServ.
    +
    +Disconnect ghosts (Duplicate nicknames):
    + Disconnect duplicate copies of your nickname.
    +
    + Typically, this is used to kill ghosted usernames. A username is
    + referred to as "ghosted" when your connection to the IRC server is
    + disconnected, but the server hasn't noticed yet.
    +
    + When this option is activated and the plugin notices your username
    + immediately after signing on is different than the nickname set on the
    + account, it will ask NickServ to kill your (regular) nickname, wait for
    + NickServ to report the nickname was killed, and then change your
    + nickname to what it should be.
    +
    + NOTE:
    + This is currently not supported on QuakeNet. The QuakeNet
    + General FAQ (http://quakenet.org/faq/faq.php?c=4&f=8#8) seems
    + to imply that the Q bot does not provide the ability to
    + disconnect ghosted usernames.
    +
    +Operator password:
    + Set a password to use to become an IRCop.
    +
    + This will issue the following IRC command on signon:
    + OPER your_nickname specified_password
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/irchelper.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,1303 @@
    +/*
    + * IRC Helper Plugin for libpurple
    + *
    + * 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>
    + * Copyright (C) 2005, Anthony Sofocleous <itchysoft_ant@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., 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>
    +
    +#include <account.h>
    +#include <accountopt.h>
    +#include <cmds.h>
    +#include <connection.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <notify.h>
    +#include <plugin.h>
    +#include <pluginpref.h>
    +#include <prefs.h>
    +#include <util.h>
    +
    +#define PLUGIN_STATIC_NAME "irchelper"
    +#define PLUGIN_ID "core-rlaager-" PLUGIN_STATIC_NAME
    +
    +#define PLUGIN_AUTHOR "Richard Laager <rlaager@guifications.org>"
    +
    +
    +/*****************************************************************************
    + * Constants *
    + *****************************************************************************/
    +
    +#define IRC_PLUGIN_ID "prpl-irc"
    +
    +/* Copied from SECS_BEFORE_RESENDING_AUTORESPONSE in src/server.c */
    +#define AUTO_RESPONSE_INTERVAL 600
    +
    +#define DOMAIN_SUFFIX_FREENODE ".freenode.net"
    +#define DOMAIN_SUFFIX_FUNCOM ".funcom.com"
    +#define DOMAIN_SUFFIX_GAMESURGE ".gamesurge.net"
    +#define DOMAIN_SUFFIX_THUNDERCITY ".thundercity.org"
    +#define DOMAIN_SUFFIX_DALNET ".dal.net"
    +#define DOMAIN_SUFFIX_JEUX ".jeux.fr"
    +#define DOMAIN_SUFFIX_QUAKENET ".quakenet.org"
    +#define DOMAIN_SUFFIX_UNDERNET ".undernet.org"
    +
    +#define MESSAGE_CHANSERV_ACCESS_LIST_ADD "You have been added to the access list for "
    +/* Omit the first character of the portion after the channel name. */
    +#define MESSAGE_CHANSERV_ACCESS_LIST_ADD_WITH_LEVEL "with level ["
    +#define MESSAGE_CHANSERV_ACCESS_LIST_DEL "You have been deleted from the access list for ["
    +#define MESSAGE_CHANSERV_NO_OP_ACCESS "You do not have channel operator access to"
    +#define MESSAGE_FRENCH_ADVERTISEMENT_START "<B>Avertissement</B> : Le pseudo <B>"
    +#define MESSAGE_FRENCH_ADVERTISEMENT_END "&lt;votre pass&gt;"
    +#define MESSAGE_FRENCH_LOGIN "Login <B>r?ussi</B>"
    +#define MESSAGE_FRENCH_MAXIMUM_CONNECTION_COUNT "Maximum de connexion"
    +#define MESSAGE_FRENCH_MOTD "Message du Jour :"
    +#define MESSAGE_PURPLE_NOTICE_PREFIX "(notice) "
    +#define MESSAGE_GAMESURGE_AUTHSERV_IDENTIFIED "I recognize you."
    +#define MESSAGE_GAMESURGE_AUTHSERV_ID_FAILURE "Incorrect password; please try again."
    +#define MESSAGE_GHOST_KILLED " has been killed"
    +#define MESSAGE_INVITED " invited "
    +#define MESSAGE_LOGIN_CONNECTION_COUNT "Highest connection count"
    +#define MESSAGE_MEMOSERV_NO_NEW_MEMOS "You have no new memos"
    +#define MESSAGE_MODE_NOTICE_PREFIX "mode (+"
    +#define MESSAGE_MODE_NOTICE_SUFFIX " ) by "
    +#define MESSAGE_NICKSERV_CLOAKED " set your hostname to"
    +#define MESSAGE_NICKSERV_ID_FAILURE "Password Incorrect"
    +#define MESSAGE_NICKSERV_IDENTIFIED "Password accepted - you are now recognized"
    +#define MESSAGE_SPOOFING_YOUR_IP "*** Spoofing your IP. congrats."
    +#define MESSAGE_QUAKENET_Q_CRUFT \
    + "Remember: NO-ONE from QuakeNet will ever ask for your password. " \
    + "NEVER send your password to ANYONE except Q@CServe.quakenet.org."
    +#define MESSAGE_QUAKENET_Q_ID_FAILURE \
    + "Lastly, When you do recover your password, please choose a NEW PASSWORD, not your old one! " \
    + "See the above URL for details."
    +#define MESSAGE_QUAKENET_Q_IDENTIFIED "AUTH&apos;d successfully."
    +#define MESSAGE_UNREAL_IRCD_HOSTNAME_FOUND "*** Found your hostname"
    +#define MESSAGE_UNREAL_IRCD_HOSTNAME_LOOKUP "*** Looking up your hostname..."
    +#define MESSAGE_UNREAL_IRCD_IDENT_LOOKUP "*** Checking ident..."
    +#define MESSAGE_UNREAL_IRCD_IDENT_NO_RESPONSE "*** No ident response; username prefixed with ~"
    +#define MESSAGE_UNREAL_IRCD_PONG_CRUFT \
    + "*** If you are having problems connecting due to ping timeouts, please type /quote pong"
    +
    +/* Generic AuthServ, not currently used for any networks. */
    +#define NICK_AUTHSERV "AuthServ"
    +
    +#define NICK_CHANSERV "ChanServ"
    +#define NICK_FREENODE_CONNECT "freenode-connect"
    +#define NICK_FUNCOM_Q_SERVICE NICK_QUAKENET_Q "@cserve.funcom.com"
    +#define NICK_GAMESURGE_AUTHSERV "AuthServ"
    +#define NICK_GAMESURGE_AUTHSERV_SERVICE NICK_GAMESURGE_AUTHSERV "@Services.GameSurge.net"
    +#define NICK_GAMESURGE_GLOBAL "Global"
    +#define NICK_JEUX_Z "Z"
    +#define NICK_JEUX_WELCOME "[Welcome]"
    +#define NICK_MEMOSERV "MemoServ"
    +#define NICK_NICKSERV "NickServ"
    +#define NICK_DALNET_AUTHSERV_SERVICE NICK_NICKSERV "@services.dal.net"
    +#define NICK_QUAKENET_L "L"
    +#define NICK_QUAKENET_Q "Q"
    +#define NICK_QUAKENET_Q_SERVICE NICK_QUAKENET_Q "@CServe.quakenet.org"
    +#define NICK_UNDERNET_X "x@channels.undernet.org"
    +
    +/* The %c exists so we can tell if a match occurred. */
    +#define PATTERN_WEIRD_LOGIN_CRUFT "o%c %*u ca %*u(%*u) ft %*u(%*u)"
    +
    +#define TIMEOUT_IDENTIFY 4000
    +#define TIMEOUT_KILLING_GHOST 4000
    +
    +
    +typedef enum {
    + IRC_NONE = 0x0000,
    + IRC_KILLING_GHOST = 0x0001,
    + IRC_WILL_ID = 0x0002,
    + IRC_DID_ID = 0x0004,
    + IRC_ID_FAILED = 0x0008,
    +
    + IRC_NETWORK_TYPE_UNKNOWN = 0x0010,
    + IRC_NETWORK_TYPE_GAMESURGE = 0x0020,
    + IRC_NETWORK_TYPE_NICKSERV = 0x0040,
    + IRC_NETWORK_TYPE_QUAKENET = 0x0080,
    + IRC_NETWORK_TYPE_JEUX = 0x0100,
    + IRC_NETWORK_TYPE_UNDERNET = 0x0200,
    + IRC_NETWORK_TYPE_THUNDERCITY = 0x0400,
    + IRC_NETWORK_TYPE_DALNET = 0x0800,
    + IRC_NETWORK_TYPE_FUNCOM = 0x1000,
    +} IRCHelperStateFlags;
    +
    +struct proto_stuff
    +{
    + gpointer *proto_data;
    + PurpleAccount *account;
    +};
    +
    +GHashTable *states;
    +
    +/*****************************************************************************
    + * Prototypes *
    + *****************************************************************************/
    +
    +static gboolean plugin_load(PurplePlugin *plugin);
    +static gboolean plugin_unload(PurplePlugin *plugin);
    +
    +
    +/*****************************************************************************
    + * Plugin Info *
    + *****************************************************************************/
    +
    +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 */
    + 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 */
    +};
    +
    +/* XXX: This is a dirty hack. It's better than what I was doing before, though. */
    +static PurpleConversation *get_conversation(PurpleAccount *account)
    +{
    + PurpleConversation *conv;
    +
    + conv = g_new0(PurpleConversation, 1);
    + conv->type = PURPLE_CONV_TYPE_IM;
    + /* 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;
    +}
    +
    +static IRCHelperStateFlags get_connection_type(PurpleConnection *connection)
    +{
    + PurpleAccount *account;
    + const gchar *protocol;
    + gchar *username;
    + IRCHelperStateFlags type = IRC_NETWORK_TYPE_UNKNOWN;
    +
    + g_return_val_if_fail(NULL != connection, IRC_NETWORK_TYPE_UNKNOWN);
    +
    + account = purple_connection_get_account(connection);
    +
    + protocol = purple_account_get_protocol_id(account);
    + g_return_val_if_fail(g_str_equal(protocol, IRC_PLUGIN_ID), IRC_NETWORK_TYPE_UNKNOWN);
    +
    + username = g_utf8_strdown(purple_account_get_username(account), -1);
    +
    + if (g_str_has_suffix(username, DOMAIN_SUFFIX_GAMESURGE))
    + type = IRC_NETWORK_TYPE_GAMESURGE;
    + else if (g_str_has_suffix(username, DOMAIN_SUFFIX_THUNDERCITY))
    + type = IRC_NETWORK_TYPE_THUNDERCITY;
    + else if (g_str_has_suffix(username, DOMAIN_SUFFIX_DALNET))
    + type = IRC_NETWORK_TYPE_DALNET;
    + else if (g_str_has_suffix(username, DOMAIN_SUFFIX_QUAKENET))
    + type = IRC_NETWORK_TYPE_QUAKENET;
    + else if (g_str_has_suffix(username, DOMAIN_SUFFIX_FUNCOM))
    + type = IRC_NETWORK_TYPE_FUNCOM;
    + else if (g_str_has_suffix(username, DOMAIN_SUFFIX_JEUX))
    + type = IRC_NETWORK_TYPE_JEUX;
    + else if (g_str_has_suffix(username, DOMAIN_SUFFIX_UNDERNET))
    + type = IRC_NETWORK_TYPE_UNDERNET;
    +
    + g_free(username);
    + return type;
    +}
    +
    +static gboolean auth_timeout(gpointer proto_data)
    +{
    + IRCHelperStateFlags state;
    +
    + state = GPOINTER_TO_INT(g_hash_table_lookup(states, proto_data));
    + if (state & IRC_WILL_ID)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Authentication failed: timeout expired\n");
    + g_hash_table_insert(states, proto_data,
    + GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_DID_ID));
    + }
    +
    + return FALSE;
    +}
    +
    +
    +/*****************************************************************************
    + * AuthServ Helper Functions *
    + *****************************************************************************/
    +
    +static void authserv_identify(const char *command, PurpleConnection *connection, IRCHelperStateFlags state)
    +{
    + PurpleAccount *account;
    + gchar **userparts = NULL;
    + const gchar *username;
    + const gchar *password;
    +
    + g_return_if_fail(NULL != connection);
    +
    + account = purple_connection_get_account(connection);
    +
    + username = purple_account_get_string(account, PLUGIN_ID "_authname", "");
    + if (NULL == username || '\0' == *username)
    + {
    + userparts = g_strsplit(purple_account_get_username(account), "@", 2);
    + username = userparts[0];
    + }
    +
    + password = purple_account_get_string(account, PLUGIN_ID "_nickpassword", "");
    +
    + if (NULL != username && '\0' != *username &&
    + NULL != password && '\0' != *password)
    + {
    + const gchar *authserv = NICK_AUTHSERV;
    + gchar *authentication = g_strconcat(command, " ", username, " ", password, NULL);
    +
    + purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: %s %s <PASSWORD>\n", command, username);
    +
    + g_hash_table_insert(states,
    + connection->proto_data,
    + GINT_TO_POINTER(state | IRC_WILL_ID));
    +
    + if (state & IRC_NETWORK_TYPE_GAMESURGE)
    + authserv = NICK_GAMESURGE_AUTHSERV_SERVICE;
    + else if (state & IRC_NETWORK_TYPE_DALNET)
    + authserv = NICK_DALNET_AUTHSERV_SERVICE;
    + else if (state & IRC_NETWORK_TYPE_QUAKENET)
    + authserv = NICK_QUAKENET_Q_SERVICE;
    + else if (state & IRC_NETWORK_TYPE_FUNCOM)
    + authserv = NICK_FUNCOM_Q_SERVICE;
    + else if (state & IRC_NETWORK_TYPE_UNDERNET)
    + authserv = NICK_UNDERNET_X;
    +
    + serv_send_im(connection, authserv, authentication, 0);
    +
    + g_free(authentication);
    +
    + /* Register a timeout... If we don't get the expected response from AuthServ,
    + * we need to stop suppressing messages from it at some point or the user
    + * could be very confused.
    + */
    + purple_timeout_add(TIMEOUT_IDENTIFY, (GSourceFunc)auth_timeout,
    + (gpointer)connection->proto_data);
    + }
    +
    + g_strfreev(userparts);
    +}
    +
    +
    +/*****************************************************************************
    + * Operator Helper Functions *
    + *****************************************************************************/
    +
    +static void jeux_identify(PurpleConnection *connection, IRCHelperStateFlags state)
    +{
    + PurpleAccount *account;
    + gchar **userparts;
    + const gchar *username;
    + const gchar *password;
    +
    + g_return_if_fail(NULL != connection);
    +
    + account = purple_connection_get_account(connection);
    +
    + userparts = g_strsplit(purple_account_get_username(account), "@", 2);
    + username = userparts[0];
    + password = purple_account_get_string(account, PLUGIN_ID "_nickpassword", "");
    +
    + if (NULL != username && '\0' != *username &&
    + NULL != password && '\0' != *password)
    + {
    + gchar *authentication = g_strdup_printf("quote %s login %s %s", NICK_JEUX_Z, username, password);
    + PurpleConversation *conv = get_conversation(account);
    + gchar *error;
    +
    + purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: quote %s login %s <PASSWORD>\n", NICK_JEUX_Z, username);
    +
    + g_hash_table_insert(states,
    + connection->proto_data,
    + GINT_TO_POINTER(state | IRC_WILL_ID));
    +
    + if (purple_cmd_do_command(conv, authentication, authentication, &error) != PURPLE_CMD_STATUS_OK)
    + {
    + /* TODO: PRINT ERROR MESSAGE */
    + g_free(error);
    + }
    + g_free(conv);
    + g_free(authentication);
    +
    + /* Register a timeout... If we don't get the expected response from AuthServ,
    + * we need to stop suppressing messages from it at some point or the user
    + * could be very confused.
    + */
    + purple_timeout_add(TIMEOUT_IDENTIFY, (GSourceFunc)auth_timeout,
    + (gpointer)connection->proto_data);
    + }
    +
    + g_strfreev(userparts);
    +}
    +
    +
    +/*****************************************************************************
    + * Operator Helper Functions *
    + *****************************************************************************/
    +
    + static void oper_identify(PurpleAccount *account)
    + {
    + const char *operpassword;
    +
    + operpassword = purple_account_get_string(account, PLUGIN_ID "_operpassword", "");
    + if ('\0' != *operpassword)
    + {
    + PurpleConversation *conv = get_conversation(account);
    + PurpleConnection *connection = purple_account_get_connection(account);
    + const gchar *name = purple_connection_get_display_name(connection);
    + char *command = g_strdup_printf("quote OPER %s %s", name, operpassword);
    + gchar *error;
    +
    + if (purple_cmd_do_command(conv, command, command, &error) != PURPLE_CMD_STATUS_OK)
    + {
    + /* TODO: PRINT ERROR MESSAGE */
    + g_free(error);
    + }
    +
    + g_free(command);
    + g_free(conv);
    + }
    +
    + }
    +
    +
    +/*****************************************************************************
    + * NickServ Helper Functions *
    + *****************************************************************************/
    +
    +static void nickserv_do_identify(char *authentication, gpointer proto_data, PurpleConnection *gc, const char *nickpassword)
    +{
    + PurpleConversation *conv = get_conversation(purple_connection_get_account(gc));
    + gchar *error;
    + gchar *tmp;
    +
    + purple_debug_misc(PLUGIN_STATIC_NAME, "Sending authentication: %s <PASSWORD>\n", authentication);
    +
    + tmp = g_strconcat(authentication, " ", nickpassword, NULL);
    + g_free(authentication);
    + authentication = tmp;
    +
    + if (purple_cmd_do_command(conv, authentication, authentication, &error) != PURPLE_CMD_STATUS_OK)
    + {
    + /* TODO: PRINT ERROR MESSAGE */
    + g_free(error);
    + }
    + g_free(authentication);
    + g_free(conv);
    +
    + /* Register a timeout... If we don't get the expected response from NickServ,
    + * we need to stop suppressing messages from it at some point or the user
    + * could be very confused.
    + */
    + purple_timeout_add(TIMEOUT_IDENTIFY, (GSourceFunc)auth_timeout,
    + (gpointer)proto_data);
    +}
    +
    +static void nickserv_identify(gpointer proto_data, PurpleConnection *gc, const char *nickpassword)
    +{
    + char *authentication = g_strdup_printf("quote %s IDENTIFY", NICK_NICKSERV);
    + nickserv_do_identify(authentication, proto_data, gc, nickpassword);
    +}
    +
    +static void nickserv_msg_identify(const char *command, gpointer proto_data, PurpleConnection *gc, const char *nickpassword)
    +{
    + char *authentication = g_strdup_printf("quote PRIVMSG %s : %s", NICK_NICKSERV, command);
    + nickserv_do_identify(authentication, proto_data, gc, nickpassword);
    +}
    +
    +static gboolean ghosted_nickname_killed_cb(struct proto_stuff *stuff)
    +{
    + PurpleConnection *gc;
    + char **userparts;
    + IRCHelperStateFlags state;
    + PurpleConversation *conv;
    + char *command;
    + gchar *error;
    +
    + state = GPOINTER_TO_INT(g_hash_table_lookup(states, stuff->proto_data));
    +
    + /* We only want this function to act once. Under normal circumstances,
    + * it's going to get called twice: Once when NickServ tells us the ghost
    + * has been killed and once when the timeout expires. Under abnormal
    + * conditions, it will only be called when the timeout expires.
    + */
    + if (!(state & IRC_KILLING_GHOST))
    + {
    + g_free(stuff);
    + return FALSE;
    + }
    + g_hash_table_insert(states, stuff->proto_data,
    + GINT_TO_POINTER((state & ~IRC_KILLING_GHOST) | IRC_WILL_ID));
    +
    + gc = purple_account_get_connection(stuff->account);
    + if (NULL == gc)
    + {
    + g_free(stuff);
    + return FALSE;
    + }
    +
    + userparts = g_strsplit(purple_account_get_username(stuff->account), "@", 2);
    +
    + /* Switch back to the normal nickname. */
    + conv = get_conversation(stuff->account);
    +
    + command = g_strdup_printf("nick %s", userparts[0]);
    +
    + if (purple_cmd_do_command(conv, command, command, &error) != PURPLE_CMD_STATUS_OK)
    + {
    + /* TODO: PRINT ERROR MESSAGE */
    + g_free(error);
    + }
    +
    + g_free(command);
    + g_free(conv);
    +
    + nickserv_identify(stuff->proto_data, gc, purple_account_get_string(stuff->account,
    + PLUGIN_ID "_nickpassword", ""));
    +
    + g_strfreev(userparts);
    + g_free(stuff);
    +
    + oper_identify(stuff->account);
    +
    + return FALSE;
    +}
    +
    +
    +/*****************************************************************************
    + * Callbacks *
    + *****************************************************************************/
    +
    +static void signed_on_cb(PurpleConnection *connection)
    +{
    + PurpleAccount *account;
    + IRCHelperStateFlags state;
    + const char *nickpassword;
    +
    + g_return_if_fail(NULL != connection);
    + g_return_if_fail(NULL != connection->proto_data);
    +
    + account = purple_connection_get_account(connection);
    +
    + g_return_if_fail(NULL != account);
    + if (!g_str_equal(purple_account_get_protocol_id(account), IRC_PLUGIN_ID))
    + return;
    +
    + state = get_connection_type(connection);
    +
    + if (state & IRC_NETWORK_TYPE_GAMESURGE)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Connected with GameSurge: %s\n",
    + purple_connection_get_display_name(connection));
    +
    + authserv_identify("AUTH", connection, state);
    + }
    + if (state & IRC_NETWORK_TYPE_DALNET)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Connected with DalNet: %s\n",
    + purple_connection_get_display_name(connection));
    +
    + authserv_identify("IDENTIFY", connection, state);
    + }
    + else if (state & IRC_NETWORK_TYPE_JEUX)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Connected with Jeux.fr: %s\n",
    + purple_connection_get_display_name(connection));
    +
    + jeux_identify(connection, state);
    + }
    + else if (state & IRC_NETWORK_TYPE_QUAKENET)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Connected with QuakeNet: %s\n",
    + purple_connection_get_display_name(connection));
    +
    + authserv_identify("AUTH", connection, state);
    + }
    + else if (state & IRC_NETWORK_TYPE_UNDERNET)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Connected with UnderNet: %s\n",
    + purple_connection_get_display_name(connection));
    +
    + authserv_identify("login ", connection, state);
    + }
    + else if (state & IRC_NETWORK_TYPE_FUNCOM)
    + {
    + purple_debug_info(PLUGIN_STATIC_NAME, "Connected with Funcom: %s\n",
    + purple_connection_get_display_name(connection));
    +
    + authserv_identify("AUTH", connection, state);
    + }
    + else
    + {
    + nickpassword = purple_account_get_string(account, PLUGIN_ID "_nickpassword", "");
    + if ('\0' != *nickpassword)
    + {
    + char **userparts;
    +
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER(IRC_NETWORK_TYPE_NICKSERV | IRC_WILL_ID));
    +
    + userparts = g_strsplit(purple_account_get_username(account), "@", 2);
    +
    + if (purple_account_get_bool(account, PLUGIN_ID "_disconnectghosts", 0) &&
    + strcmp(userparts[0], purple_connection_get_display_name(connection)))
    + {
    + struct proto_stuff *stuff = g_new0(struct proto_stuff, 1);
    + char *command;
    + PurpleConversation *conv;
    + gchar *error;
    +
    + stuff->proto_data = connection->proto_data;
    + stuff->account = account;
    +
    + /* Disconnect the ghosted connection. */
    + command = g_strdup_printf("quote %s GHOST %s %s", NICK_NICKSERV, userparts[0], nickpassword);
    + conv = get_conversation(account);
    +
    + purple_debug_misc(PLUGIN_STATIC_NAME, "Sending command: quote %s GHOST %s <PASSWORD>\n", NICK_NICKSERV, userparts[0]);
    +
    + if (purple_cmd_do_command(conv, command, command, &error) != PURPLE_CMD_STATUS_OK)
    + {
    + /* TODO: PRINT ERROR MESSAGE */
    + g_free(error);
    + }
    + g_free(command);
    + g_free(conv);
    +
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER(IRC_NETWORK_TYPE_NICKSERV | IRC_KILLING_GHOST));
    +
    + /* We have to wait for the server to disconnect the
    + * other username before we can reclaim it. This
    + * timeout sets an upper bound on the length of time
    + * we'll wait for the ghost to be killed.
    + */
    + purple_timeout_add(TIMEOUT_KILLING_GHOST,
    + (GSourceFunc)ghosted_nickname_killed_cb,
    + (gpointer)stuff);
    +
    + g_strfreev(userparts);
    + return;
    + }
    +
    + g_strfreev(userparts);
    +
    +
    + if (state & IRC_NETWORK_TYPE_THUNDERCITY)
    + nickserv_msg_identify("AUTH", connection->proto_data, connection, nickpassword);
    + else
    + nickserv_identify(connection->proto_data, connection, nickpassword);
    + }
    + }
    +
    + oper_identify(account);
    +}
    +
    +static void conversation_created_cb(PurpleConversation *conv)
    +{
    + purple_conversation_set_data(conv, PLUGIN_ID "_start_time",
    + GINT_TO_POINTER((int)time(NULL)));
    +}
    +
    +static gboolean writing_chat_msg_cb(PurpleAccount *account, const char *who,
    + char **message, PurpleConversation *conv,
    + PurpleMessageFlags flags)
    +{
    + const gchar *name;
    + const gchar *topic;
    +
    + if (!g_str_equal(purple_account_get_protocol_id(account), IRC_PLUGIN_ID))
    + return FALSE;
    +
    + if (NULL == *message)
    + return FALSE;
    +
    + g_return_val_if_fail(purple_conversation_get_type(conv) == PURPLE_CONV_TYPE_CHAT, FALSE);
    +
    + if (flags & PURPLE_MESSAGE_SYSTEM &&
    + (g_str_has_prefix(*message, MESSAGE_MODE_NOTICE_PREFIX "v ") ||
    + g_str_has_prefix(*message, MESSAGE_MODE_NOTICE_PREFIX "o ")))
    + {
    + const char *tmp = *message + sizeof(MESSAGE_MODE_NOTICE_PREFIX "X ") - 1;
    + const char *name = purple_connection_get_display_name(purple_account_get_connection(account));
    +
    + if (g_str_has_prefix(tmp, name) &&
    + g_str_has_prefix(tmp + strlen(name), MESSAGE_MODE_NOTICE_SUFFIX NICK_CHANSERV))
    + {
    + if (time(NULL) < (time_t)GPOINTER_TO_INT(purple_conversation_get_data(conv, PLUGIN_ID "_start_time")) + 10)
    + return TRUE;
    + }
    + }
    +
    + if (flags & PURPLE_MESSAGE_SYSTEM &&
    + (topic = purple_conv_chat_get_topic(PURPLE_CONV_CHAT(conv))) != NULL &&
    + (name = purple_conversation_get_name(conv)) != NULL)
    + {
    + char *name_escaped = g_markup_escape_text(name, -1);
    + char *topic_escaped = g_markup_escape_text(topic, -1);
    + char *topic_linkified = purple_markup_linkify(topic_escaped);
    +
    + if (strstr(*message, name_escaped) != NULL &&
    + strstr(*message, topic_linkified) != NULL)
    + {
    + /* We've got a topic notice. */
    + PurpleBlistNode *node;
    +
    + if ((node = (PurpleBlistNode *)purple_blist_find_chat(account, name)) != NULL)
    + {
    + const char *last_topic = purple_blist_node_get_string(node, PLUGIN_ID "_topic");
    +
    + /* If we saw this the last time we joined, suppress it. */
    + if (last_topic != NULL && strcmp(topic, last_topic) == 0)
    + {
    + g_free(name_escaped);
    + g_free(topic_escaped);
    + g_free(topic_linkified);
    +
    + return TRUE;
    + }
    + else
    + purple_blist_node_set_string(node, PLUGIN_ID "_topic", topic);
    + }
    + }
    + g_free(name_escaped);
    + g_free(topic_escaped);
    + g_free(topic_linkified);
    + }
    +
    + return FALSE;
    +}
    +
    +static GSList *auto_responses = NULL;
    +
    +struct auto_response {
    + PurpleConnection *gc;
    + char *name;
    + time_t received;
    + char *message;
    +};
    +
    +static gboolean
    +expire_auto_responses(gpointer data)
    +{
    + GSList *tmp, *cur;
    + struct auto_response *ar;
    +
    + tmp = auto_responses;
    +
    + while (tmp) {
    + cur = tmp;
    + tmp = tmp->next;
    + ar = (struct auto_response *)cur->data;
    +
    + if ((time(NULL) - ar->received) > AUTO_RESPONSE_INTERVAL) {
    + auto_responses = g_slist_remove(auto_responses, ar);
    +
    + g_free(ar->message);
    + g_free(ar);
    + }
    + }
    +
    + return FALSE; /* do not run again */
    +}
    +
    +static struct auto_response *
    +get_auto_response(PurpleConnection *gc, const char *name)
    +{
    + GSList *tmp;
    + struct auto_response *ar;
    +
    + purple_timeout_add((AUTO_RESPONSE_INTERVAL + 1) * 1000, expire_auto_responses, NULL);
    +
    + tmp = auto_responses;
    +
    + while (tmp) {
    + ar = (struct auto_response *)tmp->data;
    +
    + if (gc == ar->gc && !strncmp(name, ar->name, sizeof(ar->name)))
    + return ar;
    +
    + tmp = tmp->next;
    + }
    +
    + ar = (struct auto_response *)g_new0(struct auto_response, 1);
    + ar->name = g_strdup(name);
    + ar->gc = gc;
    + ar->received = 0;
    + auto_responses = g_slist_prepend(auto_responses, ar);
    +
    + return ar;
    +}
    +
    +static gboolean receiving_im_msg_cb(PurpleAccount *account, gchar **sender, gchar **buffer,
    + PurpleConversation *conv,
    + gint *flags, gpointer data)
    +{
    + gchar *msg;
    + gchar *nick;
    + PurpleConversation *chat;
    + PurpleConnection *connection;
    + IRCHelperStateFlags state;
    + gchar *invite_prefix;
    +
    + if (!g_str_equal(purple_account_get_protocol_id(account), IRC_PLUGIN_ID))
    + return FALSE;
    +
    + msg = *buffer;
    + nick = *sender;
    +
    + connection = purple_account_get_connection(account);
    + g_return_val_if_fail(NULL != connection, FALSE);
    + state = GPOINTER_TO_INT(g_hash_table_lookup(states, connection->proto_data));
    +
    + /* SUPPRESS EXTRA AUTO-RESPONSES */
    + if (*flags & PURPLE_MESSAGE_AUTO_RESP)
    + {
    + /* The same idea as the code in src/server.c. */
    + struct auto_response *ar = get_auto_response(connection, nick);
    + time_t now = time(NULL);
    +
    + if ((now - ar->received) <= AUTO_RESPONSE_INTERVAL &&
    + !strcmp(msg, ar->message))
    + {
    + /* We've recently received an auto-response
    + * and it's the same as the one we've just received.
    + * Drop it!
    + */
    + ar->received = now;
    + return TRUE;
    + }
    +
    + ar->received = now;
    + g_free(ar->message);
    + ar->message = g_strdup(msg);
    +
    + /* None of the other rules are for auto-responses. */
    + return FALSE;
    + }
    +
    +
    + /* SIMPLE SUPPRESSION RULES */
    +
    + /* Suppress the FreeNode stats collection bot. */
    + if (g_str_equal(nick, NICK_FREENODE_CONNECT))
    + {
    + return TRUE;
    + }
    +
    + /* Suppress useless ChanServ notification. */
    + if (g_str_equal(nick, NICK_CHANSERV) &&
    + g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_NO_OP_ACCESS))
    + {
    +
    + return TRUE;
    + }
    +
    + /* Suppress GameSurge message(s) of the day. */
    + if (state & IRC_NETWORK_TYPE_GAMESURGE &&
    + g_str_equal(nick, NICK_GAMESURGE_GLOBAL))
    + {
    + return TRUE;
    + }
    +
    + /* Suppress useless Jeux welcome. */
    + if (g_str_equal(nick, NICK_JEUX_WELCOME))
    + {
    + return TRUE;
    + }
    +
    + /* Suppress useless MemoServ notification. */
    + if (g_str_equal(nick, NICK_MEMOSERV) &&
    + g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_MEMOSERV_NO_NEW_MEMOS))
    + {
    +
    + return TRUE;
    + }
    +
    + /* Suppress QuakeNet Q password warning. */
    + if (g_str_equal(nick, NICK_QUAKENET_Q) &&
    + g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_CRUFT))
    + {
    +
    + return TRUE;
    + }
    +
    + /* Suppress Z registration notice. */
    + if (g_str_equal(nick, "Z") &&
    + g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FRENCH_ADVERTISEMENT_START) &&
    + g_str_has_suffix(msg, MESSAGE_FRENCH_ADVERTISEMENT_END))
    + {
    + return TRUE;
    + }
    +
    + /* Suppress Z's successful login notice. */
    + if (g_str_equal(nick, "Z") &&
    + (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FRENCH_LOGIN) ||
    + g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_FRENCH_MOTD)))
    + {
    + return TRUE;
    + }
    +
    + /* Suppress login message for the highest connection count. */
    + if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX
    + MESSAGE_LOGIN_CONNECTION_COUNT) ||
    + g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX
    + MESSAGE_FRENCH_MAXIMUM_CONNECTION_COUNT))
    + {
    + return TRUE;
    + }
    +
    + /* Suppress UnrealIRCd login cruft. */
    + if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_HOSTNAME_FOUND) ||
    + g_str_equal( msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_HOSTNAME_LOOKUP) ||
    + g_str_equal( msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_IDENT_LOOKUP) ||
    + g_str_equal( msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_IDENT_NO_RESPONSE) ||
    + g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_UNREAL_IRCD_PONG_CRUFT))
    + {
    +
    + return TRUE;
    + }
    +
    + /* Suppress hostname cloak notification on Freenode. */
    + if (g_str_has_suffix(nick, DOMAIN_SUFFIX_FREENODE) &&
    + g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX NICK_NICKSERV MESSAGE_NICKSERV_CLOAKED))
    + {
    +
    + return TRUE;
    + }
    +
    + /* Suppress "IP spoofing" notice on worldforge.org:
    + * http://plugins.guifications.org/trac/ticket/524 */
    + if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_SPOOFING_YOUR_IP))
    + {
    + return TRUE;
    + }
    +
    + /* SLIGHTLY COMPLICATED SUPPRESSION RULES */
    +
    + /* Supress QuakeNet and UnderNet Weird Login Cruft */
    + {
    + char temp;
    + if (sscanf(msg, MESSAGE_PURPLE_NOTICE_PREFIX PATTERN_WEIRD_LOGIN_CRUFT, &temp) == 1)
    + {
    +
    + return TRUE;
    + }
    + }
    +
    + /* Suppress silly notices of my own invites. */
    + invite_prefix = g_strconcat(MESSAGE_PURPLE_NOTICE_PREFIX,
    + purple_connection_get_display_name(connection), MESSAGE_INVITED, NULL);
    + if (g_str_has_prefix(msg, invite_prefix))
    + {
    + g_free(invite_prefix);
    + return TRUE;
    + }
    + g_free(invite_prefix);
    +
    +
    + /* SERVICE MAGIC */
    +
    + /* Display ChanServ Access List Add Notifications in the chat window. */
    + if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_ADD) &&
    + g_str_equal(nick, NICK_CHANSERV))
    + {
    + gchar *tmp;
    + gchar *channel;
    + gchar *level = NULL;
    + gchar *msg2;
    +
    + channel = purple_markup_strip_html(msg + sizeof(MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_ADD) - 1);
    + if ((tmp = strchr(channel, ' ')) != NULL)
    + {
    + *tmp = '\0';
    + if (g_str_has_prefix(tmp + 1, MESSAGE_CHANSERV_ACCESS_LIST_ADD_WITH_LEVEL))
    + {
    + /* The + 1 and - 1 are both kept to make it clear how that relates with other code. */
    + level = tmp + 1 + sizeof(MESSAGE_CHANSERV_ACCESS_LIST_ADD_WITH_LEVEL) - 1;
    + if ((tmp = strchr(level, ']')) != NULL)
    + *tmp = '\0';
    + }
    + }
    +
    + if (NULL == level)
    + msg2 = g_strdup(_("You have been added to the access list."));
    + else
    + msg2 = g_strdup_printf(_("You have been added to the access list with an access level of %s."), level);
    +
    + chat = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, channel, account);
    +
    + if (chat)
    + {
    + purple_conv_chat_write(PURPLE_CONV_CHAT(chat), nick,
    + msg2, PURPLE_MESSAGE_SYSTEM, time(NULL));
    + g_free(channel);
    + g_free(msg2);
    + return TRUE;
    + }
    +
    + g_free(channel);
    + g_free(msg2);
    + return FALSE;
    + }
    +
    + /* Display ChanServ Access List Delete Notifications in the chat window. */
    + if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_DEL) &&
    + g_str_equal(nick, NICK_CHANSERV))
    + {
    + gchar *tmp;
    + gchar *channel;
    +
    + channel = purple_markup_strip_html(msg + sizeof(MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_CHANSERV_ACCESS_LIST_DEL) - 1);
    + if ((tmp = strchr(channel, ']')) != NULL)
    + *tmp = '\0';
    +
    + chat = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, channel, account);
    +
    + if (chat)
    + {
    + purple_conv_chat_write(PURPLE_CONV_CHAT(chat), nick,
    + _("You have been removed from the access list."),
    + PURPLE_MESSAGE_SYSTEM, time(NULL));
    + g_free(channel);
    + return TRUE;
    + }
    +
    + g_free(channel);
    + return FALSE;
    + }
    +
    + /* Display ChanServ channel join messages in the chat window. */
    + if (g_str_has_prefix(msg, MESSAGE_PURPLE_NOTICE_PREFIX "[#") &&
    + (g_str_equal(nick, NICK_CHANSERV) || g_str_equal(nick, NICK_QUAKENET_L)))
    + {
    + gchar *msg2;
    + gchar *msg3;
    + gchar *channel;
    +
    + /* Duplicate the message so we can modify the string in place. */
    + msg2 = g_strdup(msg);
    +
    + /* We need to keep a pointer to the beginning of the string so we can free it later. */
    + msg3 = msg2;
    +
    + /* channel needs to start with the # */
    + channel = msg3;
    + channel += sizeof(MESSAGE_PURPLE_NOTICE_PREFIX);
    +
    + /* Find the "]", set it to the null character.
    + * This makes chan contain just the channel.
    + * Then, increment msg3 two spots so it contains
    + * just the message.
    + */
    + if ((msg3 = g_strstr_len(msg3, strlen(msg3), "]")))
    + {
    + PurpleBlistNode *node;
    +
    + *msg3 = '\0';
    +
    + /* Make sure it's safe to increment the pointer */
    + if ('\0' == msg3[1] || '\0' == msg3[2])
    + {
    + g_free(msg2);
    + return FALSE;
    + }
    +
    + msg3 += 2;
    +
    + if ((node = (PurpleBlistNode *)purple_blist_find_chat(account, channel)) != NULL)
    + {
    + const char *last_msg = purple_blist_node_get_string(node, PLUGIN_ID "_chanserv_join_msg");
    +
    + /* If we saw this the last time we joined, suppress it. */
    + if (last_msg != NULL && strcmp(msg3, last_msg) == 0)
    + {
    + g_free(msg2);
    + return TRUE;
    + }
    + else
    + purple_blist_node_set_string(node, PLUGIN_ID "_chanserv_join_msg", msg3);
    + }
    +
    + /* Write the message to the chat as a system message. */
    + chat = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, channel, account);
    +
    + if (chat)
    + {
    + purple_conv_chat_write(PURPLE_CONV_CHAT(chat), nick,
    + msg3, PURPLE_MESSAGE_SYSTEM, time(NULL));
    +
    + g_free(msg2);
    + return TRUE;
    + }
    + }
    + g_free(msg2);
    + return FALSE;
    + }
    +
    + /* Suppress useless NickServ notifications if we're identifying automatically. */
    + if (state & IRC_NETWORK_TYPE_NICKSERV &&
    + (state & IRC_WILL_ID || state & IRC_KILLING_GHOST) &&
    + g_str_equal(nick, NICK_NICKSERV))
    + {
    +
    + /* Track that the identification is finished. */
    + if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_NICKSERV_IDENTIFIED))
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER((state & ~IRC_KILLING_GHOST & ~IRC_WILL_ID)
    + | IRC_DID_ID));
    +
    + /* The ghost has been killed, continue the signon. */
    + if (state & IRC_KILLING_GHOST && strstr(msg, MESSAGE_GHOST_KILLED))
    + {
    +
    + struct proto_stuff *stuff = g_new0(struct proto_stuff, 1);
    + stuff->proto_data = connection->proto_data;
    + stuff->account = account;
    +
    + ghosted_nickname_killed_cb(stuff);
    + }
    +
    + /* The identification has failed. */
    + if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_NICKSERV_ID_FAILURE))
    + {
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER((state & ~IRC_KILLING_GHOST & ~IRC_WILL_ID)
    + | IRC_ID_FAILED));
    +
    + purple_notify_error(NULL,
    + _("NickServ Authentication Error"),
    + _("Error authenticating with NickServ"),
    + _("Check your password."));
    + }
    +
    + return TRUE;
    + }
    +
    + /* Suppress useless AuthServ notifications if we're identifying automatically. */
    + if (state & IRC_NETWORK_TYPE_GAMESURGE &&
    + state & IRC_WILL_ID &&
    + g_str_equal(nick, NICK_GAMESURGE_AUTHSERV))
    + {
    +
    + /* Track that the identification is finished. */
    + if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_GAMESURGE_AUTHSERV_IDENTIFIED))
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_DID_ID));
    +
    + /* The identification has failed. */
    + if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_GAMESURGE_AUTHSERV_ID_FAILURE))
    + {
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_ID_FAILED));
    +
    + purple_notify_error(NULL,
    + _("GameSurge Authentication Error"),
    + _("Error authenticating with AuthServ"),
    + _("Check your password."));
    + }
    +
    + return TRUE;
    + }
    +
    + /* Suppress useless Q notifications if we're identifying automatically. */
    + if ((state & IRC_NETWORK_TYPE_QUAKENET ||
    + state & IRC_NETWORK_TYPE_FUNCOM) &&
    + state & IRC_WILL_ID &&
    + g_str_equal(nick, NICK_QUAKENET_Q))
    + {
    +
    + /* Track that the identification is finished. */
    + if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_IDENTIFIED))
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_DID_ID));
    +
    + /* The identification has failed. */
    + if (g_str_equal(msg, MESSAGE_PURPLE_NOTICE_PREFIX MESSAGE_QUAKENET_Q_ID_FAILURE))
    + {
    + g_hash_table_insert(states, connection->proto_data,
    + GINT_TO_POINTER((state & ~IRC_WILL_ID) | IRC_ID_FAILED));
    +
    + purple_notify_error(NULL,
    + _("QuakeNet Authentication Error"),
    + _("Error authenticating with Q"),
    + _("Check your password."));
    + }
    +
    + return TRUE;
    + }
    +
    + return FALSE;
    +}
    +
    +
    +/*****************************************************************************
    + * Plugin Code *
    + *****************************************************************************/
    +
    +static gboolean plugin_load(PurplePlugin *plugin)
    +{
    + PurplePlugin *irc_prpl;
    + PurplePluginProtocolInfo *prpl_info;
    + PurpleAccountOption *option;
    + void *conn_handle;
    + void *conv_handle;
    +
    + irc_prpl = purple_plugins_find_with_id(IRC_PLUGIN_ID);
    +
    + if (NULL == irc_prpl)
    + return FALSE;
    +
    + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(irc_prpl);
    + if (NULL == prpl_info)
    + return FALSE;
    +
    +
    + /* Create hash table. */
    + states = g_hash_table_new(g_direct_hash, g_direct_equal);
    +
    +
    + /* Register protocol preferences. */
    +
    + option = purple_account_option_string_new(_("Auth name"), PLUGIN_ID "_authname", "");
    + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
    +
    + option = purple_account_option_string_new(_("Nick password"), PLUGIN_ID "_nickpassword", "");
    + purple_account_option_set_masked(option, TRUE);
    + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
    +
    + option = purple_account_option_bool_new(_("Disconnect ghosts (Duplicate nicknames)"),
    + PLUGIN_ID "_disconnectghosts", 0);
    + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
    +
    + option = purple_account_option_string_new(_("Operator password"), PLUGIN_ID "_operpassword", "");
    + purple_account_option_set_masked(option, TRUE);
    + prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option);
    +
    +
    + /* Register callbacks. */
    +
    + conn_handle = purple_connections_get_handle();
    + conv_handle = purple_conversations_get_handle();
    +
    + purple_signal_connect(conn_handle, "signed-on",
    + plugin, PURPLE_CALLBACK(signed_on_cb), NULL);
    + purple_signal_connect(conv_handle, "conversation-created",
    + plugin, PURPLE_CALLBACK(conversation_created_cb), NULL);
    + purple_signal_connect(conv_handle, "receiving-im-msg",
    + plugin, PURPLE_CALLBACK(receiving_im_msg_cb), NULL);
    + purple_signal_connect(conv_handle, "writing-chat-msg",
    + plugin, PURPLE_CALLBACK(writing_chat_msg_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean plugin_unload(PurplePlugin *plugin)
    +{
    + PurplePlugin *irc_prpl;
    + PurplePluginProtocolInfo *prpl_info;
    + GList *list;
    +
    + irc_prpl = purple_plugins_find_with_id(IRC_PLUGIN_ID);
    + if (NULL == irc_prpl)
    + return FALSE;
    +
    + prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(irc_prpl);
    + if (NULL == prpl_info)
    + return FALSE;
    +
    + list = prpl_info->protocol_options;
    +
    +
    + /* Remove protocol preferences. */
    + while (NULL != list)
    + {
    + PurpleAccountOption *option = (PurpleAccountOption *) list->data;
    +
    + if (g_str_has_prefix(purple_account_option_get_setting(option), PLUGIN_ID "_"))
    + {
    + GList *llist = list;
    +
    + /* Remove this element from the list. */
    + if (llist->prev)
    + llist->prev->next = llist->next;
    + if (llist->next)
    + llist->next->prev = llist->prev;
    +
    + purple_account_option_destroy(option);
    +
    + list = g_list_next(list);
    +
    + g_list_free_1(llist);
    + }
    + else
    + list = g_list_next(list);
    + }
    +
    + return TRUE;
    +}
    +
    +static void plugin_init(PurplePlugin *plugin)
    +{
    + info.dependencies = g_list_append(info.dependencies, IRC_PLUGIN_ID);
    +
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("IRC Helper");
    + info.summary = _("Handles the rough edges of the IRC protocol.");
    + info.description = _("- Transparent authentication with a variety of "
    + "services.\n- Suppression of various useless messages");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, plugin_init, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irchelper/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[IRC Helper]
    +type=default
    +depends=purple
    +provides=irchelper
    +summary=Handles the rough edges of the IRC protocol
    +description=Provides transparent authentication with a variety of services and suppresses various useless messages
    +authors=Richard Laager
    +introduced=1.0beta7
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,44 @@
    +HEADER_FILES = \
    + datechange.h \
    + irssi.h \
    + lastlog.h \
    + layout.h \
    + textfmt.h \
    + window.h
    +
    +EXTRA_DIST=\
    + Makefile.mingw \
    + plugins.cfg \
    + $(HEADER_FILES)
    +
    +irssidir = $(PIDGIN_LIBDIR)
    +
    +irssi_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +irssi_LTLIBRARIES = irssi.la
    +
    +irssi_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GLIB_LIBS)
    +
    +irssi_la_SOURCES = \
    + datechange.c \
    + irssi.c \
    + lastlog.c \
    + layout.c \
    + textfmt.c \
    + window.c
    +
    +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/irssi/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,20 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for irssi plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = irssi
    +
    +PP_SRC := \
    + datechange.c \
    + layout.c \
    + lastlog.c \
    + textfmt.c \
    + window.c \
    + irssi.c
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/datechange.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,136 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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 <time.h>
    +
    +#include <plugin.h>
    +#include <cmds.h>
    +
    +#include "irssi.h"
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static guint irssi_datechange_timeout_id = 0;
    +static gint lastday = 0;
    +
    +/******************************************************************************
    + * Helpers
    + *****************************************************************************/
    +static gint
    +irssi_datechange_get_day(time_t *t) {
    + struct tm *temp;
    +
    + temp = localtime(t);
    +
    + if(!temp)
    + return 0;
    +
    + return temp->tm_mday;
    +}
    +
    +static gint
    +irssi_datechange_get_month(time_t *t) {
    + struct tm *temp;
    +
    + temp = localtime(t);
    +
    + if(!temp)
    + return 0;
    +
    + return temp->tm_mon;
    +}
    +
    +static gboolean
    +irssi_datechange_timeout_cb(gpointer data) {
    + time_t t;
    + GList *l;
    + gint newday;
    + gchar buff[80];
    + gchar *message;
    +
    + t = time(NULL);
    + newday = irssi_datechange_get_day(&t);
    +
    + if(newday == lastday)
    + return TRUE;
    +
    + strftime(buff, sizeof(buff), "%d %b %Y", localtime(&t));
    + message = g_strdup_printf(_("Day changed to %s"), buff);
    +
    + for(l = purple_get_conversations(); l; l = l->next) {
    + PurpleConversation *conv = (PurpleConversation *)l->data;
    +
    + purple_conversation_write(conv, NULL, message,
    + PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_ACTIVE_ONLY,
    + t);
    + if ((irssi_datechange_get_day(&t) == 1) && (irssi_datechange_get_month(&t) == 0) &&
    + purple_prefs_get_bool(SENDNEWYEAR_PREF))
    + {
    + const gchar *new_year = _("Happy New Year");
    + if(conv->type == PURPLE_CONV_TYPE_IM)
    + purple_conv_im_send(PURPLE_CONV_IM(conv), new_year);
    + else if(conv->type == PURPLE_CONV_TYPE_CHAT)
    + purple_conv_chat_send(PURPLE_CONV_CHAT(conv), new_year);
    + }
    + }
    +
    + g_free(message);
    +
    + lastday = newday;
    +
    + return TRUE;
    +}
    +
    +/******************************************************************************
    + * "API"
    + *****************************************************************************/
    +void
    +irssi_datechange_init(PurplePlugin *plugin) {
    + time_t t;
    +
    + if(purple_prefs_get_bool(DATECHANGE_PREF)) {
    + if(irssi_datechange_timeout_id != 0)
    + purple_timeout_remove(irssi_datechange_timeout_id);
    +
    + t = time(NULL);
    + lastday = irssi_datechange_get_day(&t);
    +
    + /* set this to get called every 30 seconds.
    + *
    + * Yes we only care about a day change, but i'd rather get it in the first
    + * 30 seconds of the change rather than nearly a min later.
    + */
    + irssi_datechange_timeout_id = g_timeout_add(30000,
    + irssi_datechange_timeout_cb,
    + NULL);
    + }
    +}
    +
    +void
    +irssi_datechange_uninit(PurplePlugin *plugin) {
    + if(irssi_datechange_timeout_id != 0)
    + purple_timeout_remove(irssi_datechange_timeout_id);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/datechange.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,30 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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.
    + */
    +
    +#ifndef _IRSSI_DATECHANGE_H
    +#define _IRSSI_DATECHANGE_H
    +
    +void irssi_datechange_init(PurplePlugin *plugin);
    +void irssi_datechange_uninit(PurplePlugin *plugin);
    +
    +#endif /*_IRSSI_DATECHANGE_H*/
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/irssi.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,146 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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"
    +
    +/* Pidgin headers */
    +#include <gtkplugin.h>
    +
    +/* Local plugin headers */
    +#include "irssi.h"
    +
    +static gboolean
    +irssi_load(PurplePlugin *plugin) {
    + irssi_datechange_init(plugin);
    + irssi_lastlog_init(plugin);
    + irssi_layout_init(plugin);
    + irssi_window_init(plugin);
    + irssi_textfmt_init(plugin);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +irssi_unload(PurplePlugin *plugin) {
    + irssi_textfmt_uninit(plugin);
    + irssi_window_uninit(plugin);
    + irssi_layout_uninit(plugin);
    + irssi_lastlog_uninit(plugin);
    + irssi_datechange_uninit(plugin);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +irssi_pref_frame(PurplePlugin *plugin) {
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_label(_("Enable Features:"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(TEXTFMT_PREF, _("Text Formatting"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(DATECHANGE_PREF, _("Date Change Notification"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(SENDNEWYEAR_PREF, _("Happy New Year Message"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo irssi_prefs_info = {
    + irssi_pref_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static PurplePluginInfo irssi_info = { /* this tells Purple about the plugin */
    + 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 */
    +
    + irssi_load, /* load */
    + irssi_unload, /* unload */
    + NULL, /* destroy */
    +
    + NULL, /* ui_info */
    + NULL, /* extra_info */
    + &irssi_prefs_info, /* prefs_info */
    + NULL, /* actions */
    + NULL, /* reserved 1 */
    + NULL, /* reserved 2 */
    + NULL, /* reserved 3 */
    + NULL /* reserved 4 */
    +};
    +
    +static void
    +irssi_init(PurplePlugin *plugin) {
    +
    +/* if the user hasn't disabled internationalization support, tell gettext
    + * what package we're from and where our translations are, then set gettext
    + * to use UTF-8 */
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + /* set these here to allow for translations of the strings */
    + irssi_info.name = _("Irssi Features");
    + irssi_info.summary = _("Implements features of the irssi IRC client for "
    + "use in Pidgin.");
    + irssi_info.description = _("Implements some features of the IRC client "
    + "irssi to be used in Purple. It lets you know in all open "
    + "conversations when the day has changed, adds the lastlog command, "
    + "adds the window command, etc. The day changed message is not logged.");
    +
    + purple_prefs_add_none(PREFS_ROOT_PARENT);
    + purple_prefs_add_none(PREFS_ROOT);
    + purple_prefs_add_bool(TEXTFMT_PREF, TRUE);
    + purple_prefs_add_bool(DATECHANGE_PREF, TRUE);
    + purple_prefs_add_bool(SENDNEWYEAR_PREF, TRUE);
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, irssi_init, irssi_info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/irssi.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,41 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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.
    + */
    +
    +#define PLUGIN_ID "gtk-plugin_pack-irssi"
    +#define PLUGIN_STATIC_NAME "irssi"
    +#define PLUGIN_AUTHOR "\n" \
    + "\tGary Kramlich <grim@reaperworld.com>\n" \
    + "\tJohn Bailey <rekkanoryo@rekkanoryo.org>\n" \
    + "\tSadrul Habib Chowdhury <sadrul@users.sourceforge.net>"
    +
    +#define PREFS_ROOT_PARENT "/pidgin/plugins"
    +#define PREFS_ROOT "/pidgin/plugins/" PLUGIN_ID
    +#define TEXTFMT_PREF PREFS_ROOT "/textfmt"
    +#define DATECHANGE_PREF PREFS_ROOT "/datechange"
    +#define SENDNEWYEAR_PREF PREFS_ROOT "/happynewyear"
    +
    +#include "datechange.h"
    +#include "lastlog.h"
    +#include "layout.h"
    +#include "textfmt.h"
    +#include "window.h"
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/lastlog.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,126 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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 <string.h>
    +
    +#include <plugin.h>
    +#include <cmds.h>
    +#include <util.h>
    +
    +#include <gtkconv.h>
    +#include <gtkimhtml.h>
    +
    +#include "lastlog.h"
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static PurpleCmdId irssi_lastlog_cmd = 0;
    +
    +/******************************************************************************
    + * Helpers
    + *****************************************************************************/
    +static void
    +irssi_lastlog(PurpleConversation *c, const gchar *needle) {
    + PidginConversation *gtkconv = c->ui_data;
    + int i;
    + GString *result;
    + char **lines;
    +
    + /* let's avoid some warnings on anal C compilers like mipspro cc */
    + result = g_string_new(NULL);
    + lines = gtk_imhtml_get_markup_lines(GTK_IMHTML(gtkconv->imhtml));
    +
    + /* XXX: This will include all messages, including the output of the
    + * history plugin, system messages, timestamps etc. This might be
    + * undesirable. A better solution will probably be considerably more
    + * complex.
    + */
    +
    + for (i = 0; lines[i]; i++) {
    + char *strip = purple_markup_strip_html(lines[i]);
    + if (strstr(strip, needle)) {
    + result = g_string_append(result, lines[i]);
    + result = g_string_append(result, "<br>");
    + }
    +
    + g_free(strip);
    + }
    +
    + /* XXX: This should probably be moved into outputting directly in the
    + * conversation window.
    + */
    + purple_notify_formatted(gtkconv, _("Lastlog"), _("Lastlog output"), NULL,
    + result->str, NULL, NULL);
    +
    + g_string_free(result, TRUE);
    + g_strfreev(lines);
    +}
    +
    +static PurpleCmdRet
    +irssi_lastlog_cmd_cb(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar **error, void *data)
    +{
    + irssi_lastlog(conv, args[0]);
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +/******************************************************************************
    + * "API"
    + *****************************************************************************/
    +void
    +irssi_lastlog_init(PurplePlugin *plugin) {
    + const gchar *help;
    +
    + if(irssi_lastlog_cmd != 0)
    + return;
    +
    + /* XXX: Translators: DO NOT TRANSLATE "lastlog" or the HTML tags below */
    + help = _("<pre>lastlog &lt;string&gt;: Shows, from the current "
    + "conversation's history, all messages containing the word or "
    + "words specified in string. It will be an exact match, "
    + "including whitespace and special characters.");
    +
    + /*
    + * registering the /lastlog command so that it will take an arbitrary
    + * number of arguments 1 or greater, matched as a single string, mark it
    + * as a plugin-priority command, make it functional in both IMs and chats,
    + * set it such that it is NOT protocol-specific, specify the callback and
    + * the help string, and set no user data to pass to the callback
    + */
    + irssi_lastlog_cmd =
    + purple_cmd_register("lastlog", "s", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT, NULL,
    + PURPLE_CMD_FUNC(irssi_lastlog_cmd_cb), help, NULL);
    +}
    +
    +void
    +irssi_lastlog_uninit(PurplePlugin *plugin) {
    + purple_cmd_unregister(irssi_lastlog_cmd);
    +
    + irssi_lastlog_cmd = 0;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/lastlog.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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.
    + */
    +
    +#ifndef _IRSSI_LASTLOG_H
    +#define _IRSSI_LASTLOG_H
    +
    +void irssi_lastlog_init(PurplePlugin *plugin);
    +void irssi_lastlog_uninit(PurplePlugin *plugin);
    +
    +#endif /*_IRSSI_LASTLOG_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/layout.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,348 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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 <blist.h>
    +#include <cmds.h>
    +#include <plugin.h>
    +
    +#include <gtkblist.h>
    +#include <gtkconv.h>
    +
    +#include "layout.h"
    +
    +#define IRSSI_LAYOUT_SETTING "irssi::layout"
    +
    +#define SETTING_TO_INTS(s, i1, i2) { \
    + (i1) = (s) & 0x3ff; \
    + (i2) = (s) >> 10; \
    +}
    +
    +#define SETTING_FROM_INTS(i1, i2) (((i1) << 10 ) + (i2))
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static PurpleCmdId irssi_layout_cmd_id = 0;
    +
    +/******************************************************************************
    + * Helpers
    + *****************************************************************************/
    +static PurpleBlistNode *
    +irssi_layout_get_node_from_conv(PurpleConversation *conv) {
    + PurpleBlistNode *node = NULL;
    +
    + /* this is overkill for now, but who knows, we _may_ need it later */
    +
    + switch(conv->type) {
    + case PURPLE_CONV_TYPE_CHAT:
    + node = (PurpleBlistNode *)purple_blist_find_chat(conv->account,
    + conv->name);
    +
    + break;
    + case PURPLE_CONV_TYPE_IM:
    + node = (PurpleBlistNode *)purple_find_buddy(conv->account, conv->name);
    +
    + break;
    +
    + default:
    + node = NULL;
    + }
    +
    + return node;
    +}
    +
    +static PurpleConversation *
    +irssi_layout_get_conv_from_node(PurpleBlistNode *node, gboolean create) {
    + PurpleAccount *account = NULL;
    + PurpleConversation *conv = NULL;
    + PurpleConversationType ctype = PURPLE_CONV_TYPE_UNKNOWN;
    +
    + const gchar *name = NULL;
    +
    + switch(node->type) {
    + case PURPLE_BLIST_CHAT_NODE: {
    + PurpleChat *chat = (PurpleChat *)node;
    +
    + ctype = PURPLE_CONV_TYPE_CHAT;
    + name = purple_chat_get_name(chat);
    + account = chat->account;
    +
    + break;
    + }
    + case PURPLE_BLIST_BUDDY_NODE: {
    + PurpleBuddy *buddy = (PurpleBuddy *)node;
    +
    + ctype = PURPLE_CONV_TYPE_IM;
    + name = buddy->name;
    + account = buddy->account;
    +
    + break;
    + }
    + default:
    + return NULL;
    + break;
    + }
    +
    + conv = purple_find_conversation_with_account(ctype, name, account);
    +
    + if(!conv && create) {
    + conv = purple_conversation_new(ctype, account, name);
    +
    + /* dirty hack alert! */
    + if(ctype == PURPLE_BLIST_CHAT_NODE) {
    + PurpleChat *chat = (PurpleChat *)node;
    +
    + PURPLE_CONV_CHAT(conv)->left = TRUE;
    + serv_join_chat(account->gc, chat->components);
    + }
    + }
    +
    + return conv;
    +}
    +
    +static gint
    +irssi_layout_get_setting(PidginConversation *gtkconv) {
    + PurpleConversation *conv = gtkconv->active_conv;
    + PurpleBlistNode *node = NULL;
    + gint ret = 0;
    +
    + node = irssi_layout_get_node_from_conv(conv);
    +
    + if(node)
    + ret = purple_blist_node_get_int(node, IRSSI_LAYOUT_SETTING);
    +
    + return ret;
    +}
    +
    +static void
    +irssi_layout_reset(void) {
    + PurpleBlistNode *node = purple_blist_get_root();
    +
    + for(; node; node = purple_blist_node_next(node, TRUE))
    + purple_blist_node_remove_setting(node, IRSSI_LAYOUT_SETTING);
    +}
    +
    +static void
    +irssi_layout_save(void) {
    + PurpleBlistNode *node = NULL;
    + GList *wins = NULL;
    + gint i, j;
    +
    + /* reset the previous layouts if any exist */
    + irssi_layout_reset();
    +
    + /* now save the layout... */
    + wins = pidgin_conv_windows_get_list();
    +
    + for(i = 1; wins; wins = wins->next, i++) {
    + PidginWindow *win = wins->data;
    + GList *convs = pidgin_conv_window_get_gtkconvs(win);
    +
    + for(j = 1; convs; convs = convs->next, j++) {
    + PidginConversation *gtkconv = convs->data;
    + PurpleConversation *conv = gtkconv->active_conv;
    +
    + node = irssi_layout_get_node_from_conv(conv);
    +
    + if(node)
    + purple_blist_node_set_int(node, IRSSI_LAYOUT_SETTING,
    + SETTING_FROM_INTS(i, j));
    + }
    + }
    +}
    +
    +static void
    +irssi_layout_load(void) {
    + PurpleConversation *conv = NULL;
    + PurpleBlistNode *node;
    +
    + PidginConversation *gtkconv = NULL;
    + PidginWindow *window = NULL;
    +
    + GList *convs = NULL, *settings = NULL, *wins = NULL;
    +
    + gint current = 1;
    +
    + node = purple_blist_get_root();
    +
    + /* build our GList's with the conversation and the setting */
    + for(; node; node = purple_blist_node_next(node, FALSE)) {
    + gint setting = purple_blist_node_get_int(node, IRSSI_LAYOUT_SETTING);
    +
    + if(setting == 0)
    + continue;
    +
    + conv = irssi_layout_get_conv_from_node(node, FALSE);
    +
    + if(conv) {
    + convs = g_list_prepend(convs, conv);
    + settings = g_list_prepend(settings, GINT_TO_POINTER(setting));
    + }
    + }
    +
    + /* now restore the layout */
    +
    + /* we start by looping until all of our settings are handled */
    + while(convs) {
    + GList *l1 = NULL, *l2 = NULL;
    +
    + /* now go through the list and make sure we put the conversations in
    + * the correct windows.
    + */
    + for(l1 = convs, l2 = settings; l1; ) {
    + GList *s = NULL;
    + gint win, pos, setting;
    +
    + setting = GPOINTER_TO_INT(l2->data);
    + SETTING_TO_INTS(setting, pos, win);
    +
    + if(win != current)
    + continue;
    +
    + conv = l1->data;
    + gtkconv = conv->ui_data;
    +
    + /* pop of the nodes we just handled but hold our places and update
    + * the head pointers.
    + */
    + /* convs */
    + s = l1;
    + l1 = l1->next;
    + convs = g_list_delete_link(convs, s);
    +
    + /* settings */
    + s = l2;
    + l2 = l2->next;
    + settings = g_list_delete_link(settings, s);
    +
    + /* now find the actual window this should go into */
    + wins = pidgin_conv_windows_get_list();
    + window = g_list_nth_data(wins, win - 1);
    +
    + if(!window) {
    + /* make dat der dun winda */
    + window = pidgin_conv_window_new();
    + }
    +
    + /* add the conversation to the window */
    + if(gtkconv->win != window) {
    + pidgin_conv_window_remove_gtkconv(gtkconv->win, gtkconv);
    + pidgin_conv_window_add_gtkconv(window, gtkconv);
    + }
    + }
    +
    + current++;
    + }
    +
    + /* All the conversations are in the correct windows. Now we make sure
    + * they're in their right positions.
    + */
    + for(wins = pidgin_conv_windows_get_list(); wins; wins = wins->next) {
    + gint count, i, pos, position;
    + gint w; /* junk var */
    +
    + window = wins->data;
    + count = pidgin_conv_window_get_gtkconv_count(window);
    +
    + if(count <= 1)
    + continue;
    +
    + for(position = 1; position < count; position++) {
    + gtkconv = pidgin_conv_window_get_gtkconv_at_index(window, position);
    + pos = irssi_layout_get_setting(gtkconv);
    +
    + SETTING_TO_INTS(pos, pos, w);
    + if(pos <= 0)
    + continue;
    +
    + /* this could probably use tweaking, but it _should_ work */
    + for(i = pos; i < position; i++) {
    + PidginConversation *gtkconv2 = NULL;
    + gint p;
    +
    + gtkconv2 =
    + pidgin_conv_window_get_gtkconv_at_index(window, i);
    +
    + p = irssi_layout_get_setting(gtkconv2);
    + if(p <= 0 || p <= pos)
    + continue;
    +
    + gtk_notebook_reorder_child(GTK_NOTEBOOK(window->notebook),
    + gtkconv->tab_cont, i);
    + }
    + }
    + }
    +}
    +
    +static PurpleCmdRet
    +irssi_layout_cmd_cb(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar **error, void *data)
    +{
    + const gchar *sub_cmd = args[0];
    +
    + if(!g_ascii_strcasecmp(sub_cmd, "load")) {
    + irssi_layout_load();
    + } else if(!g_ascii_strcasecmp(sub_cmd, "save")) {
    + irssi_layout_save();
    + } else if(!g_ascii_strcasecmp(sub_cmd, "reset")) {
    + irssi_layout_reset();
    + }
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +/******************************************************************************
    + * "API"
    + *****************************************************************************/
    +void
    +irssi_layout_init(PurplePlugin *plugin) {
    + const gchar *help;
    +
    + if(irssi_layout_cmd_id != 0)
    + return;
    + /*
    + * XXX: Translators: DO NOT TRANSLATE the first "layout" or the "\nsave"
    + * or "reset" at the beginning of the last line below, or the HTML tags.
    + */
    + help = _("<pre>layout &lt;save|reset&gt;: Remember the layout of the "
    + "current conversations to reopen them when Purple is restarted.\n"
    + "save - saves the current layout\n"
    + "reset - clears the current saved layout\n"
    + "</pre>");
    +
    + irssi_layout_cmd_id =
    + purple_cmd_register("layout", "w", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT, NULL,
    + PURPLE_CMD_FUNC(irssi_layout_cmd_cb), help, NULL);
    +}
    +
    +void
    +irssi_layout_uninit(PurplePlugin *plugin) {
    + if(irssi_layout_cmd_id == 0)
    + return;
    +
    + purple_cmd_unregister(irssi_layout_cmd_id);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/layout.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,30 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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.
    + */
    +
    +#ifndef _IRSSI_LAYOUT_H
    +#define _IRSSI_LAYOUT_H
    +
    +void irssi_layout_init(PurplePlugin *plugin);
    +void irssi_layout_uninit(PurplePlugin *plugin);
    +
    +#endif /*_IRSSI_LAYOUT_H*/
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Irssi Features]
    +type=default
    +depends=pidgin
    +provides=irssi
    +summary=Implements features of the IRC client irssi in Pidgin
    +description=Implements some features of the IRC client irssi to be used in Purple. It lets you know in all open conversations when the day has changed, adds the lastlog command, adds the window command, etc. The day changed message is not logged.
    +authors=Gary Kramlich,John Bailey,Sadrul Habib Chowdhury
    +introduced=1.0beta1
    +notes=Originally introduced as 'irssidate', but renamed in version 1.0beta3.1 when additional functionality was added.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/textfmt.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,247 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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"
    +
    +#ifdef HAVE_REGEX_H
    +# include <regex.h>
    +#endif
    +
    +#include <plugin.h>
    +#include <cmds.h>
    +
    +#include "textfmt.h"
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +#define EXPRESSION "(^|[ ])(%s)([^ ]+)(%s)($|[ ])"
    +
    +enum {
    + GROUP_ALL = 0,
    + GROUP_LEADING,
    + GROUP_START_TOKEN,
    + GROUP_TEXT,
    + GROUP_END_TOKEN,
    + GROUP_TRAILING,
    + GROUP_TOTAL
    +};
    +
    +/******************************************************************************
    + * Helpers
    + *****************************************************************************/
    +
    +#ifdef HAVE_REGEX_H
    +
    +#define FORMAT(account, message) { \
    + if(!(message)) \
    + return FALSE; \
    + if(!(*(message))) \
    + return FALSE; \
    + if(!(account)) \
    + return FALSE; \
    + \
    + if(!(account)->gc) \
    + return FALSE; \
    + \
    + if(!((account)->gc->flags & PURPLE_CONNECTION_HTML)) \
    + return FALSE; \
    + \
    + if(!(message)) \
    + return FALSE; \
    + \
    + *(message) = irssi_textfmt_regex(*(message)); \
    + \
    + return FALSE; \
    +}
    +
    +#define APPEND_GROUP(group) { \
    + t = iter + matches[(group)].rm_so; \
    + offset = matches[(group)].rm_eo - matches[(group)].rm_so; \
    + str = g_string_append_len(str, t, offset); \
    +}
    +
    +static gchar *
    +irssi_textfmt_regex_helper(gchar *message, const gchar *token,
    + const gchar *tag)
    +{
    + GString *str = NULL;
    + gchar *ret = NULL, *expr = NULL, *iter = message, *t = NULL;
    + regex_t regex;
    + regmatch_t matches[GROUP_TOTAL];
    +
    + /* build our expression */
    + expr = g_strdup_printf(EXPRESSION, token, token);
    +
    + /* compile the expression.
    + * We may want to move this so we only do it once for each type, but this
    + * is pretty cheap, resource-wise.
    + */
    + if(regcomp(&regex, expr, REG_EXTENDED) != 0) {
    + g_free(expr);
    + return message;
    + }
    +
    + /* ok, we compiled fine, lets clean up our expression */
    + g_free(expr);
    +
    + /* 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) {
    + regfree(&regex);
    + return message;
    + }
    +
    + /* create our GString. Heh heh */
    + str = g_string_new("");
    +
    + /* now loop through the string looking for more matches */
    + do {
    + gint offset = 0;
    +
    + if(matches[0].rm_eo == -1)
    + break;
    +
    + /* append everything up to the match */
    + str = g_string_append_len(str, iter, matches[GROUP_ALL].rm_so);
    +
    + /* determine the leading white space */
    + APPEND_GROUP(GROUP_LEADING);
    +
    + /* append the starting tag */
    + g_string_append_printf(str, "<%s>", tag);
    +
    + /* append the starting token */
    + APPEND_GROUP(GROUP_START_TOKEN);
    +
    + /* append the text */
    + APPEND_GROUP(GROUP_TEXT);
    +
    + /* append the ending token */
    + APPEND_GROUP(GROUP_END_TOKEN);
    +
    + /* append the ending tag */
    + g_string_append_printf(str, "</%s>", tag);
    +
    + /* append the trailing whitespace */
    + APPEND_GROUP(GROUP_TRAILING);
    +
    + /* move iter to the end of the match */
    + 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.
    + */
    + str = g_string_append(str, iter);
    +
    + /* kill the passed in message */
    + g_free(message);
    +
    + /* take off our GString */
    + ret = str->str;
    + g_string_free(str, FALSE);
    +
    + return ret;
    +}
    +
    +static gchar *
    +irssi_textfmt_regex(gchar *message) {
    + message = irssi_textfmt_regex_helper(message, "\\*", "b");
    + message = irssi_textfmt_regex_helper(message, "/", "i");
    + message = irssi_textfmt_regex_helper(message, "-", "s");
    + message = irssi_textfmt_regex_helper(message, "_", "u");
    +
    +#if 0
    + /* don't add these until imhtml support them again */
    + message = irssi_textfmt_regex_helper(message, "\\^", "sup");
    + message = irssi_textfmt_regex_helper(message, "~", "sub");
    + message = irssi_textfmt_regex_helper(message, "!", "pre");
    +#endif
    +
    + return message;
    +}
    +
    +#endif
    +
    +/******************************************************************************
    + * Callbacks
    + *****************************************************************************/
    +#ifdef HAVE_REGEX_H
    +
    +static gboolean
    +irssi_textfmt_writing_cb(PurpleAccount *account, const gchar *who,
    + gchar **message, PurpleConversation *conv,
    + PurpleMessageFlags flags)
    +{
    + if(!(flags & (PURPLE_MESSAGE_SEND | PURPLE_MESSAGE_RECV)))
    + {
    + return FALSE;
    + }
    +
    + FORMAT(account, message);
    +}
    +
    +static gboolean
    +irssi_textfmt_sending_im_cb(PurpleAccount *account, const gchar *receiver,
    + gchar **message)
    +{
    + FORMAT(account, message);
    +}
    +
    +static gboolean
    +irssi_textfmt_sending_chat_cb(PurpleAccount *account, gchar **message, gint id) {
    + FORMAT(account, message);
    +}
    +
    +#endif
    +
    +/******************************************************************************
    + * "API"
    + *****************************************************************************/
    +void
    +irssi_textfmt_init(PurplePlugin *plugin) {
    +#ifdef HAVE_REGEX_H
    + void *handle;
    +
    + handle = purple_conversations_get_handle();
    +
    + purple_signal_connect(handle, "writing-im-msg", plugin,
    + PURPLE_CALLBACK(irssi_textfmt_writing_cb), NULL);
    + purple_signal_connect(handle, "writing-chat-msg", plugin,
    + PURPLE_CALLBACK(irssi_textfmt_writing_cb), NULL);
    + purple_signal_connect(handle, "sending-im-msg", plugin,
    + PURPLE_CALLBACK(irssi_textfmt_sending_im_cb), NULL);
    + purple_signal_connect(handle, "sending-chat-msg", plugin,
    + PURPLE_CALLBACK(irssi_textfmt_sending_chat_cb), NULL);
    +#endif
    +}
    +
    +void
    +irssi_textfmt_uninit(PurplePlugin *plugin) {
    + /* Nothing to do here, purple kills our callbacks for us. */
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/textfmt.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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.
    + */
    +
    +#ifndef _IRSSI_TEXTFMT_H
    +#define _IRSSI_TEXTFMT_H
    +
    +void irssi_textfmt_init(PurplePlugin *plugin);
    +void irssi_textfmt_uninit(PurplePlugin *plugin);
    +
    +#endif /*_IRSSI_TEXTFMT_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/window.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,181 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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 <gtk/gtk.h>
    +
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <plugin.h>
    +
    +#include <gtkconv.h>
    +
    +#include "window.h"
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static PurpleCmdId irssi_window_cmd_id = 0;
    +static PurpleCmdId irssi_win_cmd_id = 0;
    +
    +/******************************************************************************
    + * Helpers
    + *****************************************************************************/
    +static gboolean
    +irssi_window_close_cb(PurpleConversation *c) {
    + /* this gets called from a conversation since the conversation must exist
    + * until all of the commands are processed, and the output is output. */
    +
    + purple_conversation_destroy(c);
    +
    + return FALSE;
    +}
    +
    +static PurpleCmdRet
    +irssi_window_cmd(PurpleConversation *conv, const gchar *sub_cmd,
    + gchar **error)
    +{
    + PidginConversation *gtkconv;
    + PidginWindow *win;
    + gint cur;
    +
    + gtkconv = PIDGIN_CONVERSATION(conv);
    + win = gtkconv->win;
    + cur = gtk_notebook_get_current_page(GTK_NOTEBOOK(win->notebook));
    +
    + /* if the sub_cmd is a number, or starts with one, assume the user wants
    + * to switch to a specific numbered tab */
    + if(g_ascii_isdigit(*sub_cmd)) {
    + gint tab = atoi(sub_cmd) - 1; /* index starts at zero */
    +
    + if(tab < 0) {
    + *error = g_strdup(_("Invalid window specified."));
    +
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + if(tab < pidgin_conv_window_get_gtkconv_count(win))
    + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), tab);
    +
    + return PURPLE_CMD_RET_OK;
    + }
    +
    + if(!g_ascii_strcasecmp(sub_cmd, "close")) {
    + g_timeout_add(50, (GSourceFunc)irssi_window_close_cb, conv);
    +
    + return PURPLE_CMD_RET_OK;
    + } else if(!g_ascii_strcasecmp(sub_cmd, "next") ||
    + !g_ascii_strcasecmp(sub_cmd, "right"))
    + {
    + if(!pidgin_conv_window_get_gtkconv_at_index(win, cur + 1)) {
    + /* wrap around... */
    + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), 0);
    + } else {
    + /* move normally */
    + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook),
    + cur + 1);
    + }
    +
    + return PURPLE_CMD_RET_OK;
    + } else if(!g_ascii_strcasecmp(sub_cmd, "previous") ||
    + !g_ascii_strcasecmp(sub_cmd, "prev") ||
    + !g_ascii_strcasecmp(sub_cmd, "left"))
    + {
    + if(!pidgin_conv_window_get_gtkconv_at_index(win, cur - 1)) {
    + /* wrap around... */
    + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook), -1);
    + } else {
    + gtk_notebook_set_current_page(GTK_NOTEBOOK(win->notebook),
    + cur - 1);
    + }
    +
    + return PURPLE_CMD_RET_OK;
    + } else {
    + *error = g_strdup(_("Invalid argument!"));
    +
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + *error = g_strdup(_("Unknown Error!"));
    + return PURPLE_CMD_RET_FAILED;
    +}
    +
    +static PurpleCmdRet
    +irssi_window_cmd_cb(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar **error, void *data)
    +{
    + return irssi_window_cmd(conv, args[0], error);
    +}
    +
    +/******************************************************************************
    + * "API"
    + *****************************************************************************/
    +void
    +irssi_window_init(PurplePlugin *plugin) {
    + const gchar *help;
    +
    + if(irssi_window_cmd_id != 0 || irssi_win_cmd_id != 0)
    + return;
    +
    + /*
    + * XXX: Translators: DO NOT TRANSLATE the first occurance of the word
    + * "window" below, or "close", "next", "previous", "left", or "right"
    + * at the *beginning* of the lines below! The options to /window are
    + * NOT going to be translatable. Also, please don't translate the HTML
    + * tags.
    + */
    + help = _("<pre>window &lt;option&gt;: Operations for windows (tabs). "
    + "Valid options are:\n"
    + "close - closes the current conversation\n"
    + "next - move to the next conversation\n"
    + "previous - move to the previous conversation\n"
    + "left - move one conversation to the left\n"
    + "right - move one conversation to the right\n"
    + "&lt;number&gt; - go to tab <number>\n"
    + "</pre>");
    +
    + irssi_window_cmd_id =
    + purple_cmd_register("window", "w", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT, NULL,
    + PURPLE_CMD_FUNC(irssi_window_cmd_cb), help, NULL);
    +
    + /* same thing as above, except for the /win command */
    + help = _("<pre>win: THis command is synonymous with /window. Try /help "
    + "window for further details.</pre>");
    +
    + irssi_win_cmd_id =
    + purple_cmd_register("win", "w", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_CHAT, NULL,
    + PURPLE_CMD_FUNC(irssi_window_cmd_cb), help, NULL);
    +}
    +
    +void
    +irssi_window_uninit(PurplePlugin *plugin) {
    + if(irssi_window_cmd_id == 0 || irssi_win_cmd_id == 0)
    + return;
    +
    + purple_cmd_unregister(irssi_window_cmd_id);
    + purple_cmd_unregister(irssi_win_cmd_id);
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/irssi/window.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +/*
    + * irssi - Implements several irssi features for Purple
    + * 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
    + * 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.
    + */
    +
    +#ifndef _IRSSI_WINDOW_H
    +#define _IRSSI_WINDOW_H
    +
    +void irssi_window_init(PurplePlugin *plugin);
    +void irssi_window_uninit(PurplePlugin *plugin);
    +
    +#endif /*_IRSSI_WINDOW_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/lastseen/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +lastseendir = $(PIDGIN_LIBDIR)
    +
    +lastseen_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +lastseen_LTLIBRARIES = lastseen.la
    +
    +lastseen_la_SOURCES = \
    + lastseen.c
    +
    +lastseen_la_LIBADD = \
    + $(GTK_LIBS) \
    + $(PIDGIN_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS) \
    + $(GTK_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/lastseen/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for lastseen plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = lastseen
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/lastseen/lastseen.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,248 @@
    +/*
    + * Last Seen - Record when a buddy was last seen
    + * 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
    + * 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 <plugin.h>
    +#include <debug.h>
    +#include <blist.h>
    +#include <signals.h>
    +#include <util.h>
    +
    +#include <gtkplugin.h>
    +#include <gtkblist.h>
    +
    +/* global list of accounts connecting, to avoid inaccurate signon times
    + * idea stolen from guifications :) - thanks Gary.
    + */
    +GSList *connecting;
    +
    +static gboolean
    +remove_connecting_account(void *p)
    +{
    + PurpleAccount *account = (PurpleAccount *)p;
    +
    + if (account == NULL)
    + return FALSE;
    +
    + if(account->gc == NULL)
    + {
    + connecting = g_slist_remove(connecting, account);
    + return FALSE;
    + }
    +
    + if (account->gc->state == PURPLE_CONNECTING)
    + return TRUE;
    +
    + connecting = g_slist_remove(connecting, account);
    +
    + return FALSE;
    +}
    +
    +static void
    +account_connecting_cb(PurpleAccount *account, void *data)
    +{
    + if (g_slist_find(connecting, account) == NULL)
    + {
    + connecting = g_slist_append(connecting, account);
    + gtk_timeout_add(10000, remove_connecting_account, account);
    + }
    +}
    +
    +static void
    +received_im_msg_cb(PurpleAccount *account, char *sender, char *message,
    + PurpleConversation *conv, int flags, void *data)
    +{
    + PurpleBuddy *buddy;
    + gchar *said = NULL;
    + GSList *buds;
    +
    + purple_markup_html_to_xhtml(message, NULL, &said);
    + for (buds = purple_find_buddies(account, sender); buds; buds = buds->next)
    + {
    + buddy = buds->data;
    +
    + purple_blist_node_set_int((PurpleBlistNode*)buddy, "lastseen", time(NULL));
    + purple_blist_node_set_string((PurpleBlistNode*)buddy, "lastsaid", g_strchomp(said));
    + }
    + g_free(said);
    +}
    +
    +static void
    +buddy_signedon_cb(PurpleBuddy *buddy, void *data)
    +{
    + /* TODO: if the buddy is actually the account that is signing on, we
    + * should actually update signon time. Don't forget to normalize
    + * sceennames before comparing them! Also, what do we do if the buddy
    + * is always online when we log in? hmmm */
    + if (g_slist_find(connecting, buddy->account) != NULL)
    + return;
    + purple_blist_node_set_int((PurpleBlistNode*)buddy, "signedon", time(NULL));
    +}
    +
    +static void
    +buddy_signedoff_cb(PurpleBuddy *buddy, void *data)
    +{
    + purple_blist_node_set_int((PurpleBlistNode*)buddy, "signedoff", time(NULL) );
    +}
    +
    +static void
    +drawing_tooltip_cb(PurpleBlistNode *node, GString *str, gboolean full, void *data)
    +{
    + PurpleBuddy *buddy = NULL;
    + PurpleBlistNode *n;
    + time_t last = 0, max = 0, off = 0, on = 0;
    + const gchar *tmp = NULL;
    + gchar *seen = NULL, *said = NULL, *offs = NULL, *ons = NULL;
    +
    + if(PURPLE_BLIST_NODE_IS_BUDDY(node))
    + {
    + if (!full)
    + return;
    + node = (PurpleBlistNode *)purple_buddy_get_contact((PurpleBuddy *)node);
    + }
    +
    + if(!PURPLE_BLIST_NODE_IS_CONTACT(node))
    + return;
    +
    + for (n = node->child; n; n = n->next)
    + {
    + if (!PURPLE_BLIST_NODE_IS_BUDDY(n))
    + continue;
    + last = purple_blist_node_get_int(n, "lastseen");
    + if (last > max) {
    + max = last;
    + buddy = (PurpleBuddy *)n;
    + }
    + last = 0;
    + }
    +
    + if(!buddy)
    + buddy = purple_contact_get_priority_buddy((PurpleContact *)node);
    +
    + last = purple_blist_node_get_int((PurpleBlistNode *)buddy, "lastseen");
    + if (last)
    + seen = purple_str_seconds_to_string(time(NULL) - last);
    + on = purple_blist_node_get_int((PurpleBlistNode *)buddy, "signedon");
    + if (on)
    + ons = purple_str_seconds_to_string(time(NULL) - on);
    + if(!PURPLE_BUDDY_IS_ONLINE(buddy)) {
    + off = purple_blist_node_get_int((PurpleBlistNode *)buddy, "signedoff");
    + if (off)
    + offs = purple_str_seconds_to_string(time(NULL) - off);
    + }
    + tmp = purple_blist_node_get_string((PurpleBlistNode *)buddy, "lastsaid");
    + if(tmp)
    + said = g_strchomp(g_markup_escape_text(tmp, -1));
    +
    + g_string_append_printf(str,
    + "%s%s" /* Last seen */
    + "%s%s" /* Last said */
    + "%s%s" /* Signed on */
    + "%s%s", /* Signed off */
    + seen ? _("\n<b>Last Seen</b>: ") : "", seen ? seen : "",
    + said ? _("\n<b>Last Said</b>: ") : "", said ? said : "",
    + ons ? _("\n<b>Signed On</b>: ") : "", ons ? ons : "",
    + offs ? _("\n<b>Signed Off</b>: ") : "", offs ? offs : "");
    + g_free(seen);
    + g_free(said);
    + g_free(ons);
    + g_free(offs);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + void *blist = purple_blist_get_handle();
    + void *conversation = purple_conversations_get_handle();
    + void *gtkblist = pidgin_blist_get_handle();
    +
    + connecting = NULL;
    +
    + purple_signal_connect(purple_accounts_get_handle(), "account-connecting",
    + plugin, PURPLE_CALLBACK(account_connecting_cb), NULL);
    + purple_signal_connect(conversation, "received-im-msg",
    + plugin, PURPLE_CALLBACK(received_im_msg_cb), NULL);
    + purple_signal_connect(blist, "buddy-signed-on", plugin,
    + PURPLE_CALLBACK(buddy_signedon_cb), NULL);
    + purple_signal_connect(blist, "buddy-signed-off", plugin,
    + PURPLE_CALLBACK(buddy_signedoff_cb), NULL);
    + purple_signal_connect(gtkblist, "drawing-tooltip",
    + plugin, PURPLE_CALLBACK(drawing_tooltip_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + g_slist_free(connecting);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC, /**< magic */
    + PURPLE_MAJOR_VERSION, /**< major version */
    + PURPLE_MINOR_VERSION, /**< minor version */
    + PURPLE_PLUGIN_STANDARD, /**< type */
    + PIDGIN_PLUGIN_TYPE, /**< ui_requirement */
    + 0, /**< flags */
    + NULL, /**< dependencies */
    + PURPLE_PRIORITY_DEFAULT, /**< priority */
    +
    + "gtk-plugin_pack-lastseen", /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /** summary */
    + NULL, /** description */
    + "Stu Tomlinson <stu@nosnilmot.com>", /**< author */
    + PP_WEBSITE, /**< homepage */
    +
    + 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 = _("Last Seen");
    + info.summary = _("Record when a buddy was last seen.");
    + info.description = _("Logs the time of a last received message, what "
    + "they said, when they logged in, and when they "
    + "logged out, for buddies on your buddy list.");
    +}
    +
    +PURPLE_INIT_PLUGIN(lastseen, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/lastseen/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Last Seen]
    +type=default
    +depends=pidgin
    +provides=lastseen
    +summary=Record when a buddy was last seen
    +description=Logs the time of a last received message, what they said, when they logged in, and when they logged out, for buddies on your buddy list.
    +authors=Stu Tomlinson
    +introduced=1.0beta1
    +notes=Partially superseded by functionality added in Pidgin 2.1.0.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,44 @@
    +HEADER_FILES = \
    + aim_blt_files.h \
    + alias_xml_files.h \
    + gen_xml_files.h \
    + lh_util.h \
    + listhandler.h \
    + migrate.h \
    + purple_blist_xml.h
    +
    +EXTRA_DIST=\
    + Makefile.mingw \
    + plugins.cfg \
    + $(HEADER_FILES)
    +
    +listhandlerdir = $(PURPLE_LIBDIR)
    +
    +listhandler_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +listhandler_LTLIBRARIES = listhandler.la
    +
    +listhandler_la_SOURCES = \
    + aim_blt_files.c \
    + alias_xml_files.c \
    + gen_xml_files.c \
    + lh_util.c \
    + migrate.c \
    + listhandler.c \
    + purple_blist_xml.c
    +
    +listhandler_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/listhandler/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,22 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for listhandler plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = listhandler
    +
    +
    +PP_SRC := \
    + aim_blt_files.c \
    + alias_xml_files.c \
    + gen_xml_files.c \
    + lh_util.c \
    + listhandler.c \
    + migrate.c \
    + purple_blist_xml.c
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/aim_blt_files.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,479 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +#include "aim_blt_files.h"
    +
    +static gchar *filename = NULL, *file_contents = NULL;
    +static gsize length;
    +static GString *bltfile_string = NULL;
    +static PurpleAccount *source_account = NULL, *target_account = NULL;
    +static PurpleBuddyList *buddies = NULL;
    +static PurpleConnection *gc = NULL;
    +
    +static gboolean /* used to filter the account list to oscar accounts only */
    +lh_aim_filter(PurpleAccount *account)
    +{
    + const gchar *prpl_id = purple_account_get_protocol_id(account);
    +
    + if(!prpl_id)
    + return FALSE;
    +
    + if(!strcmp(prpl_id, "prpl-aim"))
    + return TRUE;
    +
    + return FALSE;
    +}
    +
    +static gchar * /* remove '{' and leading and trailing spaces from string */
    +lh_aim_str_normalize(gchar *s)
    +{
    + /* replace all instances of the { and " characters with spaces, then
    + * strip whitespace */
    + return g_strstrip(g_strdelimit(g_strdelimit(s, "\"", ' '), "{", ' '));
    +}
    +
    +static gchar * /* extract alias from string by stripping AliasString and "s */
    +lh_aim_get_alias(gchar * s, gboolean v2)
    +{
    + gint i, limit;
    +
    + /* Magic numbers: 18 = length of the string up to = for v2 files,
    + * 17 = length of the string up to "AliasString" for v1 files */
    + if(v2) /* if a v2 file, we need to convert FriendlyName= to spaces */
    + limit = 18;
    + else /* else, v1 file, we need to convert AliasString to spaces */
    + limit = 17;
    +
    + /* go through and kill off the chars that aren't part of the alias */
    + for(i = 0; i < limit; i++)
    + if(s[i] != ' ' && s[i] != '\0')
    + s[i] = ' ';
    +
    + /* now strip the stupid, useless whitespace from the string */
    + return g_strstrip(s);
    +}
    +
    +static gchar ** /* read and split the file into manageable strings */
    +lh_aim_get_file_strings(gchar *file_contents, gsize *length, guint *strings_len)
    +{
    + gchar **ret;
    + GError *error = NULL;
    +
    + /* read the file as one bigass string */
    + g_file_get_contents(filename, &file_contents, length, &error);
    +
    + if(error)
    + purple_debug_misc("listhandler: import", "Error from glib: %s\n",
    + error->message);
    +
    + /* split that bigass string into manageable ones ;) */
    + ret = g_strsplit(file_contents, "\n", 0);
    +
    + /* find out how many "manageable strings" we have */
    + if(strings_len)
    + *strings_len = g_strv_length(ret);
    +
    + if(error)
    + g_error_free(error);
    +
    + g_free(filename);
    +
    + return ret; /* leave this stupid function already! */
    +}
    +
    +static void /* find where the buddy list begins and ends */
    +lh_aim_list_find(gchar **strings, guint strings_len, guint *begin, guint *end)
    +{
    + int i;
    +
    + /* this is a bit ugly but it works */
    + for(i = 0; i < strings_len; i++) {
    + if(!strncmp(strings[i], " list {", 7))
    + *begin = i;
    + if(*begin && i > *begin && !strncmp(strings[i], " }", 2)) {
    + *end = i;
    + break;
    + }
    + }
    +
    + return;
    +}
    +
    +static void /* parse this damn buddy list already */
    +lh_aim_list_parse_and_add(gchar **strings, guint length, guint begin, guint end)
    +{
    + gchar *current_group = NULL, *current_buddy = NULL, *current_alias = NULL;
    + gint i, current_group_begin = 0, current_group_end = 0;
    + PurpleGroup *current_purple_group = NULL;
    + PurpleBuddy *tmpbuddy = NULL;
    + GList *buddies = NULL, *groups = NULL;
    +
    + /* loop until we find the end of the buddy list */
    + while(current_group_end < end && current_group_end != end - 1) {
    + purple_debug_info("listhandler: import", "Started the parsing loop\n");
    +
    + /* it's safe to start one after and end one before the start and end
    + * of the list section, so save the two iterations. the if statement
    + * determines if we've already been through at least one group or not
    + * and sets i accordingly to prevent missing or reparsing a group */
    + if(current_group_end > 0)
    + i = current_group_end + 1;
    + else
    + i = begin + 1;
    +
    + /* pass through the list from the starting point determined above
    + * until the end of the current group's section in the blt file is
    + * found */
    + for(; i < end; i++) {
    + if(!strncmp(strings[i], " ", 2) && strlen(strings[i]) >= 3 &&
    + strings[i][2] != ' ' && strings[i][2] != '}')
    + current_group_begin = i;
    +
    + if(!strncmp(strings[i], " }", 3)) {
    + current_group_end = i;
    + break;
    + }
    + }
    +
    + purple_debug_info("listhandler: import", "Current group begins %d, ends %d\n",
    + current_group_begin, current_group_end);
    +
    + /* now strip {, ", and whitespace from the group and keep an extra
    + * pointer to it around for easy access */
    + current_group = lh_aim_str_normalize(strings[current_group_begin]);
    +
    + /* create a PurpleGroup and add to the list. This is surprisingly easy. */
    + current_purple_group = purple_group_new(current_group);
    + purple_blist_add_group(current_purple_group, NULL);
    +
    + /* now parse the actual group */
    + for(i = current_group_begin + 1; i < current_group_end; i++) {
    + if(!strncmp(strings[i], " ", 3) && strlen(strings[i]) >= 4 &&
    + strings[i][3] != ' ' && strings[i][3] != '}')
    + {
    + /* this is the buddy name; keep extra pointer for easy access */
    + current_buddy = lh_aim_str_normalize(strings[i]);
    +
    + /* since the geniuses that designed the blt format decided
    + * that "M y S cr ee nn a m e" is acceptable in their blt files,
    + * I have to work around their incompetence */
    + lh_aim_str_normalize(current_buddy);
    +
    + purple_debug_info("listhandler: import", "current buddy is %s\n",
    + current_buddy);
    +
    + /* test to see if the buddy has an alias set */
    + if(!strncmp(strings[i + 1], " AliasKey {", 14) &&
    + !strncmp(strings[i + 2], " AliasString ", 17))
    + {
    + /* grab the alias */
    + current_alias = lh_aim_get_alias(strings[i + 2], FALSE);
    + i += 2; /* advance counter to prevent reparsing the alias */
    + } else if(!strncmp(strings[i + 1], " FriendlyName=", 17)) {
    + /* Version 2 .blt format uses FriendlyName= to denote an alias */
    + /* grab the alias */
    + current_alias = lh_aim_get_alias(strings[i + 1], TRUE);
    + i++; /* advance the counter to prevent reparsing the alias */
    + } else /* no alias is set */
    + current_alias = NULL;
    +
    + tmpbuddy = purple_buddy_new(target_account, current_buddy,
    + current_alias);
    + purple_debug_info("listhandler: import",
    + "new PurpleBuddy created: %s, %s, %s\n", current_buddy,
    + current_alias ? current_alias : "NULL",
    + purple_account_get_username(target_account));
    +
    + if(tmpbuddy && current_purple_group) {
    + buddies = g_list_prepend(buddies, tmpbuddy);
    + groups = g_list_prepend(groups, current_purple_group);
    +
    + purple_debug_info("listhandler: import", "added current "
    + "buddy to the GLists\n");
    + }
    + }
    + }
    + }
    +
    + if(buddies && groups) {
    + lh_util_add_to_blist(buddies, groups);
    + purple_account_add_buddies(target_account, buddies);
    + } else {
    + if(!buddies && !groups)
    + purple_debug_info("listhandler: import", "BOTH GLISTS NULL!!!!!\n");
    + if(!buddies)
    + purple_debug_info("listhandler: import", "BUDDY GLIST NULL!!!\n");
    + if(!groups)
    + purple_debug_info("listhandler: import", "GROUP GLIST NULL!!!!\n");
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_aim_import_target_request_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + gchar **strings = NULL;
    + guint strings_len = 0, list_begin = 0, list_end = 0;
    +
    + /* get the target account from the dialog we requested */
    + target_account = purple_request_fields_get_account(fields,
    + "aim_target_acct");
    +
    + /* read and split the file */
    + strings = lh_aim_get_file_strings(file_contents, &length, &strings_len);
    +
    + /* find the list in that crapload of memory that just got allocated */
    + lh_aim_list_find(strings, strings_len, &list_begin, &list_end);
    +
    + purple_debug_info("listhandler: import", "List begins at %d; ends at %d\n",
    + list_begin, list_end);
    +
    + /* parse the freaking list already */
    + lh_aim_list_parse_and_add(strings, strings_len, list_begin, list_end);
    +
    + /* clean up all that crap that got allocated */
    + g_strfreev(strings);
    + g_free(file_contents);
    +
    + return;
    +}
    +
    +static void /* does the request API calls needed */
    +lh_aim_import_target_request(void)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + purple_debug_info("listhandler: import", "Beginning Request API calls\n");
    +
    + /* It seems Purple is super-picky about the order of these first three calls */
    + /* create a request */
    + request = purple_request_fields_new();
    +
    + /* now create a field group */
    + group = purple_request_field_group_new(NULL);
    + /* and add that group to the request created above */
    + purple_request_fields_add_group(request, group);
    +
    + /* create a field */
    + field = purple_request_field_account_new("aim_target_acct",
    + _("Account"), NULL);
    + /* set the account field filter so we only see oscar accounts */
    + purple_request_field_account_set_filter(field, lh_aim_filter);
    + /* mark the field as required */
    + purple_request_field_set_required(field, TRUE);
    +
    + /* add the field to the group created above */
    + purple_request_field_group_add_field(group, field);
    +
    + /* and finally we can create the request */
    + purple_request_fields(purple_get_blist(), _("List Handler: Importing"),
    + _("Choose the account to import to:"), NULL,
    + request, _("_Import"),
    + G_CALLBACK(lh_aim_import_target_request_cb),
    + _("_Cancel"), NULL, NULL, NULL, NULL, NULL);
    +
    + purple_debug_info("listhandler: import", "Ending Request API calls\n");
    +
    + return;
    +}
    +
    +static void
    +lh_aim_import_cb(void *user_data, const char *file)
    +{
    + purple_debug_info("listhandler: import", "Beginning import\n");
    +
    + if(file) {
    + filename = g_strdup(file);
    +
    + lh_aim_import_target_request();
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_aim_string_add_buddy(PurpleBlistNode *node)
    +{
    + PurpleBuddy *buddy = (PurpleBuddy *)node;
    + const char *tmpalias = purple_buddy_get_contact_alias(buddy),
    + *tmpname = purple_buddy_get_name(buddy);
    +
    + purple_debug_info("listhandler: export", "Node is buddy. Name is: %s\n", tmpname);
    +
    + /* only export if the buddy is on the right account */
    + if(purple_buddy_get_account(buddy) == source_account) {
    + /* add the buddy's screenname to the string */
    + g_string_append_printf(bltfile_string, " \"%s\"", tmpname);
    +
    + /* if the alias is NOT the same as the screenname, add it to the string */
    + if(strcmp(tmpalias, tmpname))
    + g_string_append_printf(bltfile_string,
    + " {\n AliasKey {\n \"%s\"\n }\n }\n",
    + tmpalias );
    + else /* otherwise we're done with this buddy */
    + g_string_append_printf(bltfile_string, "\n");
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_aim_build_string(void)
    +{
    + PurpleBlistNode *root_node = buddies->root, *g = NULL,
    + *c = NULL, *b = NULL;
    +
    + bltfile_string = g_string_new("Config {\n version 1\n}\n");
    +
    + g_string_append_printf(bltfile_string, "User {\n screenname %s\n}\n",
    + purple_account_get_username(source_account));
    + g_string_append(bltfile_string, "Buddy {\n list {\n");
    +
    + /* this outer loop iterates through the group level of the tree */
    + for(g = root_node; g && PURPLE_BLIST_NODE_IS_GROUP(g); g = g->next)
    + {
    + purple_debug_info("listhandler: export", "Node is group. Name is: %s\n",
    + ((PurpleGroup *)g)->name);
    +
    + /* add the group to the string */
    + g_string_append_printf(bltfile_string, " \"%s\" {\n",
    + ((PurpleGroup *)g)->name);
    +
    + /* iterate through the contact level in this group */
    + for(c = g->child; c && PURPLE_BLIST_NODE_IS_CONTACT(c); c = c->next) {
    + purple_debug_info("listhandler: export",
    + "Node is contact. Will parse its children.\n");
    +
    + /* iterate through the contact's buddies */
    + for(b = c->child; b && PURPLE_BLIST_NODE_IS_BUDDY(b); b = b->next)
    + lh_aim_string_add_buddy(b);
    + }
    +
    + g_string_append(bltfile_string, " }\n");
    + }
    +
    + /* finish the string we'll dump to the file */
    + g_string_append(bltfile_string, " }\n}\n");
    +
    + purple_debug_info("listhandler: export", "String built. String is:\n\n%s\n",
    + bltfile_string->str);
    +
    + return;
    +}
    +
    +static void
    +lh_aim_export_request_cb(void *user_data, const char *filename)
    +{
    + FILE *export = fopen(filename, "w");
    +
    + if(export) {
    + lh_aim_build_string();
    + fprintf(export, "%s", bltfile_string->str);
    + fclose(export);
    + } else
    + purple_debug_info("listhandler: export", "Can't save file %s\n",
    + filename ? filename : "NULL");
    +
    + g_string_free(bltfile_string, TRUE);
    +
    + return;
    +}
    +
    +static void
    +lh_aim_export_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + /* get the source account from the dialog we requested */
    + source_account = purple_request_fields_get_account(fields,
    + "aim_source_acct");
    +
    + /* get the connection from the account */
    + gc = purple_account_get_connection(source_account);
    +
    + /* this grabs the purple buddy list, which will be walked thru later */
    + buddies = purple_get_blist();
    +
    + if(buddies)
    + purple_request_file(listhandler, _("Save AIM .blt File"), NULL, TRUE,
    + G_CALLBACK(lh_aim_export_request_cb), NULL,
    + source_account, NULL, NULL, NULL);
    + else
    + purple_debug_info("listhandler: export", "blist not returned\n");
    +
    + return;
    +}
    +
    +void /* do some work and export the damn blist already */
    +lh_aim_export_action_cb(PurplePluginAction *action)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + purple_debug_info("listhandler: export", "Beginning Request API calls\n");
    +
    + /* It seems Purple is super-picky about the order of these first three calls */
    + /* create a request */
    + request = purple_request_fields_new();
    +
    + /* now create a field group */
    + group = purple_request_field_group_new(NULL);
    + /* and add that group to the request created above */
    + purple_request_fields_add_group(request, group);
    +
    + /* create a field */
    + field = purple_request_field_account_new("aim_source_acct",
    + _("Account"), NULL);
    + /* set the account field filter so we only see oscar accounts */
    + purple_request_field_account_set_filter(field, lh_aim_filter);
    + /* mark the field as required */
    + purple_request_field_set_required(field, TRUE);
    +
    + /* add the field to the group created above */
    + purple_request_field_group_add_field(group, field);
    +
    + /* and finally we can create the request */
    + purple_request_fields(purple_get_blist(), _("List Handler: Exporting"),
    + _("Choose the account to export from:"), NULL, request,
    + _("_Export"), G_CALLBACK(lh_aim_export_cb), _("_Cancel"),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + purple_debug_info("listhandler: export", "Ending Request API calls\n");
    +
    + return;
    +}
    +
    +void
    +lh_aim_import_action_cb(PurplePluginAction *action)
    +{
    + purple_debug_info("listhandler: import", "Requesting the file.\n");
    +
    + purple_request_file(listhandler, _("Choose An AIM .blt File To Import"),
    + NULL, FALSE, G_CALLBACK(lh_aim_import_cb),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/aim_blt_files.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,31 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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.
    + */
    +
    +/* callbacks for the GList of plugin actions */
    +#ifndef _LISTHANDLER_AIM_BLT_H
    +#define _LISTHANDLER_AIM_BLT_H
    +
    +void lh_aim_export_action_cb(PurplePluginAction *action);
    +void lh_aim_import_action_cb(PurplePluginAction *action);
    +
    +#endif /*_LISTHANDLER_AIM_BLT_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/alias_xml_files.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,301 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +#include "alias_xml_files.h"
    +
    +static const gchar *target_prpl_id = NULL;
    +static gchar *file_contents = NULL, *filename = NULL;
    +static gsize length;
    +static PurpleAccount *target_account = NULL, *source_account = NULL;
    +static PurpleBuddyList *buddies = NULL;
    +static PurpleConnection *gc = NULL;
    +static xmlnode *root = NULL;
    +
    +static gboolean
    +lh_import_filter(PurpleAccount *account)
    +{
    + const gchar *prpl_id = purple_account_get_protocol_id(account);
    +
    + if(!prpl_id)
    + return FALSE;
    +
    + if(!strcmp(prpl_id, target_prpl_id))
    + return TRUE;
    +
    + return FALSE;
    +}
    +
    +static void
    +lh_alist_import(xmlnode *alist)
    +{
    + xmlnode *buddy = NULL;
    + PurpleBuddy *temp = NULL;
    +
    + if(alist) {
    + /* get the first buddy in the alias list */
    + buddy = xmlnode_get_child(alist, "buddy");
    +
    + while(buddy) {
    + temp = purple_find_buddy(target_account, xmlnode_get_attrib(buddy, "screenname") );
    +
    + if(temp != NULL) {
    + purple_blist_alias_buddy(temp, xmlnode_get_attrib(buddy, "alias") );
    + purple_debug_info("listhandler: import", "Added alias for %s\n",
    + xmlnode_get_attrib(buddy, "screenname") );
    + }
    +
    + buddy = xmlnode_get_next_twin(buddy);
    + }
    + }
    +}
    +
    +static void
    +lh_alist_build_alias_tree(xmlnode *parent)
    +{
    + /* root of tree group contact buddy */
    + PurpleBlistNode *root = buddies->root, *g = NULL, *c = NULL, *b = NULL;
    + xmlnode *buddy = NULL;
    + PurpleBuddy *tmpbuddy = NULL;
    + const char *tmpalias = NULL, *tmpname = NULL;
    +
    + /* iterate through the groups */
    + for(g = root; g; g = g->next) {
    + if(PURPLE_BLIST_NODE_IS_GROUP(g)) {
    + const char *group_name = ((PurpleGroup *)g)->name;
    +
    + purple_debug_info("listhandler: export", "Node is group. Name is: %s\n",
    + group_name);
    +
    + /* iterate through the contacts */
    + for(c = g->child; c; c= c->next) {
    + if(PURPLE_BLIST_NODE_IS_CONTACT(c)) {
    + purple_debug_info("listhandler: export",
    + "Node is contact. Will parse its children.\n");
    +
    + /* iterate through the buddies */
    + for(b = c->child; b && PURPLE_BLIST_NODE_IS_BUDDY(b); b = b->next) {
    + tmpbuddy = (PurpleBuddy *)b;
    + if(purple_buddy_get_account(tmpbuddy) == source_account) {
    + tmpalias = purple_buddy_get_alias_only(tmpbuddy);
    + if (tmpalias != NULL) {
    + tmpname = purple_buddy_get_name(tmpbuddy);
    + buddy = xmlnode_new_child(parent, "buddy");
    + xmlnode_set_attrib(buddy, "screenname", tmpname);
    + xmlnode_set_attrib(buddy, "alias", tmpalias);
    + }
    + }
    + }
    + }
    + }
    + }
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_alist_build_config_tree(xmlnode *parent)
    +{ /* we may need to expand this area later for future feature ehnancements */
    +
    + xmlnode_set_attrib(xmlnode_new_child(parent, "config-version"), "version", "2");
    + xmlnode_set_attrib(xmlnode_new_child(parent, "config-type"), "type", "alias-list");
    + xmlnode_set_attrib(xmlnode_new_child(parent, "prpl"), "id",
    + purple_account_get_protocol_id(source_account));
    + xmlnode_set_attrib(xmlnode_new_child(parent, "source"), "account",
    + purple_account_get_username(source_account));
    +
    + return;
    +}
    +
    +static xmlnode *
    +lh_alist_build_tree(void)
    +{
    + xmlnode *root_node = xmlnode_new("exported_alias_list");
    +
    + lh_alist_build_config_tree(xmlnode_new_child(root_node, "config"));
    + lh_alist_build_alias_tree(xmlnode_new_child(root_node, "alist"));
    +
    + return root_node;
    +}
    +
    +static void
    +lh_alist_export_request_cb(void *user_data, const char *filename)
    +{
    + FILE *export = fopen(filename, "w");
    +
    + if(export) {
    + int xmlstrlen = 0;
    + xmlnode *tree = lh_alist_build_tree();
    + char *xmlstring = xmlnode_to_formatted_str(tree, &xmlstrlen);
    +
    + purple_debug_info("listhandler: export",
    + "XML tree built and converted to string. String is:\n\n%s\n",
    + xmlstring);
    +
    + fprintf(export, "%s\n", xmlstring);
    +
    + fclose(export);
    +
    + g_free(xmlstring);
    + xmlnode_free(tree);
    + } else
    + purple_debug_info("listhandler: export", "Can't save file %s\n",
    + filename ? filename : "NULL");
    +
    + return;
    +}
    +
    +static void
    +lh_alist_export_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + /* get the source account from the dialog we requested */
    + source_account = purple_request_fields_get_account(fields, "generic_source_acct");
    +
    + /* get the connection from the account */
    + gc = purple_account_get_connection(source_account);
    +
    + /* this grabs the purple buddy list, which will be walked thru later */
    + buddies = purple_get_blist();
    +
    + if(buddies)
    + purple_request_file(listhandler, _("Save Generic .alist File"), NULL,
    + TRUE, G_CALLBACK(lh_alist_export_request_cb), NULL,
    + source_account, NULL, NULL, NULL);
    + else
    + purple_debug_info("listhandler: export alias", "blist not returned\n");
    +
    + return;
    +}
    +
    +void
    +lh_alist_export_action_cb(PurplePluginAction *action)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + request = purple_request_fields_new();
    +
    + group = purple_request_field_group_new(NULL);
    + purple_request_fields_add_group(request, group);
    +
    + field = purple_request_field_account_new("generic_source_acct", _("Account"), NULL);
    + purple_request_field_set_required(field, TRUE);
    + purple_request_field_account_set_show_all(field, TRUE);
    + purple_request_field_group_add_field(group, field);
    +
    + purple_request_fields(purple_get_blist(), _("Listhandler - Exporting"),
    + _("Choose the account to export from:"), NULL, request,
    + _("_Export"), G_CALLBACK(lh_alist_export_cb), _("_Cancel"),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    +
    +static void
    +lh_alist_import_target_request_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + /* get the target account */
    + target_account = purple_request_fields_get_account(fields, "generic_target_acct");
    +
    + purple_debug_info("listhandler: import",
    + "Got the target account and its connection.\n");
    +
    + purple_debug_info("listhandler: import", "Parsing Alias List in XML and setting aliases \n");
    +
    + lh_alist_import(xmlnode_get_child(root, "alist"));
    +
    + purple_debug_info("listhandler: import", "Finished setting aliases. "
    + "Freeing allocated memory.\n");
    +
    + xmlnode_free(root);
    +}
    +
    +static void
    +lh_alist_import_target_request(void)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    + GError *error = NULL;
    +
    + /* we need to make sure which prpl this list came from so we
    + * can filter the accounts list before showing it */
    +
    + /* read the file and get the root xml node within it*/
    + g_file_get_contents(filename, &file_contents, &length, &error);
    + root = xmlnode_from_str(file_contents, length);
    +
    + target_prpl_id = xmlnode_get_attrib(xmlnode_get_child(xmlnode_get_child(root, "config"),
    + "prpl"), "id");
    +
    + purple_debug_info("listhandler: import", "Beginning Request API calls\n");
    +
    + request = purple_request_fields_new();
    +
    + group = purple_request_field_group_new(NULL);
    + purple_request_fields_add_group(request, group);
    +
    + field = purple_request_field_account_new("generic_target_acct", _("Account"), NULL);
    + purple_request_field_account_set_filter(field, lh_import_filter);
    + purple_request_field_set_required(field, TRUE);
    + purple_request_field_group_add_field(group, field);
    +
    + purple_request_fields(purple_get_blist(), _("Listhandler - Importing"),
    + _("Choose the account to import to:"), NULL, request,
    + _("_Import"),
    + G_CALLBACK(lh_alist_import_target_request_cb),
    + _("_Cancel"), NULL, NULL, NULL, NULL, NULL);
    +
    + purple_debug_info("listhandler: import", "Ending Request API calls\n");
    +
    + g_free(filename);
    +
    + return;
    +}
    +
    +static void
    +lh_alist_import_request_cb(void *user_data, const char *file)
    +{
    + purple_debug_info("listhandler: import", "Beginning import\n");
    +
    + if(file) {
    + filename = g_strdup(file);
    +
    + lh_alist_import_target_request();
    + }
    +}
    +
    +
    +void
    +lh_alist_import_action_cb(PurplePluginAction *action)
    +{
    + purple_debug_info("listhandler: import", "Requesting the file.\n");
    +
    + purple_request_file(listhandler, _("Choose A Generic Buddy List File To Import"),
    + NULL, FALSE, G_CALLBACK(lh_alist_import_request_cb),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/alias_xml_files.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,30 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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.
    + */
    +
    +#ifndef _LISTHANDLER_ALIAS_XML_H
    +#define _LISTHANDLER_ALIAS_XML_H
    +
    +void lh_alist_import_action_cb(PurplePluginAction *action);
    +void lh_alist_export_action_cb(PurplePluginAction *action);
    +
    +#endif /* _LISTHANDLER_ALIAS_XML_H */
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/gen_xml_files.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,372 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +#include "gen_xml_files.h"
    +
    +static const gchar *target_prpl_id = NULL;
    +static gchar *file_contents = NULL, *filename = NULL;
    +static gsize length;
    +static PurpleAccount *target_account = NULL, *source_account = NULL;
    +static PurpleBuddyList *buddies = NULL;
    +static PurpleConnection *gc = NULL;
    +static xmlnode *root = NULL;
    +
    +static gboolean
    +lh_import_filter(PurpleAccount *account)
    +{
    + const gchar *prpl_id = purple_account_get_protocol_id(account);
    +
    + if(!prpl_id)
    + return FALSE;
    +
    + if(!strcmp(prpl_id, target_prpl_id))
    + return TRUE;
    +
    + return FALSE;
    +}
    +
    +static void
    +lh_generic_import_privacy(xmlnode *privacy)
    +{
    + /* XXX: This is here awaiting Bleeter's privacy rewrite that will allow
    + * importing and exporting of privacy options, lists, etc. */
    +}
    +
    +static void
    +lh_generic_import_blist(xmlnode *blist)
    +{
    + const gchar *group_name = NULL;
    + PurpleGroup *purple_group = NULL;
    + xmlnode *buddy = NULL;
    + /* get the first group */
    + xmlnode *group = xmlnode_get_child(blist, "group");
    +
    + while(group) {
    + /* get the group's name */
    + group_name = xmlnode_get_attrib(group, "name");
    +
    + purple_debug_info("listhandler: import", "Current group in XML is %s\n",
    + group_name);
    +
    + /* create and/or get a pointer to the PurpleGroup */
    + purple_group = purple_group_new(group_name);
    +
    + /* get the first buddy in this group */
    + buddy = xmlnode_get_child(group, "buddy");
    +
    + while(buddy) {
    + /* add the buddy to Purple's blist */
    + lh_util_add_buddy(group_name, purple_group,
    + xmlnode_get_attrib(buddy, "screenname"),
    + xmlnode_get_attrib(buddy, "alias"), target_account,
    + xmlnode_get_attrib(buddy, "notes"), 0, 0, 0, 0, NULL, NULL, NULL);
    +
    + /* get the next buddy in the current group */
    + buddy = xmlnode_get_next_twin(buddy);
    + }
    +
    + /* get the next group in the exported blist */
    + group = xmlnode_get_next_twin(group);
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_generic_import_target_request_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + /* get the target account */
    + target_account = purple_request_fields_get_account(fields, "generic_target_acct");
    +
    + purple_debug_info("listhandler: import",
    + "Got the target account and its connection.\n");
    +
    + purple_debug_info("listhandler: import", "Beginning to parse XML.\n");
    +
    + /* call separate functions to import the privacy and blist */
    + lh_generic_import_privacy(xmlnode_get_child(root, "privacy"));
    + lh_generic_import_blist(xmlnode_get_child(root, "blist"));
    +
    + purple_debug_info("listhandler: import", "Finished parsing XML. "
    + "Freeing allocated memory.\n");
    +
    + xmlnode_free(root);
    +}
    +
    +static void
    +lh_generic_import_target_request(void)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    + GError *error = NULL;
    +
    + /* we need to make sure which purple prpl this buddy list came from so we
    + * can filter the accounts list before showing it */
    +
    + /* read the file */
    + g_file_get_contents(filename, &file_contents, &length, &error);
    +
    + root = xmlnode_from_str(file_contents, length);
    +
    + target_prpl_id = xmlnode_get_attrib(xmlnode_get_child(xmlnode_get_child(root, "config"),
    + "prpl"), "id");
    +
    + purple_debug_info("listhandler: import", "Beginning Request API calls\n");
    +
    + /* It seems Purple is super-picky about the order of these first three calls */
    + /* create a request */
    + request = purple_request_fields_new();
    +
    + /* now create a field group */
    + group = purple_request_field_group_new(NULL);
    + /* and add that group to the request created above */
    + purple_request_fields_add_group(request, group);
    +
    + /* create a field */
    + field = purple_request_field_account_new("generic_target_acct", _("Account"), NULL);
    + /* set the account field filter so we only see accounts with the same
    + * prpl as the blist was exported from */
    + purple_request_field_account_set_filter(field, lh_import_filter);
    + /* mark the field as required */
    + purple_request_field_set_required(field, TRUE);
    +
    + /* add the field to the group created above */
    + purple_request_field_group_add_field(group, field);
    +
    + /* and finally we can create the request */
    + purple_request_fields(purple_get_blist(), _("Listhandler - Importing"),
    + _("Choose the account to import to:"), NULL, request,
    + _("_Import"),
    + G_CALLBACK(lh_generic_import_target_request_cb),
    + _("_Cancel"), NULL, NULL, NULL, NULL, NULL);
    +
    + purple_debug_info("listhandler: import", "Ending Request API calls\n");
    +
    + g_free(filename);
    +
    + return;
    +}
    +
    +static void
    +lh_generic_import_request_cb(void *user_data, const char *file)
    +{
    + purple_debug_info("listhandler: import", "Beginning import\n");
    +
    + if(file) {
    + filename = g_strdup(file);
    +
    + lh_generic_import_target_request();
    + }
    +}
    +
    +static void
    +lh_generic_build_config_tree(xmlnode *parent)
    +{ /* we may need/want to expand the config area later for future feature
    + enhancements; this is why this tree gets its own building function. */
    +
    + xmlnode_set_attrib(xmlnode_new_child(parent, "config-version"), "version", "2");
    + xmlnode_set_attrib(xmlnode_new_child(parent, "config-type"), "type", "buddy-list");
    + xmlnode_set_attrib(xmlnode_new_child(parent, "prpl"), "id",
    + purple_account_get_protocol_id(source_account));
    + xmlnode_set_attrib(xmlnode_new_child(parent, "source"), "account",
    + purple_account_get_username(source_account));
    +
    + return;
    +}
    +
    +static void
    +lh_generic_build_privacy_tree(xmlnode *parent)
    +{
    + /* XXX: This function does nothing pending Bleeter's privacy rewrite, which
    + * will allow exporting of privacy options, lists, etc. */
    + return;
    +}
    +
    +static void
    +lh_generic_build_blist_tree(xmlnode *parent)
    +{
    + /* root of tree group contact buddy */
    + PurpleBlistNode *root = buddies->root, *g = NULL, *c = NULL, *b = NULL;
    + xmlnode *group = NULL, *buddy = NULL;
    + PurpleBuddy *tmpbuddy = NULL;
    + const char *tmpalias = NULL, *tmpname = NULL, *tmpsetting = NULL;
    +
    + /* iterate through the groups */
    + for(g = root; g; g = g->next) {
    + if(PURPLE_BLIST_NODE_IS_GROUP(g)) {
    + const char *group_name = ((PurpleGroup *)g)->name;
    +
    + purple_debug_info("listhandler: export", "Node is group. Name is: %s\n",
    + group_name);
    +
    + /* add the group to the tree */
    + group = xmlnode_new_child(parent, "group");
    + xmlnode_set_attrib(group, "name", group_name);
    +
    + /* iterate through the contacts */
    + for(c = g->child; c; c= c->next) {
    + if(PURPLE_BLIST_NODE_IS_CONTACT(c)) {
    + purple_debug_info("listhandler: export",
    + "Node is contact. Will parse its children.\n");
    +
    + /* iterate through the buddies */
    + for(b = c->child; b && PURPLE_BLIST_NODE_IS_BUDDY(b); b = b->next) {
    + tmpbuddy = (PurpleBuddy *)b;
    + if(purple_buddy_get_account(tmpbuddy) == source_account) {
    + tmpalias = purple_buddy_get_contact_alias(tmpbuddy);
    + tmpname = purple_buddy_get_name(tmpbuddy);
    + tmpsetting = purple_blist_node_get_string(b, "notes");
    +
    + buddy = xmlnode_new_child(group, "buddy");
    + xmlnode_set_attrib(buddy, "screenname", tmpname);
    + xmlnode_set_attrib(buddy, "notes", tmpsetting);
    +
    + if(strcmp(tmpalias, tmpname))
    + xmlnode_set_attrib(buddy, "alias", tmpalias);
    + else
    + xmlnode_set_attrib(buddy, "alias", NULL);
    + }
    + }
    + }
    + }
    + }
    + }
    +
    + return;
    +}
    +
    +static xmlnode *
    +lh_generic_build_tree(void)
    +{
    + xmlnode *root_node = xmlnode_new("exported_buddy_list");
    +
    + /* since building this tree is really building three smaller trees that
    + * share a common parent, we'll build each tree separately to make this
    + * easier to read and understand what goes in each tree (hopefully). */
    + lh_generic_build_config_tree(xmlnode_new_child(root_node, "config"));
    + lh_generic_build_privacy_tree(xmlnode_new_child(root_node, "privacy"));
    + lh_generic_build_blist_tree(xmlnode_new_child(root_node, "blist"));
    +
    + return root_node;
    +}
    +
    +static void
    +lh_generic_export_request_cb(void *user_data, const char *filename)
    +{
    + FILE *export = fopen(filename, "w");
    +
    + if(export) {
    + int xmlstrlen = 0;
    + xmlnode *tree = lh_generic_build_tree();
    + char *xmlstring = xmlnode_to_formatted_str(tree, &xmlstrlen);
    +
    + purple_debug_info("listhandler: export",
    + "XML tree built and converted to string. String is:\n\n%s\n",
    + xmlstring);
    +
    + fprintf(export, "%s\n", xmlstring);
    +
    + fclose(export);
    +
    + g_free(xmlstring);
    + xmlnode_free(tree);
    + } else
    + purple_debug_info("listhandler: export", "Can't save file %s\n",
    + filename ? filename : "NULL");
    +
    + return;
    +}
    +
    +static void
    +lh_generic_export_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + /* get the source account from the dialog we requested */
    + source_account = purple_request_fields_get_account(fields, "generic_source_acct");
    +
    + /* get the connection from the account */
    + gc = purple_account_get_connection(source_account);
    +
    + /* this grabs the purple buddy list, which will be walked thru later */
    + buddies = purple_get_blist();
    +
    + if(buddies)
    + purple_request_file(listhandler, _("Save Generic .blist File"), NULL,
    + TRUE, G_CALLBACK(lh_generic_export_request_cb), NULL,
    + source_account, NULL, NULL, NULL);
    + else
    + purple_debug_info("listhandler: export", "blist not returned\n");
    +
    + return;
    +}
    +
    +void /* do some work and export the damn blist already */
    +lh_generic_export_action_cb(PurplePluginAction *action)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + /* It seems Purple is super-picky about the order of these first three calls */
    + /* create a request */
    + request = purple_request_fields_new();
    +
    + /* now create a field group */
    + group = purple_request_field_group_new(NULL);
    + /* and add that group to the request created above */
    + purple_request_fields_add_group(request, group);
    +
    + /* create a field */
    + field = purple_request_field_account_new("generic_source_acct", _("Account"), NULL);
    +
    + /* mark the field as required */
    + purple_request_field_set_required(field, TRUE);
    +
    + /* let's show offline accounts too */
    + purple_request_field_account_set_show_all(field, TRUE);
    +
    + /* add the field to the group created above */
    + purple_request_field_group_add_field(group, field);
    +
    + /* and finally we can create the request */
    + purple_request_fields(purple_get_blist(), _("Listhandler - Exporting"),
    + _("Choose the account to export from:"), NULL, request,
    + _("_Export"), G_CALLBACK(lh_generic_export_cb), _("_Cancel"),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    +
    +void
    +lh_generic_import_action_cb(PurplePluginAction *action)
    +{
    + purple_debug_info("listhandler: import", "Requesting the file.\n");
    +
    + purple_request_file(listhandler, _("Choose A Generic Buddy List File To Import"),
    + NULL, FALSE, G_CALLBACK(lh_generic_import_request_cb),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/gen_xml_files.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,30 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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.
    + */
    +
    +#ifndef _LISTHANDLER_GEN_XML_H
    +#define _LISTHANDLER_GEN_XML_H
    +
    +void lh_generic_import_action_cb(PurplePluginAction *action);
    +void lh_generic_export_action_cb(PurplePluginAction *action);
    +
    +#endif /*_LISTHANDLER_GEN_XML_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/lh_util.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,85 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +
    +void
    +lh_util_add_buddy(const gchar *group, PurpleGroup *purple_group,
    + const gchar *buddy, const gchar *alias, PurpleAccount *account,
    + const gchar *buddynotes, gint signed_on, gint signed_off, gint lastseen,
    + gint last_seen, const gchar *gf_theme, const gchar *icon_file, gchar *lastsaid)
    +{
    + PurpleBuddy *purple_buddy = NULL;
    + PurpleBlistNode *node = NULL;
    +
    + purple_buddy = purple_buddy_new(account, buddy, alias);
    + node = (PurpleBlistNode *)purple_buddy;
    +
    + purple_blist_add_buddy(purple_buddy, NULL, purple_group, NULL);
    + purple_account_add_buddy(account, purple_buddy);
    +
    + if(buddynotes)
    + purple_blist_node_set_string(node, "notes", buddynotes);
    + if(signed_on)
    + purple_blist_node_set_int(node, "signedon", signed_on);
    + if(signed_off)
    + purple_blist_node_set_int(node, "signedoff", signed_off);
    + if(lastseen)
    + purple_blist_node_set_int(node, "lastseen", lastseen);
    + if(last_seen)
    + purple_blist_node_set_int(node, "last_seen", last_seen);
    + if(gf_theme)
    + purple_blist_node_set_string(node, "guifications-theme", gf_theme);
    + if(icon_file)
    + purple_blist_node_set_string(node, "buddy_icon", icon_file);
    + if(lastsaid)
    + purple_blist_node_set_string(node, "lastsaid", lastsaid);
    +
    + purple_debug_info("listhandler: import",
    + "group: %s\tbuddy: %s\talias: %s\thas been added to the list\n",
    + group, buddy, alias ? alias : "NULL");
    +
    + return;
    +}
    +
    +void
    +lh_util_add_to_blist(GList *buddies, GList *groups)
    +{
    + GList *tmpb = buddies, *tmpg = groups;
    +
    + /* walk through both GLists */
    + while(tmpb && tmpb->data && tmpg && tmpg->data) {
    + /* add the current buddy to the correct group in the Purple blist */
    + purple_blist_add_buddy((PurpleBuddy *)(tmpb->data), NULL,
    + (PurpleGroup *)(tmpg->data), NULL);
    +
    + purple_debug_info("listhandler: import", "added a buddy to purple blist\n");
    +
    + /* go to the next element in each list */
    + tmpb = g_list_next(tmpb);
    + tmpg = g_list_next(tmpg);
    + }
    +
    + return;
    +}
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/lh_util.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,31 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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.
    + */
    +
    +void lh_util_add_buddy(const gchar *group, PurpleGroup *purple_group,
    + const gchar *buddy, const gchar *alias, PurpleAccount *account,
    + const gchar *buddynotes, gint signed_on, gint signed_off,
    + gint lastseen, gint last_seen, const gchar *gf_theme,
    + const gchar *icon_file, gchar *lastsaid);
    +
    +void lh_util_add_to_blist(GList *buddies, GList *groups);
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/listhandler.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,130 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +#include "aim_blt_files.h"
    +#include "alias_xml_files.h"
    +#include "gen_xml_files.h"
    +#include "purple_blist_xml.h"
    +#include "migrate.h"
    +
    +PurplePlugin *listhandler = NULL; /* the request api prefers this for a plugin */
    +
    +static GList * /* libpurple's picky and wants a GList of actions for the UI menu */
    +listhandler_actions(PurplePlugin *plugin, gpointer context)
    +{
    + GList *list = NULL;
    + PurplePluginAction *action = NULL;
    +
    + action = purple_plugin_action_new(_("Copy Buddies From One Account to Another"),
    + lh_migrate_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Import Alias List File"),
    + lh_alist_import_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Import AIM Buddy List File (.blt)"),
    + lh_aim_import_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Import Generic Buddy List File (.xml)"),
    + lh_generic_import_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Import A blist.xml From libpurple"),
    + lh_pbx_import_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Export AIM Buddy List File"),
    + lh_aim_export_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Export Alias List File"),
    + lh_alist_export_action_cb);
    + list = g_list_append(list, action);
    +
    + action = purple_plugin_action_new(_("Export Generic Buddy List File"),
    + lh_generic_export_action_cb);
    + list = g_list_append(list, action);
    +
    + purple_debug_info("listhandler", "Action list created\n");
    +
    + return list;
    +}
    +
    +static PurplePluginInfo listhandler_info =
    +{
    + PURPLE_PLUGIN_MAGIC, /* abracadabra */
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    + "core-plugin_pack-listhandler",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "John Bailey <rekkanoryo@rekkanoryo.org>",
    + PP_WEBSITE,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + listhandler_actions,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void /* purple will call this to initialize the plugin */
    +init_plugin(PurplePlugin *plugin)
    +{
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + listhandler_info.name = _("List Handler");
    + listhandler_info.summary =
    + _("Provides numerous user-requested list-handling "
    + "capabilities.");
    + listhandler_info.description =
    + _("Provides numerous user-requested list-handling "
    + "capabilities, such as importing and exporting of AIM "
    + ".blt files and generic protocol-agnostic XML .blist "
    + "files, as well as direct copying of buddies from one "
    + "account to another.");
    +
    + listhandler = plugin; /* handle needed for request API file selector */
    +
    + return;
    +}
    +
    +PURPLE_INIT_PLUGIN(listhandler, init_plugin, listhandler_info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/listhandler.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,42 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 <string.h>
    +
    +/* Purple headers */
    +#include <account.h>
    +#include <blist.h>
    +#include <debug.h>
    +#include <plugin.h>
    +#include <request.h>
    +#include <xmlnode.h>
    +
    +#include "lh_util.h"
    +
    +/* we send this to the request API so it is probably a good idea to make sure
    + * the other files that need it have it */
    +extern PurplePlugin *listhandler;
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/migrate.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,186 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +#include "migrate.h"
    +
    +static GList *buddies = NULL, *groups = NULL;
    +static PurpleAccount *source_account = NULL, *target_account = NULL;
    +static const gchar *valid_target_prpl_id = NULL;
    +
    +static gboolean
    +lh_migrate_filter(PurpleAccount *account)
    +{
    + /* get the protocol plugin id for the account passsed in */
    + const gchar *prpl_id = purple_account_get_protocol_id(account);
    +
    + /* if prpl_id is NULL, the account isn't valid */
    + if(!prpl_id)
    + return FALSE;
    +
    + /* if prpl_id matches the prpl id for valid target accounts,
    + * show this account in the request list */
    + if(!strcmp(prpl_id, valid_target_prpl_id))
    + return TRUE;
    +
    + return FALSE;
    +}
    +
    +static void
    +lh_migrate_build_lists(void)
    +{
    + PurpleBuddyList *blist = NULL;
    + PurpleBlistNode *root = NULL, *g = NULL, *c = NULL, *b = NULL;
    +
    + blist = purple_get_blist();
    + root = blist->root;
    +
    + /* walk the blist tree and build a list of the buddies and a list of
    + * the groups corresponding to each buddy */
    + /* group level */
    + for(g = root; g && PURPLE_BLIST_NODE_IS_GROUP(g); g = g->next)
    + /* contact level */
    + for(c = g->child; c && PURPLE_BLIST_NODE_IS_CONTACT(c); c = c->next)
    + /* buddy level */
    + for(b = c->child; b && PURPLE_BLIST_NODE_IS_BUDDY(b); b = b->next) {
    + PurpleGroup *tmp_group = purple_group_new(((PurpleGroup *)g)->name);
    + PurpleBuddy *tmp_buddy = (PurpleBuddy *)b;
    +
    + /* if this buddy is on the source account then add it
    + * to the GLists */
    + if(purple_buddy_get_account(tmp_buddy) == source_account) {
    + PurpleBuddy *tmp_buddy_2 = purple_buddy_new(target_account,
    + purple_buddy_get_name(tmp_buddy),
    + purple_buddy_get_alias(tmp_buddy));
    +
    + groups = g_list_prepend(groups, tmp_group);
    + buddies = g_list_prepend(buddies, tmp_buddy_2);
    + }
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_migrate_target_request_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + /* grab the target account from the request field */
    + target_account = purple_request_fields_get_account(fields,
    + "migrate_target_acct");
    +
    + /* now build GLists of the buddies and corresponding groups */
    + lh_migrate_build_lists();
    +
    + /* add the buddies to the Purple buddy list */
    + lh_util_add_to_blist(buddies, groups);
    +
    + /* add the buddies to the server-side list */
    + purple_account_add_buddies(target_account, buddies);
    +
    + /* now free the lists that were created */
    + g_list_free(buddies);
    + g_list_free(groups);
    +
    + return;
    +}
    +
    +static void
    +lh_migrate_source_request_cb(void *ignored, PurpleRequestFields *fields)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + source_account = purple_request_fields_get_account(fields,
    + "migrate_source_acct");
    + valid_target_prpl_id = purple_account_get_protocol_id(source_account);
    +
    + /* It seems Purple is super-picky about the order of these first three calls */
    + /* create a request */
    + request = purple_request_fields_new();
    +
    + /* now create a field group */
    + group = purple_request_field_group_new(NULL);
    + /* and add that group to the request created above */
    + purple_request_fields_add_group(request, group);
    +
    + /* create a field */
    + field = purple_request_field_account_new("migrate_target_acct",
    + _("Account"), NULL);
    +
    + /* mark the field as required */
    + purple_request_field_set_required(field, TRUE);
    +
    + /* filter this list so that only accounts with the same prpl as the
    + * source account can be chosen as a destination */
    + purple_request_field_account_set_filter(field, lh_migrate_filter);
    +
    + /* add the field to the group created above */
    + purple_request_field_group_add_field(group, field);
    +
    + /* and finally we can create the request */
    + purple_request_fields(purple_get_blist(), _("Listhandler - Copying"),
    + _("Choose the account to add buddies to:"), NULL,
    + request, _("_Add"),
    + G_CALLBACK(lh_migrate_target_request_cb), _("_Cancel"),
    + NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    +
    +void
    +lh_migrate_action_cb(PurplePluginAction *action)
    +{
    + PurpleRequestFields *request;
    + PurpleRequestFieldGroup *group;
    + PurpleRequestField *field;
    +
    + /* It seems Purple is super-picky about the order of these first three calls */
    + /* create a request */
    + request = purple_request_fields_new();
    +
    + /* now create a field group */
    + group = purple_request_field_group_new(NULL);
    + /* and add that group to the request created above */
    + purple_request_fields_add_group(request, group);
    +
    + /* create a field */
    + field = purple_request_field_account_new("migrate_source_acct",
    + _("Account"), NULL);
    +
    + /* mark the field as required */
    + purple_request_field_set_required(field, TRUE);
    +
    + /* let's show offline accounts too */
    + purple_request_field_account_set_show_all(field, TRUE);
    +
    + /* add the field to the group created above */
    + purple_request_field_group_add_field(group, field);
    +
    + /* and finally we can create the request */
    + purple_request_fields(purple_get_blist(), _("Listhandler - Copying"),
    + _("Choose the account to copy from:"), NULL, request,
    + _("C_opy"), G_CALLBACK(lh_migrate_source_request_cb),
    + _("_Cancel"), NULL, NULL, NULL, NULL, NULL);
    + return;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/migrate.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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.
    + */
    +#ifndef _LISTHANDLER_MIGRATE_H
    +#define _LISTHANDLER_MIGRATE_H
    +
    +void lh_migrate_action_cb(PurplePluginAction *action);
    +
    +#endif /*_LISTHANDLER_MIGRATE_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[List Handler]
    +type=default
    +depends=purple
    +provides=listhandler
    +summary=Provides numerous user-requested list-handling capabilities
    +description=Provides numerous user-requested list-handling capabilities, such as importing and exporting of AIM .blt files and generic protocol-agnostic XML .blist files, as well as direct copying of buddies from one account to another.
    +authors=John Bailey
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/purple_blist_xml.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,256 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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 "listhandler.h"
    +#include "purple_blist_xml.h"
    +
    +/*****************************************************************************
    + * Structs
    + ****************************************************************************/
    +
    +typedef struct {
    + /* important stuff for adding the buddies back */
    + gchar *screenname;
    + gchar *alias;
    + const gchar *group;
    + const gchar *account;
    + const gchar *prpl_id;
    +
    + /* useful blistnode settings */
    + gint signed_on;
    + gint signed_off;
    + gint lastseen;
    + gint last_seen;
    + gchar *gf_theme;
    + gchar *icon_file;
    + gchar *lastsaid;
    + gchar *notes;
    +
    +} LhPbxInfo;
    +
    +/*****************************************************************************
    + * Globals
    + ****************************************************************************/
    +static GList *infolist = NULL;
    +
    +/*****************************************************************************
    + * "API"
    + ****************************************************************************/
    +static LhPbxInfo *
    +lh_pbx_info_new(void)
    +{
    + return g_new0(LhPbxInfo, 1);
    +}
    +
    +static void
    +lh_pbx_info_destroy(LhPbxInfo *l)
    +{
    + /* clean up the allocated memory returned by the xmlnode api */
    + g_free(l->screenname);
    + g_free(l->alias);
    + g_free(l->gf_theme);
    + g_free(l->icon_file);
    + g_free(l->lastsaid);
    + g_free(l->notes);
    +
    + /* now clean up the memory allocated for this struct */
    + g_free(l);
    +
    + return;
    +}
    +
    +/*****************************************************************************
    + * Helpers
    + ****************************************************************************/
    +
    +static void
    +lh_pbx_import_cleanup(void)
    +{
    + GList *tmp = infolist;
    +
    + for(; tmp; tmp = g_list_next(tmp))
    + lh_pbx_info_destroy((LhPbxInfo *)(tmp->data));
    +
    + g_list_free(infolist);
    +
    + return;
    +}
    +
    +static void
    +lh_pbx_import_file_parse(const char *file)
    +{
    + GError *error = NULL;
    + LhPbxInfo *tmpinfo = NULL;
    + gchar *contents = NULL;
    + gsize length = 0;
    + xmlnode *root = NULL, *blist = NULL, *giter = NULL, *citer = NULL, *biter = NULL, *siter = NULL;
    +
    + /* grab the file contents, but bail out if there's an error */
    + if(!g_file_get_contents(file, &contents, &length, &error)) {
    + purple_debug_error("listhandler: import: blist.xml", "Error reading %s: %s\n",
    + file ? file : "(null)", error->message ? error->message : "(null)");
    +
    + return;
    + }
    +
    + /* if we get here, we have the file's contents--parse into xmlnode tree */
    + root = xmlnode_from_str(contents, -1);
    +
    + blist = xmlnode_get_child(root, "blist");
    + giter = xmlnode_get_child(blist, "group");
    +
    + for(; giter; giter = xmlnode_get_next_twin(giter)) {
    + citer = xmlnode_get_child(giter, "contact");
    +
    + for(; citer; citer = xmlnode_get_next_twin(citer)) {
    + biter = xmlnode_get_child(citer, "buddy");
    +
    + for(; biter; biter = xmlnode_get_next_twin(biter)) {
    + tmpinfo = lh_pbx_info_new();
    + siter = xmlnode_get_child(biter, "setting");
    +
    + tmpinfo->screenname = xmlnode_get_data(xmlnode_get_child(biter, "name"));
    + tmpinfo->alias = xmlnode_get_data(xmlnode_get_child(biter, "alias"));
    + tmpinfo->group = xmlnode_get_attrib(giter, "name");
    + tmpinfo->account = xmlnode_get_attrib(biter, "account");
    + tmpinfo->prpl_id = xmlnode_get_attrib(biter, "proto");
    +
    + for(; siter; siter = xmlnode_get_next_twin(siter)) {
    + const gchar *setting_name = NULL;
    + gchar *data = NULL;
    +
    + setting_name = xmlnode_get_attrib(siter, "name");
    + data = xmlnode_get_data(siter);
    +
    + if(g_ascii_strcasecmp("signedon", setting_name))
    + tmpinfo->signed_on = data ? atoi(data) : 0;
    + else if(g_ascii_strcasecmp("signedoff", setting_name))
    + tmpinfo->signed_off = data ? atoi(data) : 0;
    + else if(g_ascii_strcasecmp("lastseen", setting_name))
    + tmpinfo->lastseen = data ? atoi(data) : 0;
    + else if(g_ascii_strcasecmp("last_seen", setting_name))
    + tmpinfo->last_seen = data ? atoi(data) : 0;
    + else if(g_ascii_strcasecmp("guifications-theme", setting_name))
    + tmpinfo->gf_theme = data;
    + else if(g_ascii_strcasecmp("buddy_icon", setting_name))
    + tmpinfo->icon_file = data;
    + else if(g_ascii_strcasecmp("lastsaid", setting_name))
    + tmpinfo->lastsaid = data;
    + else if(g_ascii_strcasecmp("notes", setting_name))
    + tmpinfo->notes = data;
    + }
    +
    + infolist = g_list_prepend(infolist, tmpinfo);
    + }
    + }
    + }
    +
    + return;
    +}
    +
    +/*****************************************************************************
    + * Plugin Stuff
    + ****************************************************************************/
    +
    +static void
    +lh_pbx_import_add_buddies(void *ignored, PurpleRequestFields *fields)
    +{
    + GList *tmp = infolist;
    + LhPbxInfo *itmp = NULL;
    + PurpleAccount *target = NULL;
    + const gchar *target_name = NULL, *target_prpl = NULL;
    +
    + target = purple_request_fields_get_account(fields, "pbx_target_acct");
    +
    + target_name = purple_account_get_username(target);
    + target_prpl = purple_account_get_protocol_id(target);
    +
    + purple_debug_info("listhandler: import", "Got target account: %s on %s\n",
    + target_name, target_prpl);
    +
    + for(; tmp; tmp = tmp->next) {
    + itmp = tmp->data;
    +
    + if(!strcmp(itmp->account, target_name) && !strcmp(itmp->prpl_id, target_prpl)) {
    + purple_debug_info("listhandler: import",
    + "Current entry in infolist matches target account!\n");
    +
    + lh_util_add_buddy(itmp->group, purple_group_new(itmp->group), itmp->screenname,
    + itmp->alias, target, itmp->notes, itmp->signed_on, itmp->signed_off,
    + itmp->lastseen, itmp->last_seen, itmp->gf_theme, itmp->icon_file,
    + itmp->lastsaid);
    + }
    +
    + }
    +
    + return;
    +}
    +
    +static void
    +lh_pbx_import_target_request(void)
    +{
    + PurpleRequestField *field = NULL;
    + PurpleRequestFields *request = NULL;
    + PurpleRequestFieldGroup *group = NULL;
    +
    + request = purple_request_fields_new();
    + group = purple_request_field_group_new(NULL);
    +
    + purple_request_fields_add_group(request, group);
    +
    + field = purple_request_field_account_new("pbx_target_acct", _("Account"), NULL);
    +
    + purple_request_field_set_required(field, TRUE);
    + purple_request_field_group_add_field(group, field);
    +
    + purple_request_fields(purple_get_blist(), _("Listhandler - Importing"),
    + _("Choose the account whose buddy list you wish to restore:"),
    + NULL, request, _("_Import"), G_CALLBACK(lh_pbx_import_add_buddies),
    + _("_Cancel"), NULL, NULL, NULL, NULL, NULL);
    +
    + return;
    +}
    +
    +static void
    +lh_pbx_import_request_cb(void *user_data, const char *file)
    +{
    + purple_debug_info("listhandler: import", "In request callback\n");
    +
    + lh_pbx_import_file_parse(file);
    +
    + lh_pbx_import_target_request();
    +
    + lh_pbx_import_cleanup();
    +
    + return;
    +}
    +
    +void
    +lh_pbx_import_action_cb(PurplePluginAction *action)
    +{
    + purple_debug_info("listhandler: import", "Requesting the blist.xml to import\n");
    +
    + purple_request_file(listhandler, _("Choose a Libpurple blist.xml File To Import"),
    + NULL, FALSE, G_CALLBACK(lh_pbx_import_request_cb), NULL, NULL, NULL, NULL, NULL);
    + return;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listhandler/purple_blist_xml.h Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +/*
    + * Purple Plugin Pack
    + * Copyright (C) 2003-2008
    + * See ../AUTHORS for a list of all authors
    + *
    + * listhandler: Provides importing, exporting, and copying functions
    + * for accounts' buddy lists.
    + *
    + * 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.
    + */
    +
    +#ifndef _LISTHANDLER_PURPLE_BLIST_XML_H
    +#define _LISTHANDLER_PURPLE_BLIST_XML_H
    +
    +void lh_pbx_import_action_cb(PurplePluginAction *action);
    +
    +#endif /* _LISTHANDLER_PURPLE_BLIST_XML_H*/
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listlog/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +listlogdir = $(PIDGIN_LIBDIR)
    +
    +listlog_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +listlog_LTLIBRARIES = listlog.la
    +
    +listlog_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GLIB_LIBS)
    +
    +listlog_la_SOURCES = listlog.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/listlog/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for my status box plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = listlog
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listlog/listlog.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,137 @@
    +/*
    + * Listlog - Logs user list on chat join
    + * Copyright (C) 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 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-listlog"
    +#define PLUGIN_STATIC_NAME "listlog"
    +#define PLUGIN_AUTHOR "John Bailey <rekkanoryo@rekkanoryo.org>"
    +
    +/* libc headers */
    +#include <time.h>
    +
    +/* Purple headers */
    +#include <conversation.h>
    +#include <debug.h>
    +
    +/* Pidgin headers */
    +#include <gtkplugin.h>
    +
    +static gboolean
    +listlog_timeout_cb(gpointer data)
    +{
    + GList *users = NULL;
    + GString *message = NULL;
    + PurpleConvChat *chat = NULL;
    + PurpleConversation *conv = data;
    +
    + purple_debug_misc(PLUGIN_ID, "Conversation pointer: %p\n", conv);
    +
    + chat = purple_conversation_get_chat_data(conv);
    +
    + purple_debug_misc(PLUGIN_ID, "Chat pointer: %p\n", chat);
    +
    + users = purple_conv_chat_get_users(chat);
    +
    + purple_debug_misc(PLUGIN_ID, "List of users pointer: %p\n", users);
    +
    + message = g_string_new("List of users:\n");
    +
    + while(users) {
    + g_string_append_printf(message, "[ %s ]", purple_conv_chat_cb_get_name(users->data));
    +
    + users = users->next;
    + }
    +
    + purple_conversation_write(conv, NULL, message->str,
    + PURPLE_MESSAGE_SYSTEM | PURPLE_MESSAGE_NOTIFY, time(NULL));
    +
    + return FALSE;
    +}
    +
    +static void
    +listlog_chat_joined_cb(PurpleConversation *conv)
    +{
    + purple_timeout_add(0, listlog_timeout_cb, conv);
    +}
    +
    +static gboolean
    +listlog_load(PurplePlugin *plugin)
    +{
    + purple_signal_connect(purple_conversations_get_handle(), "chat-joined",
    + plugin, PURPLE_CALLBACK(listlog_chat_joined_cb), NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +listlog_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 */
    +
    + listlog_load, /* load */
    + listlog_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
    +listlog_init(PurplePlugin *plugin)
    +{
    +#ifdef ENABLE_NLS
    + bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
    + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
    +#endif /* ENABLE_NLS */
    +
    + info.name = _("Chat User List Logging");
    + info.summary = _("Logs the list of users present when you join a chat.");
    + info.description = _("Logs the list of users present when you join a chat.");
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, listlog_init, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/listlog/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Chat User List Logging]
    +type=default
    +depends=pidgin
    +provides=listlog
    +summary=Logs the list of users in a chat when you join
    +description=When a chat is joined, this plugin will print the list of users in the chat in the conversation window, thus causing the userlist to be logged
    +authors=John Bailey
    +introduced=2.4.0
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/mkinstalldirs Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,158 @@
    +#! /bin/sh
    +# mkinstalldirs --- make directory hierarchy
    +
    +scriptversion=2005-06-29.22
    +
    +# Original author: Noah Friedman <friedman@prep.ai.mit.edu>
    +# Created: 1993-05-16
    +# 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=
    +
    +usage="\
    +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"
    + exit $?
    + ;;
    + -m) # -m PERM arg
    + shift
    + test $# -eq 0 && { echo "$usage" 1>&2; exit 1; }
    + dirmode=$1
    + shift
    + ;;
    + --version)
    + echo "$0 $scriptversion"
    + exit $?
    + ;;
    + --) # stop option processing
    + shift
    + break
    + ;;
    + -*) # unknown option
    + echo "$usage" 1>&2
    + exit 1
    + ;;
    + *) # first non-opt arg
    + break
    + ;;
    + esac
    +done
    +
    +for file
    +do
    + if test -d "$file"; then
    + shift
    + else
    + break
    + fi
    +done
    +
    +case $# in
    + 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 --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 --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
    + case $file in
    + /*) pathcomp=/ ;;
    + *) pathcomp= ;;
    + esac
    + oIFS=$IFS
    + IFS=/
    + set fnord $file
    + shift
    + IFS=$oIFS
    +
    + for d
    + do
    + test "x$d" = x && continue
    +
    + pathcomp=$pathcomp$d
    + case $pathcomp in
    + -*) pathcomp=./$pathcomp ;;
    + esac
    +
    + if test ! -d "$pathcomp"; then
    + echo "mkdir $pathcomp"
    +
    + mkdir "$pathcomp" || lasterr=$?
    +
    + if test ! -d "$pathcomp"; then
    + errstatus=$lasterr
    + else
    + if test ! -z "$dirmode"; then
    + echo "chmod $dirmode $pathcomp"
    + lasterr=
    + chmod "$dirmode" "$pathcomp" || lasterr=$?
    +
    + if test ! -z "$lasterr"; then
    + errstatus=$lasterr
    + fi
    + fi
    + fi
    + fi
    +
    + pathcomp=$pathcomp/
    + done
    +done
    +
    +exit $errstatus
    +
    +# 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:
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/msglen/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,29 @@
    +EXTRA_DIST = \
    + plugins.cfg \
    + Makefile.mingw
    +
    +msglendir = $(PIDGIN_LIBDIR)
    +
    +msglen_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +msglen_LTLIBRARIES = msglen.la
    +
    +msglen_la_SOURCES = \
    + msglen.c
    +
    +msglen_la_LIBADD = \
    + $(GTK_LIBS) \
    + $(PIDGIN_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS) \
    + $(GTK_CFLAGS)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/msglen/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for convbadger plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = msglen
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/msglen/msglen.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,258 @@
    +/*
    + * msglen - Adds the current message's length to the menutray of a conversation
    + * Copyright (C) 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 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>
    +
    +#include <conversation.h>
    +#include <signals.h>
    +
    +#include <gtkconv.h>
    +#include <gtkmenutray.h>
    +#include <gtkplugin.h>
    +#include <gtkutils.h>
    +
    +/******************************************************************************
    + * Structs
    + *****************************************************************************/
    +typedef struct {
    + PurpleConversation *conv;
    + PidginWindow *win;
    + GtkWidget *label;
    + gulong sig_id;
    +} MsgLenData;
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +static GHashTable *data = NULL;
    +
    +/******************************************************************************
    + * helpers
    + *****************************************************************************/
    +static void
    +msg_len_data_free(MsgLenData *mld) {
    + mld->win = NULL;
    +
    + if(mld->label && GTK_IS_LABEL(mld->label))
    + gtk_widget_destroy(mld->label);
    +
    + g_free(mld);
    +
    + mld = NULL;
    +}
    +
    +static void
    +msg_len_data_free_helper(gpointer k, gpointer v, gpointer d) {
    + MsgLenData *mld = (MsgLenData *)v;
    +
    + msg_len_data_free(mld);
    +}
    +
    +static void msg_len_update(PidginWindow *win, PurpleConversation *conv);
    +
    +static gboolean
    +msg_len_key_released_cb(GtkWidget *w, GdkEventKey *e, gpointer d) {
    + MsgLenData *mld = (MsgLenData *)d;
    +
    + if(d)
    + msg_len_update(mld->win, mld->conv);
    +
    + return FALSE;
    +}
    +
    +static void
    +msg_len_add_signal(PidginConversation *pconv, MsgLenData *mld) {
    + g_signal_connect(G_OBJECT(pconv->entry),
    + "key-release-event",
    + G_CALLBACK(msg_len_key_released_cb), mld);
    +}
    +
    +static MsgLenData *
    +msg_len_find_data(PidginWindow *win) {
    + MsgLenData *mld = NULL;
    +
    + mld = g_hash_table_lookup(data, win);
    +
    + if(mld == NULL) {
    + mld = g_new0(MsgLenData, 1);
    +
    + mld->win = win;
    +
    + mld->label = gtk_label_new("");
    + pidgin_menu_tray_append(PIDGIN_MENU_TRAY(win->menu.tray), mld->label,
    + NULL);
    + gtk_widget_show(mld->label);
    +
    + g_signal_connect_swapped(G_OBJECT(mld->label), "destroy",
    + G_CALLBACK(g_nullify_pointer), &mld->label);
    + }
    +
    + return mld;
    +}
    +
    +static void
    +msg_len_update(PidginWindow *win, PurpleConversation *conv) {
    + PidginConversation *gtkconv = NULL;
    + MsgLenData *mld = NULL;
    + gchar *text = NULL;
    + gint count = 0;
    +
    + g_return_if_fail(win);
    + g_return_if_fail(conv);
    +
    + gtkconv = PIDGIN_CONVERSATION(conv);
    +
    + mld = msg_len_find_data(win);
    +
    + mld->conv = conv;
    +
    + count = gtk_text_buffer_get_char_count(gtkconv->entry_buffer);
    +
    + text = g_strdup_printf("%d", count);
    + gtk_label_set_text(GTK_LABEL(mld->label), text);
    + g_free(text);
    +
    + g_hash_table_insert(data, win, mld);
    +}
    +
    +/******************************************************************************
    + * Callbacks
    + *****************************************************************************/
    +static void
    +msg_len_conv_created_cb(PurpleConversation *conv, gpointer d) {
    + PidginConversation *pconv = PIDGIN_CONVERSATION(conv);
    + PidginWindow *win = pidgin_conv_get_window(pconv);
    + MsgLenData *mld = NULL;
    +
    + mld = msg_len_find_data(win);
    +
    + msg_len_add_signal(pconv, mld);
    +
    + msg_len_update(win, conv);
    +}
    +
    +static void
    +msg_len_conv_destroyed_cb(PurpleConversation *conv, gpointer d) {
    + g_hash_table_remove(data, conv);
    +}
    +
    +static void
    +msg_len_conv_switched_cb(PurpleConversation *conv, gpointer d) {
    + PidginConversation *pconv = PIDGIN_CONVERSATION(conv);
    + PidginWindow *win = pidgin_conv_get_window(pconv);
    +
    + msg_len_update(win, conv);
    +}
    +
    +/******************************************************************************
    + * Plugin Stuff
    + *****************************************************************************/
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + GList *convs = NULL;
    + void *conv_handle = purple_conversations_get_handle();
    +
    + data = g_hash_table_new_full(g_direct_hash, g_direct_equal,
    + NULL, NULL);
    +
    + purple_signal_connect(conv_handle, "conversation-created", plugin,
    + PURPLE_CALLBACK(msg_len_conv_created_cb), NULL);
    + purple_signal_connect(conv_handle, "deleting-conversation", plugin,
    + PURPLE_CALLBACK(msg_len_conv_destroyed_cb), NULL);
    +
    + purple_signal_connect(pidgin_conversations_get_handle(),
    + "conversation-switched", plugin,
    + PURPLE_CALLBACK(msg_len_conv_switched_cb), NULL);
    +
    + for(convs = purple_get_conversations(); convs; convs = convs->next) {
    + PurpleConversation *conv = (PurpleConversation *)convs->data;
    + PidginConversation *pconv = PIDGIN_CONVERSATION(conv);
    + MsgLenData *mld = msg_len_find_data(pconv->win);
    +
    + msg_len_add_signal(pconv, mld);
    +
    + msg_len_update(pconv->win, conv);
    + }
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin) {
    + g_hash_table_foreach(data, msg_len_data_free_helper, NULL);
    +
    + g_hash_table_destroy(data);
    +
    + data = NULL;
    +
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info = {
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + PIDGIN_PLUGIN_TYPE,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + "gtk-plugin_pack-msglen",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + 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 = _("Message Length");
    + info.summary = _("Shows the length of your current message in the menu "
    + "tray");
    + info.description = info.summary;
    +}
    +
    +PURPLE_INIT_PLUGIN(msg_len, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/msglen/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,8 @@
    +[Message Length]
    +type=incomplete
    +depends=pidgin
    +provides=msglen
    +summary=Shows the length of your current message in the menu tray
    +description=%(summary)s
    +authors=Gary Kramlich
    +introduced=2.4.0
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/mystatusbox/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +mystatusboxdir = $(PIDGIN_LIBDIR)
    +
    +mystatusbox_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +mystatusbox_LTLIBRARIES = mystatusbox.la
    +
    +mystatusbox_la_SOURCES = \
    + mystatusbox.c
    +
    +mystatusbox_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/mystatusbox/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for my status box plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = mystatusbox
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/mystatusbox/mystatusbox.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,574 @@
    +/*
    + * mystatusbox - Show/Hide the peraccount statusboxes
    + * 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
    + * 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"
    +
    +#define PLUGIN_ID "gtk-plugin_pack-mystatusbox"
    +#define PLUGIN_STATIC_NAME "mystatusbox"
    +#define PLUGIN_AUTHOR "Sadrul Habib Chowdhury <sadrul@users.sourceforge.net>"
    +
    +/* System headers */
    +#include <gdk/gdk.h>
    +#include <gtk/gtk.h>
    +
    +#include <core.h>
    +
    +#include <gtkblist.h>
    +#include <gtkmenutray.h>
    +#include <gtkplugin.h>
    +#include <gtkstatusbox.h>
    +
    +/* XXX: THIS NEEDS CHANGED WHEN PIDGIN DOES ITS PREFS MIGRATION!!!!! */
    +#define PREF_PREFIX "/plugins/gtk/" PLUGIN_ID
    +#define PREF_PANE PREF_PREFIX "/pane"
    +#define PREF_GLOBAL PREF_PREFIX "/global"
    +#define PREF_SHOW PREF_PREFIX "/show"
    +#define PREF_ICONS_HIDE PREF_PREFIX "/iconsel"
    +
    +static GtkWidget *gtkblist_statusboxbox;
    +static GList *gtkblist_statusboxes;
    +
    +typedef enum
    +{
    + PURPLE_STATUSBOX_ALL,
    + PURPLE_STATUSBOX_NONE,
    + PURPLE_STATUSBOX_OUT_SYNC
    +} PurpleStatusBoxVisibility;
    +
    +static void pidgin_status_selectors_show(PurpleStatusBoxVisibility action);
    +
    +static gboolean
    +pane_position_cb(GtkPaned *paned, GParamSpec *param_spec, gpointer data)
    +{
    + purple_prefs_set_int(PREF_PANE, gtk_paned_get_position(paned));
    + return FALSE;
    +}
    +
    +static void
    +account_enabled_cb(PurpleAccount *account, gpointer null)
    +{
    + if (purple_account_get_enabled(account, purple_core_get_ui()))
    + {
    + GtkWidget *box = pidgin_status_box_new_with_account(account);
    + g_object_set(box, "iconsel", !purple_prefs_get_bool(PREF_ICONS_HIDE), NULL);
    + gtk_widget_set_name(box, "pidginblist_statusbox_account");
    + gtk_box_pack_start(GTK_BOX(gtkblist_statusboxbox), box, FALSE, TRUE, 0);
    + gtk_widget_show(box);
    + gtkblist_statusboxes = g_list_append(gtkblist_statusboxes, box);
    + }
    +}
    +
    +static void
    +account_disabled_cb(PurpleAccount *account, gpointer null)
    +{
    + GList *iter;
    +
    + for (iter = gtkblist_statusboxes; iter; iter = iter->next)
    + {
    + PidginStatusBox *box = iter->data;
    + if (box->account == account)
    + {
    + gtkblist_statusboxes = g_list_remove(gtkblist_statusboxes, box);
    + gtk_widget_destroy(GTK_WIDGET(box));
    + break;
    + }
    + }
    +}
    +
    +static void
    +update_out_of_sync()
    +{
    + if (purple_prefs_get_int(PREF_SHOW) == PURPLE_STATUSBOX_OUT_SYNC)
    + pidgin_status_selectors_show(PURPLE_STATUSBOX_OUT_SYNC);
    +}
    +
    +static void
    +detach_per_account_boxes()
    +{
    + PidginBuddyList *gtkblist;
    + GList *list, *iter;
    + int i;
    + gboolean headline_showing;
    + GtkWidget **holds;
    +
    + gtkblist = pidgin_blist_get_default_gtk_blist();
    + if (!gtkblist)
    + return;
    +
    + holds = (GtkWidget*[]){gtkblist->headline_hbox->parent, gtkblist->treeview->parent,
    + gtkblist->error_buttons, gtkblist->statusbox,
    + gtkblist->scrollbook, NULL};
    +
    + headline_showing = GTK_WIDGET_VISIBLE(gtkblist->headline_hbox) && GTK_WIDGET_DRAWABLE(gtkblist->headline_hbox);
    +
    + for (i = 0; holds[i]; i++)
    + {
    + g_object_ref(G_OBJECT(holds[i]));
    + gtk_container_remove(GTK_CONTAINER(holds[i]->parent), holds[i]);
    + }
    +
    + list = gtk_container_get_children(GTK_CONTAINER(gtkblist->vbox));
    + for (iter = list; iter; iter = iter->next)
    + gtk_container_remove(GTK_CONTAINER(gtkblist->vbox), iter->data);
    +
    +
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->headline_hbox->parent, FALSE, FALSE, 0);
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->treeview->parent, TRUE, TRUE, 0);
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtk_hseparator_new(), FALSE, FALSE, 0);
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->error_buttons, FALSE, FALSE, 0);
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, FALSE, 0);
    +
    + if (!headline_showing)
    + gtk_widget_hide(gtkblist->headline_hbox);
    +
    + for (i = 0; holds[i]; i++)
    + g_object_unref(G_OBJECT(holds[i]));
    +
    + gtkblist_statusboxbox = NULL;
    +}
    +
    +static void
    +attach_per_account_boxes()
    +{
    + PidginBuddyList *gtkblist;
    + GList *list, *iter;
    + GtkWidget *sw2, *vpane, *vbox;
    + gboolean headline_showing;
    +
    + gtkblist = pidgin_blist_get_default_gtk_blist();
    +
    + if (!gtkblist || !gtkblist->window || gtkblist_statusboxbox)
    + return;
    +
    + headline_showing = GTK_WIDGET_VISIBLE(gtkblist->headline_hbox) && GTK_WIDGET_DRAWABLE(gtkblist->headline_hbox);
    +
    + gtkblist_statusboxbox = gtk_vbox_new(FALSE, 0);
    + gtkblist_statusboxes = NULL;
    +
    + list = purple_accounts_get_all_active();
    + for (iter = list; iter; iter = iter->next)
    + {
    + account_enabled_cb(iter->data, NULL);
    + }
    + g_list_free(list);
    +
    + gtk_widget_show_all(gtkblist_statusboxbox);
    +
    + list = gtk_container_get_children(GTK_CONTAINER(gtkblist->vbox));
    + for (iter = list; iter; iter = iter->next)
    + {
    + if (!GTK_IS_SEPARATOR(iter->data))
    + g_object_ref(iter->data);
    + gtk_container_remove(GTK_CONTAINER(gtkblist->vbox), iter->data);
    + }
    +
    + vbox = gtk_vbox_new(FALSE, 0);
    + gtk_box_pack_start(GTK_BOX(vbox), gtkblist->headline_hbox->parent, FALSE, FALSE, 0);
    + g_object_unref(G_OBJECT(gtkblist->headline_hbox->parent));
    + gtk_box_pack_start(GTK_BOX(vbox), gtkblist->treeview->parent, TRUE, TRUE, 0);
    + g_object_unref(G_OBJECT(gtkblist->treeview->parent));
    + gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(), FALSE, FALSE, 0);
    + gtk_box_pack_start(GTK_BOX(vbox), gtkblist->scrollbook, FALSE, FALSE, 0);
    + g_object_unref(G_OBJECT(gtkblist->scrollbook));
    + gtk_box_pack_start(GTK_BOX(vbox), gtkblist->error_buttons, FALSE, FALSE ,0);
    + g_object_unref(G_OBJECT(gtkblist->error_buttons));
    +
    + vpane = gtk_vpaned_new();
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), vpane, TRUE, TRUE, 0);
    +
    + gtk_paned_pack1(GTK_PANED(vpane), vbox, TRUE, FALSE);
    +
    + sw2 = gtk_scrolled_window_new(NULL, NULL);
    + gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw2), gtkblist_statusboxbox);
    + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw2), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    + gtk_widget_show(sw2);
    + gtk_paned_pack2(GTK_PANED(vpane), sw2, FALSE, TRUE);
    + gtk_widget_show_all(vpane);
    +
    + gtk_box_pack_start(GTK_BOX(gtkblist->vbox), gtkblist->statusbox, FALSE, TRUE, 0);
    + g_object_unref(G_OBJECT(gtkblist->statusbox));
    + if (purple_prefs_get_bool(PREF_GLOBAL))
    + gtk_widget_hide(gtkblist->statusbox);
    + else
    + gtk_widget_show(gtkblist->statusbox);
    +
    + if (!headline_showing)
    + gtk_widget_hide(gtkblist->headline_hbox);
    +
    + g_object_set(gtkblist->statusbox, "iconsel", !purple_prefs_get_bool(PREF_ICONS_HIDE), NULL);
    +
    + pidgin_status_selectors_show(purple_prefs_get_int(PREF_SHOW));
    +
    + gtk_paned_set_position(GTK_PANED(vpane), purple_prefs_get_int(PREF_PANE));
    + g_signal_connect(G_OBJECT(vpane), "notify::position",
    + G_CALLBACK(pane_position_cb), NULL);
    +}
    +
    +static void
    +pidgin_status_selectors_show(PurpleStatusBoxVisibility action)
    +{
    + GtkRequisition req;
    + int height;
    + GList *list;
    + PidginBuddyList *gtkblist;
    +
    + gtkblist = pidgin_blist_get_default_gtk_blist();
    +
    + purple_prefs_set_int(PREF_SHOW, action);
    +
    + if (!gtkblist || !gtkblist_statusboxbox || !gtkblist->window ||
    + !PIDGIN_IS_STATUS_BOX(gtkblist->statusbox))
    + return;
    +
    + height = purple_prefs_get_int("/pidgin/blist/height");
    +
    + if (!purple_prefs_get_bool(PREF_GLOBAL))
    + {
    + gtk_widget_size_request(gtkblist->statusbox, &req);
    + height -= req.height; /* Height of the global statusbox */
    + }
    +
    + list = gtkblist_statusboxes;
    + for (; list; list = list->next)
    + {
    + GtkWidget *box = list->data;
    +
    + if (action == PURPLE_STATUSBOX_ALL) /* Show all */
    + {
    + gtk_widget_show_all(box);
    + gtk_widget_size_request(box, &req);
    + height -= req.height;
    + }
    + else if (action == PURPLE_STATUSBOX_NONE) /* Show none */
    + {
    + gtk_widget_hide_all(box);
    + }
    + else if (action == PURPLE_STATUSBOX_OUT_SYNC) /* Show non-synced ones */
    + {
    + PurpleAccount *account = PIDGIN_STATUS_BOX(box)->account;
    + PurpleStatus *status;
    + PurpleStatusPrimitive account_prim, global_prim;
    + PurpleSavedStatus *saved;
    + PurpleSavedStatusSub *sub;
    + gboolean show = TRUE;
    + const char *global_message;
    +
    + /* This, unfortunately, is necessary, until (if at all) #1440568 gets in */
    + if (!purple_account_is_connected(account))
    + status = purple_account_get_status(account, "offline");
    + else
    + status = purple_account_get_active_status(account);
    +
    + account_prim = purple_status_type_get_primitive(purple_status_get_type(status));
    +
    + saved = purple_savedstatus_get_current();
    + sub = purple_savedstatus_get_substatus(saved, account);
    + if (sub)
    + {
    + global_prim = purple_status_type_get_primitive(purple_savedstatus_substatus_get_type(sub));
    + global_message = purple_savedstatus_substatus_get_message(sub);
    + }
    + else
    + {
    + global_prim = purple_savedstatus_get_type(saved);
    + global_message = purple_savedstatus_get_message(saved);
    + }
    +
    + if (global_prim == account_prim)
    + {
    + if (purple_status_type_get_attr(purple_status_get_type(status), "message"))
    + {
    + const char *message = NULL;
    +
    + message = purple_status_get_attr_string(status, "message");
    +
    + if ((global_message == NULL && message == NULL) ||
    + (global_message && message && g_utf8_collate(global_message, message) == 0))
    + show = FALSE;
    + }
    + else
    + show = FALSE;
    + }
    +
    + if (show)
    + {
    + gtk_widget_show_all(box);
    + gtk_widget_size_request(box, &req);
    + height -= req.height;
    + }
    + else
    + gtk_widget_hide_all(box);
    + }
    + }
    +
    + if (GTK_WIDGET_DRAWABLE(gtkblist->scrollbook) && GTK_WIDGET_VISIBLE(gtkblist->scrollbook)) {
    + gtk_widget_size_request(gtkblist->scrollbook, &req);
    + height -= req.height; /* Height of the scrollbook */
    + height -= 3; /* the separator */
    + }
    +
    + if (GTK_WIDGET_VISIBLE(gtkblist->menutray->parent)) {
    + gtk_widget_size_request(gtkblist->menutray->parent, &req);
    + height -= req.height; /* Height of the menu */
    + }
    +
    + height -= 9; /* Height of the handle of the vpane */
    +
    + gtk_paned_set_position(GTK_PANED(gtkblist_statusboxbox->parent->parent->parent), height);
    +}
    +
    +static void
    +show_boxes_all(PurplePluginAction *action)
    +{
    + pidgin_status_selectors_show(PURPLE_STATUSBOX_ALL);
    +}
    +
    +static void
    +show_boxes_none(PurplePluginAction *action)
    +{
    + pidgin_status_selectors_show(PURPLE_STATUSBOX_NONE);
    +}
    +
    +static void
    +show_boxes_out_of_sync(PurplePluginAction *action)
    +{
    + pidgin_status_selectors_show(PURPLE_STATUSBOX_OUT_SYNC);
    +}
    +
    +static void
    +toggle_icons(PurplePluginAction *action)
    +{
    + gboolean v = purple_prefs_get_bool(PREF_ICONS_HIDE);
    + purple_prefs_set_bool(PREF_ICONS_HIDE, !v);
    +}
    +
    +static void
    +toggle_global(PurplePluginAction *action)
    +{
    + gboolean v = purple_prefs_get_bool(PREF_GLOBAL);
    + purple_prefs_set_bool(PREF_GLOBAL, !v);
    +}
    +
    +static GList *
    +actions(PurplePlugin *plugin, gpointer context)
    +{
    + GList *l = NULL;
    + PurplePluginAction *act = NULL;
    +
    + act = purple_plugin_action_new(_("All"), show_boxes_all);
    + l = g_list_append(l, act);
    +
    + act = purple_plugin_action_new(_("None"), show_boxes_none);
    + l = g_list_append(l, act);
    +
    + act = purple_plugin_action_new(_("Out of sync ones"), show_boxes_out_of_sync);
    + l = g_list_append(l, act);
    +
    + l = g_list_append(l, NULL);
    +
    + act = purple_plugin_action_new(_("Toggle icon selectors"), toggle_icons);
    + l = g_list_append(l, act);
    +
    + act = purple_plugin_action_new(_("Toggle global selector"), toggle_global);
    + l = g_list_append(l, act);
    +
    + return l;
    +}
    +
    +static void
    +toggle_iconsel_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer null)
    +{
    + GList *iter = gtkblist_statusboxes;
    + gboolean value = !GPOINTER_TO_INT(val);
    + PidginBuddyList *gtkblist;
    +
    + for (; iter; iter = iter->next)
    + {
    + g_object_set(iter->data, "iconsel", value, NULL);
    + }
    +
    + gtkblist = pidgin_blist_get_default_gtk_blist();
    + if (gtkblist)
    + g_object_set(gtkblist->statusbox, "iconsel", value, NULL);
    +}
    +
    +static void
    +hide_global_callback(const char *name, PurplePrefType type, gconstpointer val, gpointer null)
    +{
    + PidginBuddyList *gtkblist = pidgin_blist_get_default_gtk_blist();
    + gboolean hide = GPOINTER_TO_UINT(val);
    +
    + if (!gtkblist)
    + return;
    +
    + if (hide)
    + gtk_widget_hide(gtkblist->statusbox);
    + else
    + gtk_widget_show(gtkblist->statusbox);
    +}
    +
    +static void
    +account_status_changed_cb(PurpleAccount *account, PurpleStatus *os, PurpleStatus *ns, gpointer null)
    +{
    + update_out_of_sync();
    +}
    +
    +static void
    +global_status_changed_cb(const char *name, PurplePrefType type, gconstpointer val, gpointer null)
    +{
    + update_out_of_sync();
    +}
    +
    +static void
    +signed_on_off_cb(PurpleConnection *gc, gpointer null)
    +{
    + update_out_of_sync();
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + attach_per_account_boxes();
    +
    + purple_signal_connect(pidgin_blist_get_handle(), "gtkblist-created",
    + plugin, PURPLE_CALLBACK(attach_per_account_boxes), NULL);
    + purple_signal_connect(purple_accounts_get_handle(), "account-added", plugin,
    + PURPLE_CALLBACK(account_enabled_cb), NULL);
    + purple_signal_connect(purple_accounts_get_handle(), "account-enabled", plugin,
    + PURPLE_CALLBACK(account_enabled_cb), NULL);
    + purple_signal_connect(purple_accounts_get_handle(), "account-removed", plugin,
    + PURPLE_CALLBACK(account_disabled_cb), NULL);
    + purple_signal_connect(purple_accounts_get_handle(), "account-disabled", plugin,
    + PURPLE_CALLBACK(account_disabled_cb), NULL);
    + purple_signal_connect(purple_accounts_get_handle(), "account-status-changed", plugin,
    + PURPLE_CALLBACK(account_status_changed_cb), NULL);
    + purple_signal_connect(purple_connections_get_handle(), "signed-on", plugin,
    + PURPLE_CALLBACK(signed_on_off_cb), NULL);
    + purple_signal_connect(purple_connections_get_handle(), "signed-off", plugin,
    + PURPLE_CALLBACK(signed_on_off_cb), NULL);
    +
    + purple_prefs_connect_callback(plugin, PREF_GLOBAL, hide_global_callback, NULL);
    + purple_prefs_connect_callback(plugin, PREF_ICONS_HIDE, toggle_iconsel_cb, NULL);
    + purple_prefs_connect_callback(plugin, "/core/savedstatus/current", global_status_changed_cb, NULL);
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + PidginBuddyList *gtkblist;
    +
    + pidgin_status_selectors_show(PURPLE_STATUSBOX_ALL);
    + detach_per_account_boxes();
    +
    + gtkblist = pidgin_blist_get_default_gtk_blist();
    + if (gtkblist)
    + gtk_widget_show(gtkblist->statusbox);
    + purple_prefs_disconnect_by_handle(plugin);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_GLOBAL, _("Hide global status selector"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_ICONS_HIDE, _("Hide icon-selectors"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +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 */
    + &prefs_info, /* prefs_info */
    + actions, /* 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 = _("Mystatusbox (Show Statusboxes)");
    + info.summary = _("Hide/Show the per-account statusboxes");
    + info.description = _("You can show all the per-account statusboxes, hide "
    + "all of them, or just show the ones that are in a different status "
    + "from the global status. For ease of use, you can bind keyboard "
    + "shortcuts for the menu items.");
    +
    + purple_prefs_add_none(PREF_PREFIX);
    + purple_prefs_add_int(PREF_PANE, 300);
    + purple_prefs_add_bool(PREF_GLOBAL, FALSE);
    + purple_prefs_add_int(PREF_SHOW, PURPLE_STATUSBOX_ALL);
    + purple_prefs_add_bool(PREF_ICONS_HIDE, FALSE);
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/mystatusbox/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Mystatusbox]
    +type=default
    +depends=pidgin
    +provides=mystatusbox
    +summary=Hide/Show the per-account statusboxes
    +description=You can show all the per-account statusboxes, hide all of them, or just show the ones that are in a different status from the global status. For ease of use, you can bind keyboard shortcuts for the menu items.
    +authors=Sadrul Habib Chowdhury
    +introduced=1.0beta1
    +
    Binary file napster/16/napster.png has changed
    Binary file napster/22/napster.png has changed
    Binary file napster/48/napster.png has changed
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/napster/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,47 @@
    +PIXMAPS = \
    + 16/napster.png \
    + 22/napster.png \
    + 48/napster.png
    +
    +EXTRA_DIST=\
    + Makefile.mingw \
    + plugins.cfg \
    + $(PIXMAPS)
    +
    +napsterdir = $(PURPLE_LIBDIR)
    +
    +napster_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +napster_LTLIBRARIES = napster.la
    +
    +napster_la_SOURCES = \
    + napster.c
    +
    +napster_la_LIBADD = \
    + $(GLIB_LIBS) \
    + $(PURPLE_LIBS)
    +
    +if HAVE_PIDGIN
    +napsterpix16dir=$(PIDGIN_PIXMAPSDIR)/protocols/16
    +napsterpix16_DATA=16/napster.png
    +
    +napsterpix22dir=$(PIDGIN_PIXMAPSDIR)/protocols/22
    +napsterpix22_DATA=22/napster.png
    +
    +napsterpix48dir=$(PIDGIN_PIXMAPSDIR)/protocols/48
    +napsterpix48_DATA=48/napster.png
    +
    +endif
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PURPLE_LIBDIR)\" \
    + -DDATADIR=\"$(PURPLE_DATADIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(PURPLE_CFLAGS)
    +
    +bullshit:
    + echo $(DESTDIR)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/napster/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,11 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for napster plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = napster
    +
    +include $(PP_TOP)/win_pp.mak
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/napster/napster.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,759 @@
    +/*
    + * napster - Napster Protocol Plugin
    + *
    + * Copyright (C) 2000-2001, Rob Flynn <rob@marko.net>
    + *
    + * Assimilated for inclusion in the Plugin Pack:
    + * 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
    + * 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 <account.h>
    +#include <accountopt.h>
    +#include <blist.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <notify.h>
    +#include <prpl.h>
    +#include <proxy.h>
    +#include <util.h>
    +
    +#include <errno.h>
    +#include <fcntl.h>
    +#include <string.h>
    +#include <unistd.h>
    +
    +#define NAP_SERVER "64.124.41.187"
    +#define NAP_PORT 8888
    +
    +#define NAPSTER_CONNECT_STEPS 2
    +
    +GSList *nap_connections = NULL;
    +
    +struct nap_data {
    + int fd;
    + gchar *email;
    +};
    +
    +static PurpleConversation *
    +nap_find_chat(PurpleConnection *gc, const char *name) {
    + GSList *bcs = gc->buddy_chats;
    +
    + while (bcs) {
    + PurpleConversation *b = bcs->data;
    + if (!purple_utf8_strcasecmp(b->name, name))
    + return b;
    + bcs = bcs->next;
    + }
    +
    + return NULL;
    +}
    +
    +static void
    +nap_write_packet(PurpleConnection *gc, unsigned short command,
    + const char *format, ...)
    +{
    + struct nap_data *ndata = (struct nap_data *)gc->proto_data;
    + va_list ap;
    + gchar *message;
    + unsigned short size;
    +
    + va_start(ap, format);
    + message = g_strdup_vprintf(format, ap);
    + va_end(ap);
    +
    + size = strlen(message);
    + purple_debug(PURPLE_DEBUG_MISC, "napster", "S %3hd: %s\n", command, message);
    +
    + write(ndata->fd, &size, 2);
    + write(ndata->fd, &command, 2);
    + write(ndata->fd, message, size);
    +
    + g_free(message);
    +}
    +
    +static int
    +nap_do_irc_style(PurpleConnection *gc, const char *message, const char *name) {
    + gchar **res;
    +
    + purple_debug(PURPLE_DEBUG_MISC, "napster", "C %s\n", message);
    +
    + res = g_strsplit(message, " ", 2);
    +
    + if (!g_ascii_strcasecmp(res[0], "/ME")) { /* MSG_CLIENT_PUBLIC */
    + nap_write_packet(gc, 824, "%s \"%s\"", name, res[1]);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/MSG")) { /* MSG_CLIENT_PUBLIC */
    + nap_write_packet(gc, 205, "%s", res[1]);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/JOIN")) { /* join chatroom MSG_CLIENT_JOIN */
    + if (!res[1]) {
    + g_strfreev(res);
    + return 1;
    + }
    + if (res[1][0] != '#')
    + nap_write_packet(gc, 400, "#%s", res[1]);
    + else
    + nap_write_packet(gc, 400, "%s", res[1]);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/PART")) { /* partchatroom MSG_CLIENT_PART */
    + nap_write_packet(gc, 401, "%s", res[1] ? res[1] : name);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/TOPIC")) { /* set topic MSG_SERVER_TOPIC */
    + nap_write_packet(gc, 410, "%s", res[1] ? res[1] : name);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/WHOIS")) { /* whois request MSG_CLIENT_WHOIS */
    + nap_write_packet(gc, 603, "%s", res[1]);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/PING")) { /* send ping MSG_CLIENT_PING */
    + nap_write_packet(gc, 751, "%s", res[1]);
    +
    + } else if (!g_ascii_strcasecmp(res[0], "/KICK")) { /* kick asswipe MSG_CLIENT_KICK */
    + nap_write_packet(gc, 829, "%s", res[1]);
    +
    + } else {
    + g_strfreev(res);
    + return 1;
    + }
    +
    + g_strfreev(res);
    + return 0;
    +}
    +
    +/* 205 - MSG_CLIENT_PRIVMSG */
    +static int
    +nap_send_im(PurpleConnection *gc, const char *who, const char *message,
    + PurpleMessageFlags flags)
    +{
    + char *tmp = purple_unescape_html(message);
    +
    + if ((strlen(tmp) < 2) || (tmp[0] != '/' ) || (tmp[1] == '/')) {
    + /* Actually send a chat message */
    + nap_write_packet(gc, 205, "%s %s", who, tmp);
    + } else {
    + /* user typed an IRC-style command */
    + nap_do_irc_style(gc, tmp, who);
    + }
    +
    + g_free(tmp);
    +
    + return 1;
    +}
    +
    +/* 207 - MSG_CLIENT_ADD_HOTLIST */
    +static void
    +nap_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) {
    + nap_write_packet(gc, 207, "%s", buddy->name);
    +}
    +
    +/* 208 - MSG_CLIENT_ADD_HOTLIST_SEQ */
    +static void
    +nap_send_buddylist(PurpleConnection *gc) {
    + PurpleBuddyList *blist;
    + PurpleBlistNode *gnode, *cnode, *bnode;
    + PurpleBuddy *buddy;
    +
    + if ((blist = purple_get_blist()) != NULL)
    + {
    + for (gnode = blist->root; gnode != NULL; gnode = gnode->next)
    + {
    + if (!PURPLE_BLIST_NODE_IS_GROUP(gnode))
    + continue;
    + for (cnode = gnode->child; cnode != NULL; cnode = cnode->next)
    + {
    + if (!PURPLE_BLIST_NODE_IS_CONTACT(cnode))
    + continue;
    + for (bnode = cnode->child; bnode != NULL; bnode = bnode->next)
    + {
    + if (!PURPLE_BLIST_NODE_IS_BUDDY(bnode))
    + continue;
    + buddy = (PurpleBuddy *)bnode;
    + nap_write_packet(gc, 208, "%s", buddy->name);
    + }
    + }
    + }
    + }
    +}
    +
    +/* 303 - MSG_CLIENT_REMOVE_HOTLIST */
    +static void
    +nap_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) {
    + nap_write_packet(gc, 303, "%s", buddy->name);
    +}
    +
    +static char *
    +nap_get_chat_name(GHashTable *data) {
    + char *name = g_hash_table_lookup(data, "group");
    +
    + /* Make sure the name has a # preceding it */
    + if (name[0] != '#') {
    + return g_strdup_printf("#%s", name);
    + }
    +
    + return g_strdup(name);
    +}
    +
    +/* 400 - MSG_CLIENT_JOIN */
    +static void
    +nap_join_chat(PurpleConnection *gc, GHashTable *data) {
    + char *name;
    +
    + if (!data)
    + return;
    +
    + name = nap_get_chat_name(data);
    +
    + if (name) {
    + nap_write_packet(gc, 400, "%s", name);
    + g_free(name);
    + }
    +}
    +
    +/* 401 - MSG_CLIENT_PART */
    +static void
    +nap_chat_leave(PurpleConnection *gc, int id) {
    + PurpleConversation *c = purple_find_chat(gc, id);
    +
    + if (!c)
    + return;
    +
    + nap_write_packet(gc, 401, "%s", c->name);
    +}
    +
    +/* 402 - MSG_CLIENT_PUBLIC */
    +static int
    +nap_chat_send(PurpleConnection *gc, int id, const char *message,
    + PurpleMessageFlags flags)
    +{
    + PurpleConversation *c = purple_find_chat(gc, id);
    + char *tmp = purple_unescape_html(message);
    +
    + if (!c)
    + return -EINVAL;
    +
    + if ((strlen(tmp) < 2) || (tmp[0] != '/' ) || (tmp[1] == '/')) {
    + /* Actually send a chat message */
    + nap_write_packet(gc, 402, "%s %s", c->name, tmp);
    + } else {
    + /* user typed an IRC-style command */
    + nap_do_irc_style(gc, tmp, c->name);
    + }
    +
    + g_free(tmp);
    +
    + return 0;
    +}
    +
    +/* 603 - MSG_CLIENT_WHOIS */
    +static void
    +nap_get_info(PurpleConnection *gc, const char *who) {
    + nap_write_packet(gc, 603, "%s", who);
    +}
    +
    +static void
    +nap_callback(gpointer data, gint source, PurpleInputCondition condition) {
    + PurpleConnection *gc = data;
    + struct nap_data *ndata = gc->proto_data;
    + 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;
    + }
    +
    + len = header[0];
    + command = header[1];
    + buf = (gchar *)g_malloc((len + 1) * sizeof(gchar));
    + buf[len] = '\0';
    +
    + i = 0;
    + do {
    + 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);
    + purple_connection_error(gc, buf);
    + g_free(buf);
    + return;
    + }
    + i += tmp;
    + } while (i != len);
    +
    + purple_debug(PURPLE_DEBUG_MISC, "napster", "R %3hd: %s\n", command, buf);
    +
    + switch (command) {
    + case 000: /* MSG_SERVER_ERROR */
    + purple_notify_error(gc, NULL, buf, NULL);
    + purple_input_remove(gc->inpa);
    + gc->inpa = 0;
    + close(source);
    + purple_connection_error(gc, _("Unknown server error."));
    + break;
    +
    + case 003: /* MSG_SERVER_EMAIL */
    + purple_debug(PURPLE_DEBUG_MISC, "napster", "Registered with e-mail address: %s\n", buf);
    + ndata->email = g_strdup(buf);
    +
    + /* Our signon is complete */
    + purple_connection_set_state(gc, PURPLE_CONNECTED);
    +
    + /* Send the server our buddy list */
    + nap_send_buddylist(gc);
    +
    + break;
    +
    + case 201: /* MSG_SERVER_SEARCH_RESULT */
    + res = g_strsplit(buf, " ", 0);
    + purple_prpl_got_user_status(account, res[0], "available", NULL);
    + g_strfreev(res);
    + break;
    +
    + case 202: /* MSG_SERVER_SEARCH_END */
    + purple_prpl_got_user_status(account, buf, "offline", NULL);
    + break;
    +
    + case 205: /* MSG_CLIENT_PRIVMSG */
    + res = g_strsplit(buf, " ", 2);
    + buf2 = g_markup_escape_text(res[1], -1);
    + serv_got_im(gc, res[0], buf2, 0, time(NULL));
    + g_free(buf2);
    + g_strfreev(res);
    + break;
    +
    + case 209: /* MSG_SERVER_USER_SIGNON */
    + /* USERNAME SPEED */
    + res = g_strsplit(buf, " ", 2);
    + purple_prpl_got_user_status(account, res[0], "available", NULL);
    + g_strfreev(res);
    + break;
    +
    + case 210: /* MSG_SERVER_USER_SIGNOFF */
    + /* USERNAME SPEED */
    + res = g_strsplit(buf, " ", 2);
    + purple_prpl_got_user_status(account, res[0], "offline", NULL);
    + g_strfreev(res);
    + break;
    +
    + case 214: /* MSG_SERVER_STATS */
    + res = g_strsplit(buf, " ", 3);
    + buf2 = g_strdup_printf(_("users: %s, files: %s, size: %sGB"), res[0], res[1], res[2]);
    + serv_got_im(gc, "server", buf2, 0, time(NULL));
    + g_free(buf2);
    + g_strfreev(res);
    + break;
    +
    + case 301: /* MSG_SERVER_HOTLIST_ACK */
    + /* Our buddy was added successfully */
    + break;
    +
    + case 302: /* MSG_SERVER_HOTLIST_ERROR */
    + buf2 = g_strdup_printf(_("Unable to add \"%s\" to your Napster hotlist"), buf);
    + purple_notify_error(gc, NULL, buf2, NULL);
    + g_free(buf2);
    + break;
    +
    + case 316: /* MSG_SERVER_DISCONNECTING */
    + /* we have been kicked off =^( */
    + purple_connection_error(gc, _("You were disconnected from the server."));
    + break;
    +
    + case 401: /* MSG_CLIENT_PART */
    + c = nap_find_chat(gc, buf);
    + if (c)
    + serv_got_chat_left(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)));
    + break;
    +
    + case 403: /* MSG_SERVER_PUBLIC */
    + res = g_strsplit(buf, " ", 3);
    + c = nap_find_chat(gc, res[0]);
    + if (c)
    + serv_got_chat_in(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), res[1], 0, res[2], time((time_t)NULL));
    + g_strfreev(res);
    + break;
    +
    + case 404: /* MSG_SERVER_NOSUCH */
    + /* abused by opennap servers to broadcast stuff */
    + buf2 = g_markup_escape_text(buf, -1);
    + serv_got_im(gc, "server", buf2, 0, time(NULL));
    + g_free(buf2);
    + break;
    +
    + case 405: /* MSG_SERVER_JOIN_ACK */
    + c = nap_find_chat(gc, buf);
    + if (!c)
    + serv_got_joined_chat(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(c)), buf);
    + break;
    +
    + case 407: /* MSG_SERVER_PART */
    + res = g_strsplit(buf, " ", 0);
    + c = nap_find_chat(gc, res[0]);
    + purple_conv_chat_remove_user(PURPLE_CONV_CHAT(c), res[1], NULL);
    + g_strfreev(res);
    + break;
    +
    + case 406: /* MSG_SERVER_JOIN */
    + case 408: /* MSG_SERVER_CHANNEL_USER_LIST */
    + res = g_strsplit(buf, " ", 4);
    + c = nap_find_chat(gc, res[0]);
    + purple_conv_chat_add_user(PURPLE_CONV_CHAT(c), res[1], NULL, PURPLE_CBFLAGS_NONE, TRUE);
    + g_strfreev(res);
    + break;
    +
    + case 409: /* MSG_SERVER_CHANNEL_USER_LIST_END */
    + break;
    +
    + case 410: /* MSG_SERVER_TOPIC */
    + /* display the topic in the channel */
    + res = g_strsplit(buf, " ", 2);
    + c = nap_find_chat(gc, res[0]);
    + purple_conv_chat_set_topic(PURPLE_CONV_CHAT(c), res[0], res[1]);
    + g_strfreev(res);
    + break;
    +
    + case 603: /* MSG_CLIENT_WHOIS */
    + buf2 = g_strdup_printf(_("%s requested your information"), buf);
    + serv_got_im(gc, "server", buf2, 0, time(NULL));
    + g_free(buf2);
    + break;
    +
    + case 604: /* MSG_SERVER_WHOIS_RESPONSE */
    + /* 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 */
    + 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;
    +
    + case 621:
    + case 622: /* MSG_CLIENT_MOTD */
    + /* also replaces MSG_SERVER_MOTD, so we should display it */
    + buf2 = g_markup_escape_text(buf, -1);
    + serv_got_im(gc, "motd", buf2, 0, time(NULL));
    + g_free(buf2);
    + break;
    +
    + case 627: /* MSG_CLIENT_WALLOP */
    + /* abused by opennap server maintainers to broadcast stuff */
    + buf2 = g_markup_escape_text(buf, -1);
    + serv_got_im(gc, "wallop", buf2, 0, time(NULL));
    + g_free(buf2);
    + break;
    +
    + case 628: /* MSG_CLIENT_ANNOUNCE */
    + buf2 = g_markup_escape_text(buf, -1);
    + serv_got_im(gc, "announce", buf2, 0, time(NULL));
    + g_free(buf);
    + break;
    +
    + case 748: /* MSG_SERVER_GHOST */
    + /* Looks like someone logged in as us! =-O */
    + purple_connection_error(gc, _("You have signed on from another location."));
    + break;
    +
    + case 751: /* MSG_CLIENT_PING */
    + buf2 = g_strdup_printf(_("%s requested a PING"), buf);
    + serv_got_im(gc, "server", buf2, 0, time(NULL));
    + g_free(buf2);
    + /* send back a pong */
    + /* MSG_CLIENT_PONG */
    + nap_write_packet(gc, 752, "%s", buf);
    + break;
    +
    + case 752: /* MSG_CLIENT_PONG */
    + buf2 = g_strdup_printf("Received pong from %s", buf);
    + purple_notify_info(gc, NULL, buf2, NULL);
    + g_free(buf2);
    + break;
    +
    + case 824: /* MSG_CLIENT_EMOTE */
    + res = g_strsplit(buf, " ", 3);
    + buf2 = g_strndup(res[2]+1, strlen(res[2]) - 2); /* chomp off the surround quotes */
    + buf3 = g_strdup_printf("/me %s", buf2);
    + g_free(buf2);
    + if ((c = nap_find_chat(gc, res[0]))) {
    + purple_conv_chat_write(PURPLE_CONV_CHAT(c), res[1], buf3, PURPLE_MESSAGE_NICK, time(NULL));
    + }
    + g_free(buf3);
    + g_strfreev(res);
    + break;
    +
    + default:
    + purple_debug(PURPLE_DEBUG_MISC, "napster", "Unknown packet %hd: %s\n", command, buf);
    + break;
    + }
    +
    + g_free(buf);
    +}
    +
    +/* 002 - MSG_CLIENT_LOGIN */
    +static void
    +nap_login_connect(gpointer data, gint source, const gchar *error_message) {
    + PurpleConnection *gc = data;
    + struct nap_data *ndata = (struct nap_data *)gc->proto_data;
    + gchar *buf;
    +
    + if (!g_list_find(purple_connections_get_all(), gc)) {
    + close(source);
    + return;
    + }
    +
    + if (source < 0) {
    + purple_connection_error(gc, _("Unable to connect."));
    + return;
    + }
    +
    + /* Clear the nonblocking flag
    + This protocol should be updated to support nonblocking I/O if
    + anyone is going to actually use it */
    + fcntl(source, F_SETFL, 0);
    +
    + ndata->fd = source;
    +
    + /* Update the login progress status display */
    + buf = g_strdup_printf("Logging in: %s", purple_account_get_username(gc->account));
    + purple_connection_update_progress(gc, buf, 1, NAPSTER_CONNECT_STEPS);
    + g_free(buf);
    +
    + /* 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), PP_VERSION);
    +
    + /* And set up the input watcher */
    + gc->inpa = purple_input_add(ndata->fd, PURPLE_INPUT_READ, nap_callback, gc);
    +}
    +
    +static void
    +nap_login(PurpleAccount *account) {
    + PurpleConnection *gc = purple_account_get_connection(account);
    +
    + purple_connection_update_progress(gc, _("Connecting"), 0, NAPSTER_CONNECT_STEPS);
    +
    + gc->proto_data = g_new0(struct nap_data, 1);
    + if (purple_proxy_connect(gc, account,
    + purple_account_get_string(account, "server", NAP_SERVER),
    + purple_account_get_int(account, "port", NAP_PORT),
    + nap_login_connect, gc) != 0) {
    + purple_connection_error(gc, _("Unable to connect."));
    + }
    +}
    +
    +static void
    +nap_close(PurpleConnection *gc) {
    + struct nap_data *ndata = (struct nap_data *)gc->proto_data;
    +
    + if (gc->inpa)
    + purple_input_remove(gc->inpa);
    +
    + if (!ndata)
    + return;
    +
    + close(ndata->fd);
    +
    + g_free(ndata->email);
    + g_free(ndata);
    +}
    +
    +static const char *
    +nap_list_icon(PurpleAccount *a, PurpleBuddy *b) {
    + return "napster";
    +}
    +
    +static GList *
    +nap_status_types(PurpleAccount *account) {
    + GList *types = NULL;
    + PurpleStatusType *type;
    +
    + g_return_val_if_fail(account != NULL, NULL);
    +
    + type = purple_status_type_new_full(PURPLE_STATUS_AVAILABLE,
    + NULL, NULL, TRUE, TRUE, FALSE);
    + types = g_list_append(types, type);
    +
    + type = purple_status_type_new_full(PURPLE_STATUS_OFFLINE,
    + NULL, NULL, TRUE, TRUE, FALSE);
    + types = g_list_append(types, type);
    +
    + return types;
    +}
    +
    +static GList *
    +nap_chat_info(PurpleConnection *gc) {
    + GList *m = NULL;
    + struct proto_chat_entry *pce;
    +
    + pce = g_new0(struct proto_chat_entry, 1);
    + pce->label = _("_Group:");
    + pce->identifier = "group";
    + m = g_list_append(m, pce);
    +
    + return m;
    +}
    +
    +static GHashTable *
    +nap_chat_info_defaults(PurpleConnection *gc, const char *chat_name) {
    + GHashTable *defaults;
    +
    + defaults = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
    +
    + if (chat_name != NULL)
    + g_hash_table_insert(defaults, "group", g_strdup(chat_name));
    +
    + return defaults;
    +}
    +
    +static PurplePlugin *my_protocol = NULL;
    +
    +static PurplePluginProtocolInfo prpl_info = {
    + OPT_PROTO_CHAT_TOPIC,
    + NULL, /* user_splits */
    + NULL, /* protocol_options */
    + NO_BUDDY_ICONS, /* icon_spec */
    + nap_list_icon, /* list_icon */
    + NULL, /* list_emblems */
    + NULL, /* status_text */
    + NULL, /* tooltip_text */
    + nap_status_types, /* status_types */
    + NULL, /* blist_node_menu */
    + nap_chat_info, /* chat_info */
    + nap_chat_info_defaults, /* chat_info_defaults */
    + nap_login, /* login */
    + nap_close, /* close */
    + nap_send_im, /* send_im */
    + NULL, /* set_info */
    + NULL, /* send_typing */
    + nap_get_info, /* get_info */
    + NULL, /* set_away */
    + NULL, /* set_idle */
    + NULL, /* change_passwd */
    + nap_add_buddy, /* add_buddy */
    + NULL, /* add_buddies */
    + nap_remove_buddy, /* remove_buddy */
    + NULL, /* remove_buddies */
    + NULL, /* add_permit */
    + NULL, /* add_deny */
    + NULL, /* rem_permit */
    + NULL, /* rem_deny */
    + NULL, /* set_permit_deny */
    + nap_join_chat, /* join_chat */
    + NULL, /* reject chat invite */
    + nap_get_chat_name, /* get_chat_name */
    + NULL, /* chat_invite */
    + nap_chat_leave, /* chat_leave */
    + NULL, /* chat_whisper */
    + nap_chat_send, /* chat_send */
    + NULL, /* keepalive */
    + NULL, /* register_user */
    + NULL, /* get_cb_info */
    + NULL, /* get_cb_away */
    + NULL, /* alias_buddy */
    + NULL, /* group_buddy */
    + NULL, /* rename_group */
    + NULL, /* buddy_free */
    + NULL, /* convo_closed */
    + NULL, /* normalize */
    + NULL, /* set_buddy_icon */
    + NULL, /* remove_group */
    + NULL, /* get_cb_real_name */
    + NULL, /* set_chat_topic */
    + NULL, /* find_blist_chat */
    + NULL, /* roomlist_get_list */
    + NULL, /* roomlist_cancel */
    + NULL, /* roomlist_expand_category */
    + NULL, /* can_receive_file */
    + NULL, /* send_file */
    + NULL, /* new_xfer */
    + NULL, /* offline_message */
    + NULL, /* whiteboard_prpl_ops */
    + NULL, /* send_raw */
    + NULL, /* roomlist_room_serialize */
    + NULL, /* unregister_user */
    + NULL, /* send_attention */
    + NULL, /* get_attention_types */
    + sizeof(PurplePluginProtocolInfo), /* struct_size */
    +};
    +
    +static PurplePluginInfo info = {
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_PROTOCOL, /**< type */
    + NULL, /**< ui_requirement */
    + 0, /**< flags */
    + NULL, /**< dependencies */
    + PURPLE_PRIORITY_DEFAULT, /**< priority */
    +
    + "prpl-napster", /**< id */
    + N_("Napster"), /**< name */
    + PP_VERSION, /**< version */
    + /** summary */
    + N_("NAPSTER Protocol Plugin"),
    + /** description */
    + N_("NAPSTER Protocol Plugin"),
    + NULL, /**< author */
    + PP_WEBSITE, /**< homepage */
    +
    + NULL, /**< load */
    + NULL, /**< unload */
    + NULL, /**< destroy */
    +
    + NULL, /**< ui_info */
    + &prpl_info, /**< extra_info */
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static void
    +init_plugin(PurplePlugin *plugin) {
    + PurpleAccountOption *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);
    +
    + my_protocol = plugin;
    +
    + info.description = _(info.description);
    + info.summary = _(info.summary);
    +}
    +
    +PURPLE_INIT_PLUGIN(napster, init_plugin, info);
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/napster/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,10 @@
    +[Napster]
    +type=default
    +depends=purple
    +provides=napster
    +summary=NAPSTER Protocol Plugin
    +description=%(summary)s
    +authors=Rob Flynn
    +introduced=1.0beta6
    +notes=Introduced into the Purple Plugin Pack after it was removed from libpurple.
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nicksaid/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +nicksaiddir = $(PIDGIN_LIBDIR)
    +
    +nicksaid_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +nicksaid_LTLIBRARIES = nicksaid.la
    +
    +nicksaid_la_SOURCES = \
    + nicksaid.c
    +
    +nicksaid_la_LIBADD = \
    + $(PIDGIN_LIBS) \
    + $(GTK_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(GTK_CFLAGS) \
    + $(DEBUG_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nicksaid/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for nicksaid plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = nicksaid
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nicksaid/nicksaid.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,672 @@
    +/*
    + * Nicksaid - Record when someone said your nick in a chat.
    + * 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
    + * 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"
    +
    +#define PLUGIN_ID "gtk-plugin_pack-nicksaid"
    +#define PLUGIN_STATIC_NAME "nicksaid"
    +#define PLUGIN_AUTHOR "Sadrul H Chowdhury <sadrul@users.sourceforge.net>"
    +
    +#define PREF_PREFIX "/plugins/gtk/" PLUGIN_ID
    +#define PREF_HLWORDS PREF_PREFIX "/hlwords"
    +#define PREF_CHARS PREF_PREFIX "/chars"
    +#define PREF_TIMESTAMP PREF_PREFIX "/timestamp"
    +#define PREF_DATESTAMP PREF_PREFIX "/datestamp"
    +#define PREF_SHOWWHO PREF_PREFIX "/showwho"
    +#define PREF_SHOWALL PREF_PREFIX "/showall"
    +
    +/* System headers */
    +#include <string.h>
    +#include <gdk/gdk.h>
    +#include <gtk/gtk.h>
    +
    +/* libpurple headers */
    +#include <notify.h>
    +
    +/* Pidgin headers */
    +#include <gtkconv.h>
    +#include <gtkimhtml.h>
    +#include <gtkmenutray.h>
    +#include <gtkplugin.h>
    +#include <gtkutils.h>
    +#include <pidginstock.h>
    +
    +#define DELIMS " .,;|<>?/\\`~!@#$%^&*()_-+={}[]:'\""
    +
    +static GList *hlwords = NULL; /* Words to highlight on */
    +
    +#if 0
    +static void
    +go_next(GtkWidget *w, PidginConversation *gtkconv)
    +{
    +}
    +
    +static void
    +go_previous(GtkWidget *w, PidginConversation *gtkconv)
    +{
    +}
    +#endif
    +
    +typedef struct _NickSaid NickSaid;
    +
    +struct _NickSaid
    +{
    + int offset;
    + char *who;
    + char *what;
    +};
    +
    +/* <lift src="pidgin/src/util.c"> ??? */
    +static const gchar *
    +ns_time(void)
    +{
    + static gchar buf[80];
    + time_t tme;
    +
    + time(&tme);
    + strftime(buf, sizeof(buf), "%H:%M:%S", localtime(&tme));
    +
    + return buf;
    +}
    +
    +static const gchar *
    +ns_date(void)
    +{
    + static gchar buf[80];
    + time_t tme;
    +
    + time(&tme);
    + strftime(buf, sizeof(buf), "%Y-%m-%d", localtime(&tme));
    +
    + return buf;
    +}
    +
    +static const gchar *
    +ns_date_full(void)
    +{
    + gchar *buf;
    + time_t tme;
    +
    + time(&tme);
    + buf = ctime(&tme);
    + buf[strlen(buf) - 1] = '\0';
    +
    + return buf;
    +}
    +/* <lift/> */
    +
    +struct _callbackdata
    +{
    + GtkTextView *view;
    + GtkTextIter iter;
    +};
    +
    +static gboolean draw_line(GtkWidget *widget, GdkEventExpose *event, struct _callbackdata *data);
    +
    +static gboolean
    +remove_line(struct _callbackdata *data)
    +{
    + if (g_signal_handlers_disconnect_matched(G_OBJECT(data->view), G_SIGNAL_MATCH_DATA,
    + 0, 0, NULL, NULL, data))
    + {
    + g_object_set_data(G_OBJECT(data->view), "nicksaid:mark", GINT_TO_POINTER(0));
    + gtk_widget_queue_draw(GTK_WIDGET(data->view));
    + g_free(data);
    + }
    +
    + return FALSE;
    +}
    +
    +static gboolean
    +draw_line(GtkWidget *widget, GdkEventExpose *event, struct _callbackdata *data)
    +{
    + GtkTextIter iter;
    + GdkRectangle rect, visible_rect;
    + int top, bottom, pad;
    + GdkGC *gc;
    + GtkTextView *view;
    + GdkColor green = {0, 0, 0xffff, 0};
    +
    + view = data->view;
    + iter = data->iter;
    +
    + gtk_text_view_get_iter_location(view, &iter, &rect);
    + pad = (gtk_text_view_get_pixels_below_lines(view) +
    + gtk_text_view_get_pixels_above_lines(view)) / 2;
    + top = rect.y - pad;
    + bottom = rect.y + rect.height + pad;
    +
    + gtk_text_view_buffer_to_window_coords(view, GTK_TEXT_WINDOW_TEXT,
    + 0, top, 0, &top);
    + gtk_text_view_buffer_to_window_coords(view, GTK_TEXT_WINDOW_TEXT,
    + 0, bottom, 0, &bottom);
    +
    + gtk_text_view_get_visible_rect(view, &visible_rect);
    +
    + gc = gdk_gc_new(GDK_DRAWABLE(event->window));
    + gdk_gc_set_rgb_fg_color(gc, &green);
    + gdk_draw_line(event->window, gc, 0, top, visible_rect.width, top);
    + gdk_draw_line(event->window, gc, 0, bottom, visible_rect.width, bottom);
    + gdk_gc_unref(gc);
    +
    + if (!g_object_get_data(G_OBJECT(view), "nicksaid:mark"))
    + {
    + g_timeout_add(2500, (GSourceFunc)remove_line, data);
    + g_object_set_data(G_OBJECT(view), "nicksaid:mark", GINT_TO_POINTER(1));
    + }
    +
    + return FALSE;
    +}
    +
    +static void
    +go_selected(GtkWidget *w, PidginConversation *gtkconv)
    +{
    + GtkTextIter iter;
    + int offset;
    + struct _callbackdata *data;
    +
    + offset = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w), "nicksaid:offset")) + 1;
    +
    + gtk_text_buffer_get_iter_at_offset(GTK_IMHTML(gtkconv->imhtml)->text_buffer, &iter, offset);
    + gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(gtkconv->imhtml), &iter, 0, TRUE, 0, 0);
    +
    + data = g_new0(struct _callbackdata, 1);
    + data->view = GTK_TEXT_VIEW(gtkconv->imhtml);
    + data->iter = iter;
    +
    + g_signal_connect(G_OBJECT(gtkconv->imhtml), "expose_event", G_CALLBACK(draw_line), data);
    + gtk_widget_queue_draw(gtkconv->imhtml);
    +}
    +
    +static void
    +clear_list(GtkWidget *w, PidginConversation *gtkconv)
    +{
    + GList *ll, *list;
    +
    + ll = list = g_object_get_data(G_OBJECT(gtkconv->imhtml), "nicksaid:list");
    +
    + while (list)
    + {
    + NickSaid *said = list->data;
    + g_free(said->who);
    + g_free(said->what);
    + g_free(said);
    + list = list->next;
    + }
    + g_list_free(ll);
    +
    + g_object_set_data(G_OBJECT(gtkconv->imhtml), "nicksaid:list", NULL);
    +}
    +
    +static void
    +show_all(GtkWidget *w, PidginConversation *gtkconv)
    +{
    + GList *list = g_object_get_data(G_OBJECT(gtkconv->imhtml), "nicksaid:list");
    + GString *str = g_string_new(NULL);
    +
    + while (list)
    + {
    + NickSaid *said = list->data;
    + g_string_append_printf(str, "%s<br/>\n", said->what);
    + list = list->next;
    + }
    +
    + purple_notify_formatted(gtkconv, _("Nicksaid"), _("List of highlighted messages:"),
    + NULL, str->str, NULL, NULL);
    + g_string_free(str, TRUE);
    +}
    +
    +static gboolean
    +generate_popup(GtkWidget *w, GdkEventButton *event, PidginWindow *win)
    +{
    + GtkWidget *menu, *item;
    + PurpleConversation *conv;
    + PidginConversation *gtkconv;
    + GList *list;
    +
    + conv = pidgin_conv_window_get_active_conversation(win);
    + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT)
    + return FALSE;
    +
    + menu = gtk_menu_new();
    +
    + gtkconv = PIDGIN_CONVERSATION(conv);
    +
    + list = g_object_get_data(G_OBJECT(gtkconv->imhtml), "nicksaid:list");
    + if (!list)
    + {
    + item = gtk_menu_item_new_with_label(_("None"));
    + gtk_widget_set_sensitive(item, FALSE);
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + gtk_widget_show(item);
    + }
    + else
    + {
    +#if 0
    + item = gtk_menu_item_new_with_label(_("Next"));
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(go_next), gtkconv);
    +
    + item = gtk_menu_item_new_with_label(_("Previous"));
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(go_previous), gtkconv);
    +
    + pidgin_separator(menu);
    +#endif
    +
    + while (list)
    + {
    + NickSaid *said = list->data;
    + item = gtk_menu_item_new_with_label(said->who);
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + gtk_widget_show(item);
    + g_object_set_data(G_OBJECT(item), "nicksaid:offset",
    + GINT_TO_POINTER(said->offset));
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(go_selected), gtkconv);
    + /* TODO:
    + * If the line to scrollback to is greater /pidgin/gtk/conversations/scrollback_lines,
    + * desensitise the widget so it at least displays.. or something like that. you get the drift. */
    + list = list->next;
    + }
    +
    + pidgin_separator(menu);
    +
    + item = gtk_menu_item_new_with_label(_("Clear History"));
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(clear_list), gtkconv);
    +
    + item = gtk_menu_item_new_with_label(_("Show All"));
    + gtk_widget_show(item);
    + gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
    + if (purple_prefs_get_bool(PREF_SHOWALL))
    + g_signal_connect(G_OBJECT(item), "activate", G_CALLBACK(show_all), gtkconv);
    + else
    + gtk_widget_set_sensitive(item, FALSE);
    + }
    +
    + gtk_widget_show_all(menu);
    + gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button,
    + event->time);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +displaying_msg_cb(PurpleAccount *account, const char *name, char **buffer,
    + PurpleConversation *conv, PurpleMessageFlags flags, gpointer null)
    +{
    + PidginConversation *gtkconv;
    + GtkWidget *imhtml;
    + GtkTextIter iter;
    + GList *list;
    + int pos;
    + char *tmp, *who, *prefix = NULL;
    + NickSaid *said;
    + gboolean timestamp = purple_prefs_get_bool(PREF_TIMESTAMP);
    + gboolean datestamp = purple_prefs_get_bool(PREF_DATESTAMP);
    + gboolean showwho = purple_prefs_get_bool(PREF_SHOWWHO);
    +
    + if (!(flags & PURPLE_MESSAGE_NICK) || !PIDGIN_IS_PIDGIN_CONVERSATION(conv) ||
    + purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT)
    + return FALSE;
    +
    + gtkconv = PIDGIN_CONVERSATION(conv);
    + imhtml = gtkconv->imhtml;
    +
    + gtk_text_buffer_get_end_iter(GTK_IMHTML(imhtml)->text_buffer, &iter);
    + pos = gtk_text_iter_get_offset(&iter);
    + list = g_object_get_data(G_OBJECT(imhtml), "nicksaid:list");
    +
    + tmp = purple_markup_strip_html(*buffer);
    + who = g_strndup(tmp, purple_prefs_get_int(PREF_CHARS));
    + g_free(tmp);
    +
    + if (!g_utf8_validate(who, -1, (const char **)&tmp))
    + *tmp = '\0';
    +
    + if (showwho) {
    + tmp = who;
    + who = g_strdup_printf("%s: %s", name, tmp);
    + g_free(tmp);
    + }
    +
    + if (datestamp && timestamp) {
    + prefix = g_strdup_printf("(%s) ", ns_date_full());
    + } else if (datestamp && !timestamp) {
    + prefix = g_strdup_printf("(%s) ", ns_date());
    + } else if (!datestamp && timestamp) {
    + prefix = g_strdup_printf("(%s) ", ns_time());
    + }
    +
    + said = g_new0(NickSaid, 1);
    + said->offset = pos;
    +
    + if (prefix != NULL) {
    + said->who = g_strdup_printf("%s%s", prefix, who);
    + g_free(who);
    + } else {
    + said->who = who;
    + }
    +
    + if (purple_prefs_get_bool(PREF_SHOWALL))
    + {
    + said->what = g_strdup_printf("%s<b>%s: </b>%s",
    + prefix ? prefix : "", name, *buffer);
    + }
    +
    + g_free(prefix);
    +
    + list = g_list_prepend(list, said);
    + g_object_set_data(G_OBJECT(imhtml), "nicksaid:list", list);
    +
    + return FALSE;
    +}
    +
    +static GtkWidget *
    +get_tray_icon_for_window(PidginWindow *win)
    +{
    + GtkWidget *w = g_object_get_data(G_OBJECT(win->window), "nicksaid:trayicon");
    + if (w == NULL)
    + {
    + w = gtk_event_box_new();
    +#if GTK_CHECK_VERSION(2,4,0)
    + gtk_event_box_set_visible_window(GTK_EVENT_BOX(w), TRUE);
    +#endif
    + gtk_container_add(GTK_CONTAINER(w),
    + gtk_image_new_from_stock(PIDGIN_STOCK_TOOLBAR_MESSAGE_NEW, GTK_ICON_SIZE_MENU));
    + pidgin_menu_tray_append(PIDGIN_MENU_TRAY(win->menu.tray), w, "Nicksaid");
    + gtk_widget_show_all(w);
    + g_object_set_data(G_OBJECT(win->window), "nicksaid:trayicon", w);
    +
    + g_signal_connect(G_OBJECT(w), "button_press_event", G_CALLBACK(generate_popup), win);
    + }
    + return w;
    +}
    +
    +static void
    +update_menu_tray(PurpleConversation *conv)
    +{
    + PidginConversation *gtkconv;
    + PidginWindow *win;
    + GtkWidget *icon;
    +
    + if (!PIDGIN_IS_PIDGIN_CONVERSATION(conv))
    + return;
    +
    + gtkconv = PIDGIN_CONVERSATION(conv);
    + win = gtkconv->win;
    +
    + if (!win)
    + return;
    +
    + icon = get_tray_icon_for_window(win);
    +
    + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT)
    + gtk_widget_hide_all(icon);
    + else
    + gtk_widget_show_all(icon);
    +}
    +
    +static void
    +deleting_conversation_cb(PurpleConversation *conv, gpointer null)
    +{
    + if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_CHAT ||
    + !PIDGIN_IS_PIDGIN_CONVERSATION(conv))
    + return;
    +
    + clear_list(NULL, PIDGIN_CONVERSATION(conv));
    +}
    +
    +static gboolean
    +rcvd_msg_cb(PurpleAccount *account, const char **who, const char **message,
    + PurpleConversation *conv, PurpleMessageFlags *flags)
    +{
    + char *msg, *delims;
    + GList *iter;
    +
    + if (*flags & PURPLE_MESSAGE_NICK)
    + return FALSE;
    +
    + delims = g_strdup(DELIMS);
    + g_strdelimit(delims, purple_prefs_get_string(PREF_HLWORDS), ' ');
    +
    + msg = g_strdup(*message);
    + g_strdelimit(msg, delims, ' ');
    + g_free(delims);
    +
    + for (iter = hlwords; iter; iter = iter->next)
    + {
    + char *s = g_strstr_len(msg, -1, iter->data);
    + if (s && (s == msg || *(s - 1) == ' '))
    + {
    + char *e = s + strlen(iter->data);
    + if (*e == ' ' || *e == '\0')
    + {
    + *flags |= PURPLE_MESSAGE_NICK;
    + break;
    + }
    + }
    + }
    +
    + g_free(msg);
    + return FALSE;
    +}
    +
    +static void
    +destroy_list()
    +{
    + while (hlwords)
    + {
    + g_free(hlwords->data);
    + hlwords = g_list_delete_link(hlwords, hlwords);
    + }
    +}
    +
    +static void
    +construct_list()
    +{
    + char *string;
    + const char *s, *e;
    +
    + destroy_list();
    +
    + string = g_strdup(purple_prefs_get_string(PREF_HLWORDS));
    + s = string;
    + e = NULL;
    +
    + if (s == NULL)
    + return;
    +
    + while (*s)
    + {
    + while (*s == ' ' || *s == '\t')
    + s++;
    + e = s + 1;
    + while (*e != ' ' && *e != '\t' && *e != '\0')
    + e++;
    +
    + hlwords = g_list_prepend(hlwords, g_strndup(s, e-s));
    + s = e;
    + }
    +
    + g_free(string);
    +}
    +
    +static void
    +unload_cleanup_gtkconv(PidginConversation *gtkconv, gpointer null)
    +{
    + clear_list(NULL, gtkconv);
    +}
    +
    +static void
    +unload_cleanup_win(PidginWindow *win, gpointer null)
    +{
    + GtkWidget *w = get_tray_icon_for_window(win);
    + g_object_set_data(G_OBJECT(win->window), "nicksaid:trayicon", NULL);
    + gtk_widget_destroy(w);
    +
    + g_list_foreach(pidgin_conv_window_get_gtkconvs(win), (GFunc)unload_cleanup_gtkconv, NULL);
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + construct_list();
    +
    + purple_signal_connect(purple_conversations_get_handle(), "receiving-chat-msg",
    + plugin, PURPLE_CALLBACK(rcvd_msg_cb), NULL);
    + purple_signal_connect(pidgin_conversations_get_handle(), "displaying-chat-msg",
    + plugin, PURPLE_CALLBACK(displaying_msg_cb), NULL);
    + purple_signal_connect(pidgin_conversations_get_handle(), "conversation-switched",
    + plugin, PURPLE_CALLBACK(update_menu_tray), NULL);
    + purple_signal_connect(purple_conversations_get_handle(), "deleting-conversation",
    + plugin, PURPLE_CALLBACK(deleting_conversation_cb), NULL);
    +
    + purple_prefs_connect_callback(plugin, PREF_HLWORDS,
    + (PurplePrefCallback)construct_list, NULL);
    +
    + purple_conversation_foreach(update_menu_tray);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + destroy_list();
    +
    + g_list_foreach(pidgin_conv_windows_get_list(), (GFunc)unload_cleanup_win, NULL);
    +
    + purple_prefs_disconnect_by_handle(plugin);
    +
    + return TRUE;
    +}
    +
    +static PurplePluginPrefFrame *
    +get_plugin_pref_frame(PurplePlugin *plugin)
    +{
    + PurplePluginPrefFrame *frame;
    + PurplePluginPref *pref;
    +
    + frame = purple_plugin_pref_frame_new();
    +
    + pref = purple_plugin_pref_new_with_label(_("Highlight"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_HLWORDS,
    + _("_Words to highlight on\n(separate the words with a blank space)"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_label(_("Number of displayed characters"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_CHARS,
    + _("_Set the number of characters displayed\nin the nicksaid menu"));
    + purple_plugin_pref_set_bounds(pref, 10, 40);
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_SHOWWHO,
    + _("Display who said your name in the nicksaid menu"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_TIMESTAMP,
    + _("Display _timestamps in the nicksaid menu"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_DATESTAMP,
    + _("_Display _datestamps in the nicksaid menu"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + pref = purple_plugin_pref_new_with_name_and_label(PREF_SHOWALL,
    + _("Allow displaying in a separate dialog"));
    + purple_plugin_pref_frame_add(frame, pref);
    +
    + return frame;
    +}
    +
    +static PurplePluginUiInfo prefs_info = {
    + get_plugin_pref_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +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 */
    + &prefs_info, /* 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 = _("Nicksaid");
    + info.summary = _("Record when someone said your nick in a chat.");
    + info.description = _("Record when someone said your nick in a chat.");
    +
    + purple_prefs_add_none(PREF_PREFIX);
    + purple_prefs_add_string(PREF_HLWORDS, "");
    + purple_prefs_add_int(PREF_CHARS, 15);
    + purple_prefs_add_bool(PREF_TIMESTAMP, TRUE);
    + purple_prefs_add_bool(PREF_DATESTAMP, FALSE);
    + purple_prefs_add_bool(PREF_SHOWWHO, TRUE);
    + purple_prefs_add_bool(PREF_SHOWALL, FALSE);
    +}
    +
    +PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nicksaid/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Nicksaid]
    +type=default
    +depends=pidgin
    +provides=nicksaid
    +summary=Record when someone said your nick in a chat
    +description=%(summary)s
    +authors=Sadrul Habib Chowdhury
    +introduced=1.0beta1
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nomobility/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +nomobilitydir = $(PURPLE_LIBDIR)
    +
    +nomobility_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +nomobility_LTLIBRARIES = nomobility.la
    +
    +nomobility_la_SOURCES = \
    + nomobility.c
    +
    +nomobility_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/nomobility/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for dice plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = nomobility
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nomobility/nomobility.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,314 @@
    +/*
    + * nomobility - stops you from sending messages to mobile users
    + * Copyright (C) 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 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>
    +
    +#include <plugin.h>
    +
    +#include <blist.h>
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <status.h>
    +
    +/******************************************************************************
    + * Globals
    + *****************************************************************************/
    +#define NO_MOBILITY_QUEUE_KEY "no-mobility"
    +#define NO_MOBILITY_COMMAND "mobile"
    +
    +static PurpleCmdId nomobility_cmd_id = 0;
    +
    +/******************************************************************************
    + * Helpers
    + *****************************************************************************/
    +static void
    +nomobility_list_messages(PurpleConversation *conv, GList *queue) {
    + GList *l = NULL;
    + const gchar *name = purple_conversation_get_name(conv);
    + gint i = 0;
    +
    + if(!queue) {
    + purple_conv_im_write(PURPLE_CONV_IM(conv), name,
    + _("There are no messages in the queue."),
    + PURPLE_MESSAGE_NO_LOG, time(NULL));
    +
    + return;
    + }
    +
    + for(l = queue; l; l = l->next, i++) {
    + gchar *msg = g_strdup_printf(_("%d. %s"), i + 1, (gchar *)l->data);
    +
    + purple_conv_im_write(PURPLE_CONV_IM(conv), name, msg,
    + PURPLE_MESSAGE_NO_LOG, time(NULL));
    +
    + g_free(msg);
    + }
    +}
    +
    +static void
    +nomobility_clear(PurpleConversation *conv, GList *queue) {
    + GList *l = NULL;
    +
    + for(l = queue; l; l = l->next)
    + g_free(l->data);
    +
    + g_list_free(queue);
    +
    + purple_conversation_set_data(conv, NO_MOBILITY_QUEUE_KEY, NULL);
    +}
    +
    +static void
    +nomobility_send(PurpleConversation *conv, GList *queue) {
    + GList *l = NULL;
    + GString *str = g_string_new("");
    +
    + for(l = queue; l; l = l->next) {
    + gchar *msg = (gchar *)l->data;
    + const gchar *suffix = (l->next) ? "\n" : "";
    +
    + g_string_append_printf(str, "%s%s", msg, suffix);
    + }
    +
    + purple_conv_im_send(PURPLE_CONV_IM(conv), str->str);
    +
    + g_string_free(str, TRUE);
    +
    + nomobility_clear(conv, queue);
    +}
    +
    +static void
    +nomobility_delete(PurpleConversation *conv, GList *queue, gint n_msg) {
    + GList *l = NULL;
    + gint i = 0;
    +
    + for(l = queue; i < n_msg - 1; i++, l = l->next);
    +
    + if(l)
    + g_free(l->data);
    +
    + queue = g_list_remove(queue, l);
    +
    + purple_conversation_set_data(conv, NO_MOBILITY_QUEUE_KEY, queue);
    +}
    +
    +/******************************************************************************
    + * Callbacks
    + *****************************************************************************/
    +static void
    +sending_im_msg(PurpleAccount *account, gchar *receiver, gchar **message,
    + gpointer data)
    +{
    + PurpleBuddy *buddy = NULL;
    + PurplePresence *presence = NULL;
    +
    + if(!message || !*message)
    + return;
    +
    + buddy = purple_find_buddy(account, receiver);
    +
    + if(!buddy)
    + return;
    +
    + presence = purple_buddy_get_presence(buddy);
    +
    + if(!presence)
    + return;
    +
    +#if 0
    + if(purple_presence_is_status_primitive_active(presence,
    + PURPLE_STATUS_MOBILE))
    +#endif
    + {
    + PurpleConversation *conv = NULL;
    + gchar *msg = NULL;
    +
    + msg = g_strdup_printf(_("Cancelled message to %s, they are currently "
    + "mobile."),
    + receiver);
    +
    + conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM,
    + receiver, account);
    +
    + /* if we have the account, add the message to our queue */
    + if(conv) {
    + GList *queue = NULL;
    +
    + queue = purple_conversation_get_data(conv, NO_MOBILITY_QUEUE_KEY);
    +
    + queue = g_list_append(queue, g_strdup(*message));
    + purple_conversation_set_data(conv, NO_MOBILITY_QUEUE_KEY, queue);
    + }
    +
    + /* now actually kill the message */
    + g_free(*message);
    + *message = NULL;
    +
    + /* if we failed to find the conv, write a debug message and bail */
    + if(!conv) {
    + purple_debug_info("nomobility", "%s\n", msg);
    + g_free(msg);
    +
    + return;
    + }
    +
    + /* we have a conv, so note that we queue the message in conv */
    + purple_conv_im_write(PURPLE_CONV_IM(conv), receiver, msg,
    + PURPLE_MESSAGE_NO_LOG | PURPLE_MESSAGE_DELAYED,
    + time(NULL));
    + g_free(msg);
    + }
    +}
    +
    +static PurpleCmdRet
    +nomobility_cmd(PurpleConversation *conv, const gchar *cmd, gchar **args,
    + gchar **error, gpointer data)
    +{
    + GList *queue = NULL;
    + gchar *lower = NULL;
    +
    + if(!*args && !args[0]) {
    + *error = g_strdup("eek!");
    +
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + queue = purple_conversation_get_data(conv, NO_MOBILITY_QUEUE_KEY);
    +
    + lower = g_ascii_strdown(args[0], strlen(args[0]));
    +
    + if(strcmp(lower, "clear") == 0) {
    + nomobility_clear(conv, queue);
    + } else if(strcmp(lower, "delete") == 0) {
    + gint n_msg = 0;
    +
    + if(!args[1]) {
    + *error = g_strdup(_("Delete failed: no message number given!"));
    +
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + n_msg = atoi(args[1]);
    + if(n_msg < 0 || n_msg >= g_list_length(queue)) {
    + *error =
    + g_strdup_printf(_("Delete failed: no messaged numbered %d!"),
    + n_msg);
    +
    + return PURPLE_CMD_RET_FAILED;
    + }
    +
    + nomobility_delete(conv, queue, n_msg);
    + } else if(strcmp(lower, "list") == 0) {
    + nomobility_list_messages(conv, queue);
    + } else if(strcmp(lower, "sendall") == 0) {
    + nomobility_send(conv, queue);
    + }
    +
    + g_free(lower);
    +
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +/******************************************************************************
    + * Plugin Stuff
    + *****************************************************************************/
    +static gboolean
    +plugin_load(PurplePlugin *plugin) {
    + PurpleCmdFlag flags = PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_ALLOW_WRONG_ARGS;
    + void *conv_handle = purple_conversations_get_handle();
    + gchar *help = NULL;
    +
    + /* signals */
    + purple_signal_connect(conv_handle, "sending-im-msg", plugin,
    + PURPLE_CALLBACK(sending_im_msg), NULL);
    +
    + /* commands */
    + help = g_strdup_printf(_("%s &lt;[clear][clear][delete][send]&gt;\n"
    + "clear Clears all queued messages\n"
    + "delete # Deletes the message numbered #\n"
    + "list Lists all queued messages\n"
    + "sendall Sends all queued messages\n"),
    + NO_MOBILITY_COMMAND);
    +
    + nomobility_cmd_id =
    + purple_cmd_register(NO_MOBILITY_COMMAND, "ws", PURPLE_CMD_P_PLUGIN,
    + flags, NULL,
    + PURPLE_CMD_FUNC(nomobility_cmd), help, NULL);
    + g_free(help);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + return TRUE;
    +}
    +
    +static PurplePluginInfo info =
    +{
    + PURPLE_PLUGIN_MAGIC,
    + PURPLE_MAJOR_VERSION,
    + PURPLE_MINOR_VERSION,
    + PURPLE_PLUGIN_STANDARD,
    + NULL,
    + 0,
    + NULL,
    + PURPLE_PRIORITY_DEFAULT,
    +
    + "core-plugin_pack-nomobility",
    + NULL,
    + PP_VERSION,
    + NULL,
    + NULL,
    + "Gary Kramlich <grim@reaperworld.com>",
    + PP_WEBSITE,
    +
    + plugin_load,
    + plugin_unload,
    + 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 = _("No Mobility");
    + info.summary = _("Stops you from messaging mobile users");
    + info.description = info.summary;
    +
    +}
    +
    +PURPLE_INIT_PLUGIN(nomobility, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nomobility/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[No Mobility]
    +type=incomplete
    +depends=purple
    +provides=nomobility
    +summary=Stops you from messaging mobile users
    +description=%(summary)s
    +authors=Gary Kramlich
    +introduced=2.5.0
    +
    Binary file nsis/header.bmp has changed
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/nsis/translations/english.nsh Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,17 @@
    +;;
    +;; english.nsh
    +;;
    +;; Default language strings for the Windows Purple Plugin Pack NSIS installer.
    +;; Windows Code Page: 1252
    +;; Language Code: 1033
    +;;
    +
    +;; Startup Checks
    +LangString PIDGIN_NEEDED ${LANG_ENGLISH} "The Purple Plugin Pack requires that Pidgin be installed. You must install Pidgin before install the Purple Plugin Pack."
    +
    +; Overrides for default text in windows:
    +LangString WELCOME_TITLE ${LANG_ENGLISH} "Purple Plugin Pack v${PP_VERSION} Installer"
    +
    +LangString WELCOME_TEXT ${LANG_ENGLISH} "Note: This version of the plugin is designed for Pidgin ${PIDGIN_VERSION}, and will not install or function with versions of Pidgin having a different major version number.\r\n\r\nWhen you upgrade your version of Pidgin, you must uninstall or upgrade the Purple Plugin Pack as well.\r\n\r\n"
    +
    +;; vi: syntax=nsis
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/oldlogger/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,28 @@
    +EXTRA_DIST = \
    + Makefile.mingw \
    + plugins.cfg
    +
    +oldloggerdir = $(PURPLE_LIBDIR)
    +
    +oldlogger_la_LDFLAGS = -module -avoid-version
    +
    +if HAVE_PURPLE
    +
    +oldlogger_LTLIBRARIES = oldlogger.la
    +
    +oldlogger_la_SOURCES = \
    + oldlogger.c
    +
    +oldlogger_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/oldlogger/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,12 @@
    +#
    +# Makefile.mingw
    +#
    +# Description: Makefile for oldlogger plugin.
    +#
    +
    +PP_TOP := ..
    +
    +PP = oldlogger
    +
    +include $(PP_TOP)/win_pp.mak
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/oldlogger/oldlogger.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,520 @@
    +/*
    + * Old Logger - Re-implements the legacy, deficient, logging
    + * 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
    + * 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"
    +
    +#define OLDLOGGER_PLUGIN_ID "core-plugin_pack-oldlogger"
    +
    +#include <errno.h>
    +#include <stdio.h>
    +#include <string.h>
    +#include <sys/stat.h>
    +#include <unistd.h>
    +
    +#include <account.h>
    +#include <debug.h>
    +#include <log.h>
    +#include <prefs.h>
    +#include <prpl.h>
    +#include <stringref.h>
    +#include <util.h>
    +
    +/* We want to use the gstdio functions when possible so that non-ASCII
    + * filenames are handled properly on Windows. */
    +#if GLIB_CHECK_VERSION(2,6,0)
    +#include <glib/gstdio.h>
    +#else
    +#include <sys/stat.h>
    +#define g_fopen fopen
    +#define g_rename rename
    +#define g_stat stat
    +#define g_unlink unlink
    +#endif
    +
    +static PurpleLogLogger *oldtxt_logger;
    +static PurpleLogLogger *oldhtml_logger;
    +#define return_written return written
    +
    +struct basic_logger_data {
    + FILE *file;
    + char *filename;
    + gboolean new;
    + long offset;
    + time_t old_last_modified;
    +};
    +
    +static const gchar *
    +oldlogger_date_full(void)
    +{
    + gchar *buf;
    + time_t tme;
    +
    + time(&tme);
    + buf = ctime(&tme);
    + buf[strlen(buf) - 1] = '\0';
    +
    + return buf;
    +}
    +
    +static void old_logger_create(PurpleLog *log)
    +{
    + if(log->type == PURPLE_LOG_SYSTEM){
    + const char *ud = purple_user_dir();
    + char *dir;
    + char *filename;
    + struct basic_logger_data *data;
    + struct stat st;
    +
    + dir = g_build_filename(ud, "logs", NULL);
    + purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    + filename = g_build_filename(dir, "system", NULL);
    + g_free(dir);
    +
    + log->logger_data = data = g_new0(struct basic_logger_data, 1);
    +
    + if (g_stat(filename, &st) < 0)
    + data->new = TRUE;
    + else
    + data->old_last_modified = st.st_mtime;
    +
    + data->file = g_fopen(filename, "a");
    + if (!data->file) {
    + 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;
    + }
    + data->filename = filename;
    + data->offset = ftell(data->file);
    + }
    +}
    +
    +static void old_logger_update_index(PurpleLog *log)
    +{
    + struct basic_logger_data *data = log->logger_data;
    + struct stat st;
    + char *index_path;
    + char *index_data;
    + GError *error = NULL;
    + int index_fd;
    + char *index_tmp;
    + FILE *index;
    +
    + g_return_if_fail(data->offset > 0);
    +
    + index_path = g_strdup(data->filename);
    + /* Change the .log extension to .idx */
    + strcpy(index_path + strlen(index_path) - 3, "idx");
    + if (!data->new && (g_stat(index_path, &st) || st.st_mtime < data->old_last_modified))
    + {
    + g_free(index_path);
    + return;
    + }
    +
    + /* The index file exists and is at least as new as the log, so open it. */
    + if (!data->new && !g_file_get_contents(index_path, &index_data, NULL, &error))
    + {
    + purple_debug_error("log", "Failed to read contents of index \"%s\": %s\n",
    + index_path, error->message);
    + g_error_free(error);
    + g_free(index_path);
    + return;
    + }
    + if (data->new)
    + index_data = g_strdup("");
    +
    + index_tmp = g_strdup_printf("%s.XXXXXX", index_path);
    + if ((index_fd = g_mkstemp(index_tmp)) == -1) {
    + purple_debug_error("log", "Failed to open index temp file: %s\n",
    + strerror(errno));
    + g_free(index_path);
    + g_free(index_data);
    + g_free(index_tmp);
    + return;
    + } else {
    + if ((index = fdopen(index_fd, "wb")) == NULL)
    + {
    + purple_debug_error("log", "Failed to fdopen() index temp file: %s\n",
    + strerror(errno));
    + close(index_fd);
    + if (index_tmp != NULL)
    + {
    + g_unlink(index_tmp);
    + g_free(index_tmp);
    + }
    + g_free(index_path);
    + g_free(index_data);
    + return;
    + }
    + }
    +
    + fprintf(index, "%s", index_data);
    + fprintf(index, "%ld\t%ld\t%lu\n", data->offset, ftell(data->file) - data->offset, (unsigned long)log->time);
    + fclose(index);
    +
    + if (g_rename(index_tmp, index_path))
    + {
    + purple_debug_warning("log", "Failed to rename index temp file \"%s\" to \"%s\": %s\n",
    + index_tmp, index_path, strerror(errno));
    + g_unlink(index_tmp);
    + }
    +
    + g_free(index_tmp);
    + g_free(index_path);
    + g_free(index_data);
    +}
    +
    +static void old_logger_finalize(PurpleLog *log)
    +{
    + struct basic_logger_data *data = log->logger_data;
    + if (data) {
    + if(data->file)
    + fflush(data->file);
    +
    + old_logger_update_index(log);
    +
    + if(data->file)
    + fclose(data->file);
    +
    + g_free(data->filename);
    + g_free(data);
    + }
    +}
    +
    +/*******************************
    + ** Ye Olde PLAIN TEXT LOGGER **
    + *******************************/
    +
    +static gsize
    +oldtxt_logger_write(PurpleLog *log, PurpleMessageFlags type,
    + const char *from, time_t time, const char *message)
    +{
    + char date[64];
    + char *stripped = NULL;
    + struct basic_logger_data *data = log->logger_data;
    + const char *prpl = (purple_find_prpl(purple_account_get_protocol_id(log->account)))->info->name;
    + gsize written = 0;
    + if (!data) {
    + /* This log is new. We could use the logger's 'new' function, but
    + * creating a new file there would result in empty files in the case
    + * that you open a convo with someone, but don't say anything.
    + *
    + * The log is also not system log. Because if it is, data would be
    + * created in old_logger_create
    + * Stu: well, this isn't really necessary with crappy old logging, but I'm
    + * too lazy to do it any other way.
    + */
    + const char *ud = purple_user_dir();
    + char *filename;
    + char *guy = g_strdup(purple_normalize(log->account, log->name));
    + char *chat;
    + char *dir;
    + 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);
    + guy = chat;
    + }
    + logfile = g_strdup_printf("%s.log", guy);
    + g_free(guy);
    +
    + dir = g_build_filename(ud, "logs", NULL);
    + purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    +
    + filename = g_build_filename(dir, logfile, NULL);
    + g_free(dir);
    + g_free(logfile);
    +
    + log->logger_data = data = g_new0(struct basic_logger_data, 1);
    +
    + if (g_stat(filename, &st) < 0)
    + data->new = TRUE;
    + else
    + data->old_last_modified = st.st_mtime;
    +
    + data->file = g_fopen(filename, "a");
    + if (!data->file) {
    + 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;
    +
    + if (data->new)
    + written += fprintf(data->file, _("IM Sessions with %s\n"), purple_normalize(log->account, log->name));
    +
    + written += fprintf(data->file, "---- New Conversation @ %s ----\n",
    + oldlogger_date_full());
    + data->offset = ftell(data->file);
    + }
    +
    + /* if we can't write to the file, give up before we hurt ourselves */
    + if(!data->file)
    + return_written;
    +
    + purple_markup_html_to_xhtml(message, NULL, &stripped);
    +
    + if(log->type == PURPLE_LOG_SYSTEM){
    + if (!strncmp(stripped, "+++ ", 4)) {
    + written += fprintf(data->file, "---- %s @ %s ----\n", stripped, oldlogger_date_full());
    + } else {
    + written += fprintf(data->file, "---- %s (%s) reported that %s @ %s ----\n", purple_account_get_username(log->account), prpl, stripped, oldlogger_date_full());
    + }
    + } else {
    + strftime(date, sizeof(date), "%H:%M:%S", localtime(&time));
    + if (type & PURPLE_MESSAGE_SEND ||
    + type & PURPLE_MESSAGE_RECV) {
    + if (type & PURPLE_MESSAGE_AUTO_RESP) {
    + written += fprintf(data->file, _("(%s) %s <AUTO-REPLY>: %s\n"), date,
    + from, stripped);
    + } else {
    + if(purple_message_meify(stripped, -1))
    + written += fprintf(data->file, "(%s) ***%s %s\n", date, from,
    + stripped);
    + else
    + written += fprintf(data->file, "(%s) %s: %s\n", date, from,
    + stripped);
    + }
    + } else if ((type & PURPLE_MESSAGE_SYSTEM) || (type & PURPLE_MESSAGE_ERROR))
    + written += fprintf(data->file, "(%s) %s\n", date, stripped);
    + else if (type & PURPLE_MESSAGE_RAW)
    + written += fprintf(data->file, "%s\n", stripped);
    + else if (type & PURPLE_MESSAGE_NO_LOG) {
    + /* This shouldn't happen */
    + g_free(stripped);
    + return_written;
    + } else if (type & PURPLE_MESSAGE_WHISPER)
    + written += fprintf(data->file, "(%s) *%s* %s\n", date, from, stripped);
    + else
    + written += fprintf(data->file, "(%s) %s%s %s\n", date, from ? from : "",
    + from ? ":" : "", stripped);
    + }
    +
    + fflush(data->file);
    + g_free(stripped);
    +
    + return_written;
    +}
    +
    +/****************************
    + ** Ye Olde HTML LOGGER *****
    + ****************************/
    +static gsize
    +oldhtml_logger_write(PurpleLog *log, PurpleMessageFlags type,
    + const char *from, time_t time, const char *message)
    +{
    + char date[64];
    + char *msg_fixed = NULL;
    + struct basic_logger_data *data = log->logger_data;
    + const char *prpl = (purple_find_prpl(purple_account_get_protocol_id(log->account)))->info->name;
    + gsize written = 0;
    + if(!data) {
    + /* This log is new */
    + const char *ud = purple_user_dir();
    + char *filename;
    + char *guy = g_strdup(purple_normalize(log->account, log->name));
    + char *chat;
    + char *dir;
    + 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);
    + guy = chat;
    + }
    + logfile = g_strdup_printf("%s.log", guy);
    + g_free(guy);
    +
    + dir = g_build_filename(ud, "logs", NULL);
    + purple_build_dir (dir, S_IRUSR | S_IWUSR | S_IXUSR);
    +
    + filename = g_build_filename(dir, logfile, NULL);
    + g_free(dir);
    + g_free(logfile);
    +
    + log->logger_data = data = g_new0(struct basic_logger_data, 1);
    +
    + if (g_stat(filename, &st) < 0)
    + data->new = TRUE;
    + else
    + data->old_last_modified = st.st_mtime;
    +
    + data->file = g_fopen(filename, "a");
    + if (!data->file) {
    + 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;
    +
    + if (data->new) {
    + written += fprintf(data->file, "<HTML><HEAD><TITLE>");
    + written += fprintf(data->file, _("IM Sessions with %s"), purple_normalize(log->account, log->name));
    + written += fprintf(data->file, "</TITLE></HEAD><BODY BGCOLOR=\"#ffffff\">\n");
    + }
    + written += fprintf(data->file, "<HR><BR><H3 Align=Center> ");
    + written += fprintf(data->file, "---- New Conversation @ %s ----</H3><BR>\n",
    + oldlogger_date_full());
    + data->offset = ftell(data->file);
    + }
    +
    + /* if we can't write to the file, give up before we hurt ourselves */
    + if(!data->file)
    + return_written;
    +
    + purple_markup_html_to_xhtml(message, &msg_fixed, NULL);
    +
    + if(log->type == PURPLE_LOG_SYSTEM){
    + if (!strncmp(msg_fixed, "+++ ", 4)) {
    + written += fprintf(data->file, "---- %s @ %s ----<BR>\n", msg_fixed, oldlogger_date_full());
    + } else {
    + written += fprintf(data->file, "---- %s (%s) reported that %s @ %s ----<BR>\n", purple_account_get_username(log->account), prpl, msg_fixed, oldlogger_date_full());
    + }
    + } else {
    + strftime(date, sizeof(date), "%H:%M:%S", localtime(&time));
    + if (type & PURPLE_MESSAGE_SYSTEM)
    + written += fprintf(data->file, "<FONT COLOR=\"#000000\" sml=\"%s\">(%s) <B>%s</B></FONT><BR>\n", prpl, date, msg_fixed);
    + else if (type & PURPLE_MESSAGE_ERROR)
    + written += fprintf(data->file, "<FONT COLOR=\"#FF0000\" sml=\"%s\">(%s) <B>%s</B></FONT><BR>\n", prpl, date, msg_fixed);
    + else if (type & PURPLE_MESSAGE_RAW)
    + written += fprintf(data->file, "%s<BR>\n", msg_fixed);
    + else if (type & PURPLE_MESSAGE_WHISPER)
    + written += fprintf(data->file, "<FONT COLOR=\"#6C2585\" sml=\"%s\">(%s) <B>%s:</B></FONT> %s<BR>\n",
    + prpl, date, from, msg_fixed);
    + else if (type & PURPLE_MESSAGE_AUTO_RESP) {
    + if (type & PURPLE_MESSAGE_SEND)
    + written += fprintf(data->file, _("<FONT COLOR=\"#16569E\" sml=\"%s\">(%s) <B>%s &lt;AUTO-REPLY&gt;:</B></FONT> %s<BR>\n"), prpl, date, from, msg_fixed);
    + else if (type & PURPLE_MESSAGE_RECV)
    + written += fprintf(data->file, _("<FONT COLOR=\"#A82F2F\" sml=\"%s\">(%s) <B>%s &lt;AUTO-REPLY&gt;:</B></FONT> %s<BR>\n"), prpl, date, from, msg_fixed);
    + } else if (type & PURPLE_MESSAGE_RECV) {
    + if(purple_message_meify(msg_fixed, -1))
    + written += fprintf(data->file, "<FONT COLOR=\"#6C2585\" sml=\"%s\">(%s) <B>***%s</B></FONT> <font sml=\"%s\">%s</FONT><BR>\n",
    + prpl, date, from, prpl, msg_fixed);
    + else
    + written += fprintf(data->file, "<FONT COLOR=\"#A82F2F\" sml=\"%s\">(%s) <B>%s:</B></FONT> <font sml=\"%s\">%s</FONT><BR>\n",
    + prpl, date, from, prpl, msg_fixed);
    + } else if (type & PURPLE_MESSAGE_SEND) {
    + if(purple_message_meify(msg_fixed, -1))
    + written += fprintf(data->file, "<FONT COLOR=\"#6C2585\" sml=\"%s\">(%s) <B>***%s</B></FONT> <font sml=\"%s\">%s</FONT><BR>\n",
    + prpl, date, from, prpl, msg_fixed);
    + else
    + written += fprintf(data->file, "<FONT COLOR=\"#16569E\" sml=\"%s\">(%s) <B>%s:</B></FONT> <font sml=\"%s\">%s</FONT><BR>\n",
    + prpl, date, from, prpl, msg_fixed);
    + }
    + }
    +
    + fflush(data->file);
    + g_free(msg_fixed);
    +
    + return_written;
    +}
    +
    +static gboolean
    +plugin_load(PurplePlugin *plugin)
    +{
    + oldtxt_logger = purple_log_logger_new("oldtxt", N_("Old plain text"), 3,
    + old_logger_create,
    + oldtxt_logger_write,
    + old_logger_finalize);
    + purple_log_logger_add(oldtxt_logger);
    + oldhtml_logger = purple_log_logger_new("oldhtml", N_("Old HTML"), 3,
    + old_logger_create,
    + oldhtml_logger_write,
    + old_logger_finalize);
    + purple_log_logger_add(oldhtml_logger);
    + purple_prefs_trigger_callback("/purple/logging/format");
    + return TRUE;
    +}
    +
    +static gboolean
    +plugin_unload(PurplePlugin *plugin)
    +{
    + purple_log_logger_remove(oldtxt_logger);
    + purple_log_logger_remove(oldhtml_logger);
    + purple_prefs_trigger_callback("/purple/logging/format");
    + return TRUE;
    +}
    +
    +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 */
    +
    + OLDLOGGER_PLUGIN_ID, /**< id */
    + NULL, /**< name */
    + PP_VERSION, /**< version */
    + NULL, /**< summary */
    + NULL, /**< description */
    + "Stu Tomlinson <stu@nosnilmot.com>", /**< author */
    + PP_WEBSITE, /**< homepage */
    +
    + 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 = _("Old Logger");
    + info.summary = _("Re-implements the legacy, deficient, logging");
    + info.description = _("Re-implements the legacy, deficient, logging");
    +}
    +
    +PURPLE_INIT_PLUGIN(oldlogger, init_plugin, info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/oldlogger/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,8 @@
    +[Old Logger]
    +type=default
    +depends=purple
    +provides=oldlogger
    +summary=Re-implements the legacy, deficient, logging
    +description=%(summary)s
    +authors=Stu Tomlinson
    +introduced=1.0beta1
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/plonkers/Makefile.am Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,30 @@
    +EXTRA_DIST = \
    + plugins.cfg
    +
    +plonkersdir = $(PIDGIN_LIBDIR)
    +
    +plonkers_la_LDFLAGS = \
    + -module \
    + -avoid-version
    +
    +if HAVE_PIDGIN
    +
    +plonkers_LTLIBRARIES = plonkers.la
    +
    +plonkers_la_SOURCES = \
    + plonkers.c
    +
    +plonkers_la_LIBADD = \
    + $(GTK_LIBS) \
    + $(PIDGIN_LIBS)
    +
    +endif
    +
    +AM_CPPFLAGS = \
    + -DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
    + -DDATADIR=\"$(PIDGIN_DATADIR)\" \
    + -DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
    + $(DEBUG_CFLAGS) \
    + $(GTK_CFLAGS) \
    + $(GLIB_CFLAGS) \
    + $(PIDGIN_CFLAGS)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/plonkers/plonkers.c Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,360 @@
    +/*
    + Purple-Plonkers - Manage the plonkers out in cyberland
    + Copyright (C) 2005-2008 Peter Lawler
    +
    + Very loosely based on gxr, Copyright (C) 2004 Gary Kramlich
    +
    + 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 <gtk/gtk.h>
    +#include <gdk/gdk.h>
    +
    +#include <cmds.h>
    +#include <conversation.h>
    +#include <debug.h>
    +#include <gtkconv.h>
    +#include <gtkplugin.h>
    +#include <gtkprefs.h>
    +#include <gtkutils.h>
    +#include <plugin.h>
    +#include <prefs.h>
    +#include <util.h>
    +
    +/* #define PLONKERS_DEBUG */
    +/*******************************************************************************
    + * Constants
    + ******************************************************************************/
    +
    +/*******************************************************************************
    + * Globals
    + ******************************************************************************/
    +static PurpleCmdId plonkers_cmd;
    +static PurpleCmdId plonk_cmd;
    +
    +/*******************************************************************************
    + * Callbacks
    + ******************************************************************************/
    +
    +/*******************************************************************************
    + * Helpers
    + ******************************************************************************/
    +static gchar *
    +plonkers_format_info(PurpleConversation *conv) {
    + GString *plonkers_str;
    + gchar *ret, *plonkers_char;
    + const gchar *format;
    + GList *plonkers_list;
    + guint plonkers_size;
    +
    + plonkers_list = purple_conv_chat_get_ignored(PURPLE_CONV_CHAT(conv));
    + if (!plonkers_list)
    + return NULL;
    + plonkers_size = g_list_length (plonkers_list);
    + plonkers_char = NULL;
    + for ( ; plonkers_list; plonkers_list = plonkers_list->next) {
    + if (!plonkers_char) {
    + plonkers_char = g_strdup_printf("%s", (char *)plonkers_list->data);
    + } else {
    + plonkers_char = g_strdup_printf("%s, %s", plonkers_char, (char *)plonkers_list->data);
    + }
    + }
    + plonkers_str = g_string_new("");
    + if (plonkers_size == 1) {
    + format = g_strdup(purple_prefs_get_string("/plugins/core/plugin_pack/plonkers/plonkers/format_singular"));
    + } else {
    + format = g_strdup(purple_prefs_get_string("/plugins/core/plugin_pack/plonkers/plonkers/format_plural"));
    + }
    +
    + while(format) {
    +#ifdef PLONKERS_DEBUG
    + purple_debug_info("plonkers", "Str: %s\n", plonkers_str->str);
    +#endif
    + if(format[0] != '%') {
    + plonkers_str = g_string_append_c(plonkers_str, format[0]);
    + format++;
    + continue;
    + }
    +
    + format++;
    + if(!format[0])
    + break;
    +
    + switch(format[0]) {
    + case '%':
    + plonkers_str = g_string_append_c(plonkers_str, '%');
    + break;
    + case 'N':
    + g_string_append_printf(plonkers_str, "%d", plonkers_size);
    + break;
    + case 'P':
    + plonkers_str = g_string_append(plonkers_str, plonkers_char);
    + break;
    + }
    + format++;
    + }
    + ret = plonkers_str->str;
    + g_string_free(plonkers_str, FALSE);
    + if (plonkers_char)
    + g_free(plonkers_char);
    + purple_debug_info("plonkers", "Formatted plonkers: %s\n", ret);
    + return ret;
    +}
    +
    +static void
    +plonkers_display(PurpleConversation *conv) {
    + gchar *text = NULL;
    +
    + g_return_if_fail(conv);
    + text = plonkers_format_info(conv);
    +
    + if(!text)
    + return;
    + purple_conv_chat_send(PURPLE_CONV_CHAT(conv), text);
    + if(text)
    + g_free(text);
    +}
    +
    +/*******************************************************************************
    + * Command cb's
    + ******************************************************************************/
    +static PurpleCmdRet
    +plonkers_cmd_cb(PurpleConversation *c, const gchar *cmd, gchar **args, gchar **error, void *data) {
    + /* I plan a switch that dumps the current 'block' list, once purple privacy
    + * can export */
    +#if 0
    + gchar *lower;
    +
    + if (args[0])
    + lower = g_ascii_strdown(args[0], strlen(args[0]));
    +#endif
    + plonkers_display(c);
    +#if 0
    + if (args[0])
    + g_free(lower);
    +#endif
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +static PurpleCmdRet
    +plonk_cmd_cb(PurpleConversation *c, const gchar *cmd, gchar **args, gchar **error, void *data) {
    +/* this is the funky 'mass block/ignore' routine.
    + * given a/n list of ID/'s it'll add that|those to all block|ignore lists
    + * of each account of the same prpl type.
    + */
    +/*
    + * gchar* g_strdelimit (gchar *string, const gchar *delimiters, gchar new_delimiter);
    + * gchar** g_strsplit (const gchar *string, const gchar *delimiter, gint max_tokens);
    + */
    + PurpleConversationUiOps *ops;
    + GSList *l;
    + char *room = NULL;
    + GList *plonks = NULL;
    + GList *members = NULL;
    + gchar **tmp;
    + if(!args[0]) {
    + purple_debug_info("Plonkers", "Bad arg: %s\n", args[0]);
    + return PURPLE_CMD_RET_FAILED;
    + }
    + if(!g_utf8_validate(*args, -1, NULL)) {
    + purple_debug_info("Plonkers", "Invalid UTF8: %s\n", args[0]);
    + return PURPLE_CMD_RET_FAILED;
    + }
    + purple_debug_info("plonkers", "Plonk arg: %s\n", args[0]);
    + g_strdelimit (*args, "_-|> <.,:;", ' ');
    + purple_debug_info("plonkers", "Plonk delimited arg: %s\n", args[0]);
    + tmp = g_strsplit(args[0], " ", 0);
    + purple_debug_info("plonkers", "Plonk strsplit length: %i\n", g_strv_length(tmp));
    + /* next step, remove duplicates in the array */
    +
    + ops = purple_conversation_get_ui_ops(c);
    +
    + PurpleAccount *account = purple_conversation_get_account(c);
    + members = purple_conv_chat_get_users(PURPLE_CONV_CHAT(c));
    + for (l = account->deny; l != NULL; l = l->next) {
    + for (plonks = members; plonks; plonks = plonks->next) {
    + if (!purple_utf8_strcasecmp((char *)l->data, plonks->data)) {
    + purple_debug_info("plonkers", "Ignoring room member %s in room %s\n" ,(gchar *)plonks->data, room);
    +/* purple_conv_chat_ignore(PURPLE_CONV_CHAT(c),plonks->data);
    + * ops->chat_update_user((c), plonks->data); */
    + }
    + }
    + }
    + g_list_free(plonks);
    + g_list_free(members);
    + g_strfreev(tmp);
    + return PURPLE_CMD_RET_OK;
    +}
    +
    +/*******************************************************************************
    + * Prefs stuff
    + ******************************************************************************/
    +static GtkWidget *
    +plonkers_make_label(const gchar *text, GtkSizeGroup *sg) {
    + GtkWidget *label;
    +
    + label = gtk_label_new_with_mnemonic(text);
    + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    + gtk_widget_show(label);
    + if(sg)
    + gtk_size_group_add_widget(sg, label);
    +
    + return label;
    +}
    +
    +static GtkWidget *
    +plonkers_get_config_frame(PurplePlugin *plugin) {
    + GtkWidget *vbox, *hbox, *frame, *label;
    + GtkSizeGroup *sg;
    +
    + vbox = gtk_vbox_new(FALSE, 6);
    + gtk_container_set_border_width(GTK_CONTAINER(vbox), 12);
    +
    + frame = pidgin_make_frame(vbox, _("Ignored Plonkers"));
    +
    + pidgin_prefs_labeled_entry(frame, _("Plonkers singular format:"),
    + "/plugins/core/plugin_pack/plonkers/plonkers/format_singular",
    + NULL);
    + pidgin_prefs_labeled_entry(frame, _("Plonkers plural format:"),
    + "/plugins/core/plugin_pack/plonkers/plonkers/format_plural",
    + NULL);
    +
    + frame = pidgin_make_frame(vbox, _("Plonking"));
    + pidgin_prefs_labeled_entry(frame, _("Plonked singular plural:"),
    + "/plugins/core/plugin_pack/plonkers/plonked/format_singular",
    + NULL);
    + pidgin_prefs_labeled_entry(frame, _("Plonked plural format:"),
    + "/plugins/core/plugin_pack/plonkers/plonked/format_plural",
    + NULL);
    + sg = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
    +
    +
    + frame = pidgin_make_frame(vbox, _("Format information"));
    + hbox = gtk_hbox_new(FALSE, 6);
    + gtk_box_pack_start(GTK_BOX(frame), hbox, FALSE, FALSE, 0);
    + gtk_widget_show(hbox);
    +
    + label = plonkers_make_label(_("%P: List of plonkers"), sg);
    + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    + label = plonkers_make_label(_("%N: Number of plonkers"), NULL);
    + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
    +
    +
    + gtk_widget_show_all(vbox);
    +
    + return vbox;
    +}
    +
    +/*******************************************************************************
    + * Stock stuff
    + ******************************************************************************/
    +
    +/*******************************************************************************
    + * Plugin stuff
    + ******************************************************************************/
    +static gboolean
    +plonkers_load(PurplePlugin *plugin) {
    + const gchar *help = _("<pre>plonkers;\nTell people in a chat what you "
    + "really think of them\n</pre>");
    +
    + /* register our command */
    + plonkers_cmd = purple_cmd_register("plonkers", "", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_CHAT, NULL,
    + plonkers_cmd_cb, help, NULL);
    + plonk_cmd = purple_cmd_register("plonk", "s", PURPLE_CMD_P_PLUGIN,
    + PURPLE_CMD_FLAG_CHAT|PURPLE_CMD_FLAG_IM, NULL,
    + plonk_cmd_cb, help, NULL);
    +
    + return TRUE;
    +}
    +
    +static gboolean
    +plonkers_unload(PurplePlugin *plugin) {
    + /* remove our command */
    + purple_cmd_unregister(plonkers_cmd);
    + purple_cmd_unregister(plonk_cmd);
    +
    + return TRUE;
    +}
    +
    +static PidginPluginUiInfo ui_info = {
    + plonkers_get_config_frame,
    + 0,
    + NULL,
    + NULL,
    + NULL,
    + NULL
    +};
    +
    +static PurplePluginInfo plonkers_info = {
    + PURPLE_PLUGIN_MAGIC, /* Fear */
    + PURPLE_MAJOR_VERSION, /* the */
    + PURPLE_MINOR_VERSION, /* reaper */
    + PURPLE_PLUGIN_STANDARD, /* type */
    + PIDGIN_PLUGIN_TYPE, /* ui requirement */
    + 0, /* flags */
    + NULL, /* dependencies */
    + PURPLE_PRIORITY_DEFAULT, /* priority */
    +
    + "core-plugin_pack-Plonkers", /* id */
    + NULL, /* name */
    + PP_VERSION, /* version */
    + NULL, /* summary */
    + NULL, /* description */
    + "Peter Lawler <bleeter from users.sf.net>", /* author */
    + PP_WEBSITE, /* homepage */
    + plonkers_load, /* load */
    + plonkers_unload, /* unload */
    + NULL, /* destroy */
    +
    + &ui_info, /* ui info */
    + NULL, /* extra info */
    + NULL, /* prefs info */
    + NULL, /* actions info */
    + NULL, /* reserved 1 */
    + NULL, /* reserved 2 */
    + NULL, /* reserved 3 */
    + NULL /* reserved 4 */
    +};
    +
    +static void
    +init_plugin(PurplePlugin *plugin) {
    + purple_prefs_add_none("/plugins/core/plugin_pack");
    + purple_prefs_add_none("/plugins/core/plugin_pack/plonkers");
    + purple_prefs_add_none("/plugins/core/plugin_pack/plonkers/plonkers");
    + purple_prefs_add_string("/plugins/core/plugin_pack/plonkers/plonkers/format_singular",
    + _("/me has identified %N plonker: %P."));
    + purple_prefs_add_string("/plugins/core/plugin_pack/plonkers/plonkers/format_plural",
    + _("/me has identified %N plonkers: %P."));
    + purple_prefs_add_none("/plugins/core/plugin_pack/plonkers/plonked");
    + purple_prefs_add_string("/plugins/core/plugin_pack/plonkers/plonked/format_singular",
    + _("/me plonks: %P."));
    + purple_prefs_add_string("/plugins/core/plugin_pack/plonkers/plonked/format_plural",
    + _("/me plonks: %P."));
    +
    + plonkers_info.name = _("Plonkers");
    + plonkers_info.summary = _("Tell plonkers what you really think.");
    + plonkers_info.description = _("Plonkers is a small plugin that lets you "
    + "announce to a chat room your current list of ignores, as well as "
    + "providing other pointless ignore and privacy tools for dealing "
    + "with idiots. The name is inspired by the British/Irish word for "
    + "'idiots.'");
    +}
    +
    +PURPLE_INIT_PLUGIN(plonkers, init_plugin, plonkers_info)
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/plonkers/plugins.cfg Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,9 @@
    +[Plonkers]
    +type=default
    +depends=pidgin
    +provides=plonkers
    +summary=Tell plonkers what you really think
    +description=Plonkers is a small plugin that lets you announce to a chat room your current list of ignores, as well as providing other pointless ignore and privacy tools for dealing with idiots. The name is inspired by the British/Irish word for 'idiots.'
    +authors=Peter Lawler
    +introduced=1.0beta2
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/plugin_pack.nsi Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,66 @@
    +; NSIS Script for the Purple Plugin Pack
    +; Author Gary Kramlich
    +; Based on the guifications2.x installer
    +; Uses NSIS v2.0
    +
    +;Include Modern UI
    +!include "MUI.nsh"
    +
    +!include "FileFunc.nsh"
    +!insertmacro GetParameters
    +!insertmacro GetOptions
    +
    +;---------
    +; General
    +;---------
    +Name "Plugin Pack ${PP_VERSION}"
    +CRCCheck On
    +OutFile "purple-plugin-pack-${PP_VERSION}.exe"
    +
    +InstallDir "$PROGRAMFILES\pidgin"
    +InstallDirRegKey HKLM SOFTWARE\pidgin ""
    +
    +ShowInstDetails show
    +ShowUnInstDetails show
    +SetCompressor /SOLID lzma
    +
    +!insertmacro MUI_RESERVEFILE_LANGDLL
    +!define PP_UNINST_EXE "purple-plugin-pack-uninst.exe"
    +
    +
    +; Pidgin helper stuff
    +!addincludedir "${PIDGIN_TREE_TOP}\pidgin\win32\nsis"
    +!include "pidgin-plugin.nsh"
    +
    +;---------------------------
    +; UI Config
    +;---------------------------
    +!define MUI_HEADERIMAGE
    +!define MUI_HEADERIMAGE_BITMAP "nsis\header.bmp"
    +!define MUI_ABORTWARNING
    +
    +;---------------------------
    +; Translations
    +;---------------------------
    +!insertmacro MUI_LANGUAGE "English"
    +!include "nsis\translations\english.nsh"
    +
    +;---------------------------
    +; Pages
    +;---------------------------
    +
    +; Welcome
    +!define MUI_WELCOMEPAGE_TITLE $(WELCOME_TITLE)
    +!define MUI_WELCOMEPAGE_TEXT $(WELCOME_TEXT)
    +!insertmacro MUI_PAGE_WELCOME
    +
    +;---------------------------
    +; Sections
    +;---------------------------
    +Section -SecUninstallOld
    + ; Check install rights...
    + Call CheckUserInstallRights
    + Pop $R0
    +
    + StrCmp $R0 "HKLM" rights_hklm
    + StrCmp $R0 "HKCU" rights_hkcu done
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/plugin_pack.py Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,613 @@
    +#!/usr/bin/python
    +
    +# plugin_pack.py - Helper script for obtaining info about the plugin pack
    +# Copyright (C) 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 02111-1301, USA.
    +
    +"""Usage: plugin_pack.py [OPTION...] command
    +
    +Flags:
    +
    + -a Load abusive plugins
    + -d Load default plugins
    + -i Load incomplate plugins
    +
    + -f Load finch plugins
    + -p Load purple plugins
    + -P Load pidgin plugins
    +
    +Commands:
    +"""
    +
    +import ConfigParser
    +import getopt
    +import glob
    +import os.path
    +import string
    +import sys
    +
    +webpage = 'http://plugins.guifications.org/'
    +
    +def printerr(msg):
    + print >> sys.stderr, msg
    +
    +class Plugin:
    + name = ''
    + directory = ''
    + type = ''
    + depends = []
    + provides = ''
    + summary = ''
    + description = ''
    + authors = []
    + introduced = ''
    + notes = ''
    +
    + def __init__(self, directory, name, parser):
    + self.name = name
    +
    + self.directory = directory
    +
    + self.type = parser.get(name, 'type')
    + self.depends = parser.get(name, 'depends').split()
    + self.provides = parser.get(name, 'provides')
    + self.summary = parser.get(name, 'summary')
    + self.description = parser.get(name, 'description')
    + self.authors = parser.get(name, 'authors').split(',')
    + self.introduced = parser.get(name, 'introduced')
    +
    + if parser.has_option(name, 'notes'):
    + self.notes = parser.get(name, 'notes')
    +
    + if self.type != 'default' and self.type != 'incomplete' and self.type != 'abusive':
    + printerr('\'%s\' has an unknown type of \'%s\'!' % (self.name, self.type))
    +
    + def __str__(self):
    + output = 'name: %s\n' % self.name
    + output += 'authors: %s\n' % string.join(self.authors, ', ')
    + output += 'type: %s\n' % self.type
    + output += 'depends: %s\n' % string.join(self.depends, ' ')
    + output += 'provides: %s\n' % self.provides
    + output += 'directory: %s\n' % self.directory
    + output += 'summary: %s\n' % self.summary
    + output += 'description: %s\n' % self.description
    +
    + if self.notes:
    + output += 'notes: %s\n' % self.notes
    +
    + return output
    +
    +class PluginPack:
    + commands = {}
    + plugins = {}
    +
    + def load_plugins(self, types, depends):
    + if len(types) == 0:
    + types = None
    +
    + if len(depends) == 0:
    + depends = None
    +
    + for file in glob.glob('*/plugins.cfg'):
    + parser = ConfigParser.ConfigParser()
    +
    + try:
    + parser.read(file)
    + except ConfigParser.ParsingError, msg:
    + printerr('Failed to parse \'%s\':\n%s' % (file, msg))
    + continue
    +
    + for plugin in parser.sections():
    + p = Plugin(os.path.dirname(file), plugin, parser)
    +
    + # this is kind of hacky, but if we have types, we check to see
    + # if the type is in list of types to load.
    + if types and not p.type in types:
    + continue
    +
    + # now we check if the give plugins depends match the search
    + # depends
    + if depends:
    + if len(set(depends).intersection(set(p.depends))) == 0:
    + continue
    +
    + self.plugins[p.provides] = p
    +
    + def list_type(self, type):
    + list = []
    +
    + for name in self.plugins.keys():
    + plugin = self.plugins[name]
    + if plugin.type == type:
    + list.append(plugin)
    +
    + list.sort()
    +
    + return list
    +
    + def list_dep(self, dep):
    + list = []
    +
    + for name in self.plugins.keys():
    + plugin = self.plugins[name]
    +
    + if dep in plugin.depends:
    + list.append(plugin)
    +
    + list.sort()
    +
    + return list
    +
    + def print_names(self, list):
    + names = []
    +
    + for plugin in list:
    + names.append(plugin.name)
    +
    + print string.join(names, ',')
    +
    + def default_plugins(self):
    + return self.list_type('default')
    +
    + def abusive_plugins(self):
    + return self.list_type('abusive')
    +
    + def incomplete_plugins(self):
    + return self.list_type('incomplete')
    +
    + def purple_plugins(self):
    + return self.list_dep('purple')
    +
    + def finch_plugins(self):
    + return self.list_dep('finch')
    +
    + def pidgin_plugins(self):
    + return self.list_dep('pidgin')
    +
    + def unique_dirs(self):
    + dirs = {}
    + for name in self.plugins.keys():
    + dirs[self.plugins[name].directory] = 1
    +
    + dirs = dirs.keys()
    + dirs.sort()
    +
    + return dirs
    +
    + def help(self, args):
    + """Displays information about other commands"""
    + try:
    + cmd = self.commands[args[0]]
    + print cmd.__doc__
    + except KeyError:
    + print 'command \'%s\' was not found' % args[0]
    + except IndexError:
    + print '%s' % (self.help.__doc__)
    + print
    + print 'help usage:'
    + print ' help <command>'
    + print
    + print 'Available commands:'
    +
    + cmds = self.commands.keys()
    + cmds.remove('help')
    + cmds.sort()
    + print ' %s' % (string.join(cmds, ' '))
    + commands['help'] = help
    +
    + def dist_dirs(self, args):
    + """Displays a list of all plugin directories to included in the distribution"""
    + print string.join(self.unique_dirs(), ' ')
    + commands['dist_dirs'] = dist_dirs
    +
    + def build_dirs(self, args):
    + """Displays a list of the plugins that can be built"""
    + if len(args) != 2:
    + printerr('build_dirs expects 2 arguments:')
    + printerr('\ta comma separated list of dependencies')
    + printerr('\ta comma separated list of plugins to build')
    + sys.exit(1)
    +
    + # store the external depedencies
    + externals = args[0].split(',')
    +
    + deps = {}
    +
    + # run through the provided dependencies, setting their dependencies to
    + # nothing since we know we already have them
    + for d in externals:
    + deps[d] = []
    +
    + # now run through the plugins adding their deps to the dictionary
    + for name in self.plugins.keys():
    + plugin = self.plugins[name]
    +
    + deps[plugin.provides] = plugin.depends
    +
    + # run through the requested plugins and store their plugin instance in check
    + check = []
    + for provides in args[1].split(','):
    + try:
    + if provides == 'all':
    + defaults = []
    + for p in self.default_plugins():
    + defaults.append(p.provides)
    +
    + check += defaults
    +
    + continue
    +
    + plugin = self.plugins[provides]
    + check.append(plugin.provides)
    + except KeyError:
    + continue
    +
    + # convert our list of plugins to check into a set to remove dupes
    + #check = set(check)
    +
    + # create our list of plugins to build
    + build = []
    +
    + # now define a function to check our deps
    + def has_deps(provides):
    + # don't add anything to build more than once
    + if provides in build:
    + return True
    +
    + try:
    + dep_list = deps[provides]
    + except KeyError:
    + return False
    +
    + # now check the dependencies
    + for dep in dep_list:
    + if '|' in dep:
    + count = 0
    + for d in dep.split('|'):
    + if has_deps(d):
    + count += 1
    +
    + if count == 0:
    + return False
    + else:
    + if not has_deps(dep):
    + return False
    +
    + # make sure the provides isn't an external
    + if not provides in externals:
    + build.append(provides)
    +
    + # everything checks out!
    + return True
    +
    + # check all the plugins we were told to for their dependencies
    + for c in check:
    + has_deps(c)
    +
    + # now create a list of all directories to build
    + output = []
    +
    + for provides in build:
    + plugin = self.plugins[provides]
    +
    + output.append(plugin.directory)
    +
    + output.sort()
    +
    + print "%s" % (string.join(output, ','))
    + commands['build_dirs'] = build_dirs
    +
    + def list_plugins(self, args):
    + """Displays a list similiar to 'dpkg -l' about the plugin pack"""
    +
    + data = {}
    +
    + # create an array for the widths, we initialize it to the lengths of
    + # the title strings. We ignore summary, since that one shouldn't
    + # matter.
    + widths = [4, 8, 0]
    +
    + for p in self.plugins.keys():
    + plugin = self.plugins[p]
    +
    + if plugin.type == 'abusive':
    + type = 'a'
    + elif plugin.type == 'incomplete':
    + type = 'i'
    + else:
    + type = 'd'
    +
    + if 'finch' in plugin.depends:
    + ui = 'f'
    + elif 'pidgin' in plugin.depends:
    + ui = 'p'
    + elif 'purple' in plugin.depends:
    + ui = 'r'
    + else:
    + ui = 'u'
    +
    + widths[0] = max(widths[0], len(plugin.name))
    + widths[1] = max(widths[1], len(plugin.provides))
    + widths[2] = max(widths[2], len(plugin.summary))
    +
    + data[plugin.provides] = [type, ui, plugin.name, plugin.provides, plugin.summary]
    +
    + print 'Type=Default/Incomplete/Abusive'
    + print '| UI=Finch/Pidgin/puRple/Unknown'
    + print '|/ Name%s Provides%s Summary' % (' ' * (widths[0] - 4), ' ' * (widths[1] - 8))
    + print '++-%s-%s-%s' % ('=' * (widths[0]), '=' * (widths[1]), '=' * (widths[2]))
    +
    + # create the format var
    + fmt = '%%s%%s %%-%ds %%-%ds %%s' % (widths[0], widths[1]) #, widths[2])
    +
    + # now loop through the list again, with everything formatted
    + list = data.keys()
    + list.sort()
    +
    + for p in list:
    + d = data[p]
    + print fmt % (d[0], d[1], d[2], d[3], d[4])
    + commands['list'] = list_plugins
    +
    + def config_file(self, args):
    + """Outputs the contents for the file to be m4_include()'d from configure"""
    + uniqdirs = self.unique_dirs()
    +
    + # add our --with-plugins option
    + print 'AC_ARG_WITH(plugins,'
    + print ' AC_HELP_STRING([--with-plugins], [what plugins to build]),'
    + print ' ,with_plugins=all)'
    + print 'if test -z $with_plugins ; then'
    + print '\twith_plugins=all'
    + print 'fi'
    +
    + # determine and add our output files
    + print 'PP_DIST_DIRS="%s"' % (string.join(uniqdirs, ' '))
    + print 'AC_SUBST(PP_DIST_DIRS)'
    + print
    + print 'AC_CONFIG_FILES(['
    + for dir in uniqdirs:
    + print '\t%s/Makefile' % (dir)
    + print '])'
    + print
    +
    + # setup a second call to determine the plugins to be built
    + print 'PP_BUILD=`$PYTHON $srcdir/plugin_pack.py build_dirs $DEPENDENCIES $with_plugins`'
    + print
    + print 'PP_BUILD_DIRS=`echo $PP_BUILD | sed \'s/,/\ /g\'`'
    + print 'AC_SUBST(PP_BUILD_DIRS)'
    + print
    + print 'PP_PURPLE_BUILD="$PYTHON $srcdir/plugin_pack.py -p show_names $PP_BUILD"'
    + print 'PP_PIDGIN_BUILD="$PYTHON $srcdir/plugin_pack.py -P show_names $PP_BUILD"'
    + print 'PP_FINCH_BUILD="$PYTHON $srcdir/plugin_pack.py -f show_names $PP_BUILD"'
    + commands['config_file'] = config_file
    +
    + def dependency_graph(self, args):
    + """Outputs a graphviz script showing plugin dependencies"""
    + def node_label(plugin):
    + node = plugin.provides.replace('-', '_')
    + label = plugin.name
    +
    + return node, label
    +
    + def print_plugins(list):
    + for plugin in list:
    + node, label = node_label(plugin)
    +
    + print '\t%s[label="%s"];' % (node, label)
    +
    + print 'digraph {'
    + print '\tlabel="Dependency Graph";'
    + print '\tlabelloc="t";'
    + print '\tsplines=TRUE;'
    + print '\toverlap=FALSE;'
    + print
    + print '\tnode[fontname="sans", fontsize="8", style="filled"];'
    + print
    +
    + # run through the default plugins
    + print '\t/* default plugins */'
    + print '\tnode[fillcolor="palegreen",shape="tab"];'
    + print_plugins(self.default_plugins())
    + print
    +
    + # run through the incomplete plugins
    + print '\t/* incomplete plugins */'
    + print '\tnode[fillcolor="lightyellow1",shape="note"];'
    + print_plugins(self.incomplete_plugins())
    + print
    +
    + # run through the abusive plugins
    + print '\t/* abusive plugins */'
    + print '\tnode[fillcolor="lightpink",shape="octagon"];'
    + print_plugins(self.abusive_plugins())
    + print
    +
    + # run through again, this time showing the relations
    + print '\t/* dependencies'
    + print '\t * exteranl ones that don\'t have nodes get colored to the following'
    + print '\t */'
    + print '\tnode[fillcolor="powderblue", shape="egg"];'
    +
    + for name in self.plugins.keys():
    + plugin = self.plugins[name]
    +
    + node, label = node_label(plugin)
    +
    + for dep in plugin.depends:
    + dep = dep.replace('-', '_')
    + print '\t%s -> %s;' % (node, dep)
    +
    + print '}'
    + commands['dependency_graph'] = dependency_graph
    +
    + def debian_description(self, args):
    + """Outputs the description for the Debian packages"""
    + print 'Description: %d useful plugins for Pidgin, Finch, and Purple' % len(self.plugins)
    + print ' The Plugin Pack is a collection of many simple-yet-useful plugins for Pidgin,'
    + print ' Finch, and Purple. You will find a summary of each plugin below. For more'
    + print ' about an individual plugin, please see %s' % webpage
    + print ' .'
    + print ' Note: not all %d of these plugins are currently usable' % len(self.plugins)
    +
    + list = self.plugins.keys()
    + list.sort()
    + for key in list:
    + plugin = self.plugins[key]
    + print ' .'
    + print ' %s: %s' % (plugin.name, plugin.summary)
    +
    + print ' .'
    + print ' .'
    + print ' Homepage: %s' % webpage
    + commands['debian_description'] = debian_description
    +
    + def show_names(self, args):
    + """Displays the names of the given comma separated list of provides"""
    +
    + if len(args) == 0 or len(args[0]) == 0:
    + printerr('show_names expects a comma separated list of provides')
    + sys.exit(1)
    +
    + provides = args[0].split(',')
    + if len(provides) == 0:
    + print "none"
    +
    + line = " "
    +
    + for provide in provides:
    + if not provide in self.plugins:
    + continue
    +
    + name = self.plugins[provide].name
    +
    + if len(line) + len(name) + 2 > 75:
    + print line.rstrip(',')
    + line = ' '
    +
    + line += ' %s,' % name
    +
    + if len(line) > 1:
    + print line.rstrip(',')
    + commands['show_names'] = show_names
    +
    + def info(self, args):
    + """Displays all information about the given plugins"""
    + for p in args:
    + try:
    + print self.plugins[p].__str__().strip()
    + except KeyError:
    + print 'Failed to find a plugin that provides \'%s\'' % (p)
    +
    + print
    + commands['info'] = info
    +
    + def stats(self, args):
    + """Displays stats about the plugin pack"""
    + counts = {}
    +
    + counts['total'] = len(self.plugins)
    + counts['default'] = len(self.default_plugins())
    + counts['incomplete'] = len(self.incomplete_plugins())
    + counts['abusive'] = len(self.abusive_plugins())
    + counts['purple'] = len(self.purple_plugins())
    + counts['finch'] = len(self.finch_plugins())
    + counts['pidgin'] = len(self.pidgin_plugins())
    +
    + def value(val):
    + return "%3d (%6.2f%%)" % (val, (float(val) / float(counts['total'])) * 100.0)
    +
    + print "Purple Plugin Pack Stats"
    + print ""
    + print "%d plugins in total" % (counts['total'])
    + print
    + print "Status:"
    + print " complete: %s" % (value(counts['default']))
    + print " incomplete: %s" % (value(counts['incomplete']))
    + print " abusive: %s" % (value(counts['abusive']))
    + print ""
    + print "Type:"
    + print " purple: %s" % (value(counts['purple']))
    + print " finch: %s" % (value(counts['finch']))
    + print " pidgin: %s" % (value(counts['pidgin']))
    + commands['stats'] = stats
    +
    +def show_usage(pp, exitcode):
    + print __doc__
    +
    + cmds = pp.commands.keys()
    + cmds.sort()
    +
    + for cmd in cmds:
    + print " %-20s %s" % (cmd, pp.commands[cmd].__doc__)
    +
    + print ""
    +
    + sys.exit(exitcode)
    +
    +def main():
    + # create our main instance
    + pp = PluginPack()
    +
    + types = []
    + depends = []
    +
    + try:
    + shortopts = 'adfiPp'
    +
    + opts, args = getopt.getopt(sys.argv[1:], shortopts)
    + except getopt.error, msg:
    + print msg
    + show_usage(pp, 1)
    +
    + for o, a in opts:
    + if o == '-a':
    + types.append('abusive')
    + elif o == '-d':
    + types.append('default')
    + elif o == '-i':
    + types.append('incomplete')
    + elif o == '-f':
    + depends.append('finch')
    + elif o == '-P':
    + depends.append('pidgin')
    + elif o == '-p':
    + depends.append('purple')
    +
    + # load the plugins that have been requested, if both lists are empty, all
    + # plugins are loaded
    + pp.load_plugins(types, depends)
    +
    + if(len(args) == 0):
    + show_usage(pp, 1)
    +
    + cmd = args[0]
    + args = args[1:]
    +
    + try:
    + pp.commands[cmd](pp, args)
    + except KeyError:
    + printerr('\'%s\' command not found' % (cmd))
    +
    +if __name__ == '__main__':
    + # this is a work around when we're called for a directory that isn't the
    + # directory that this file is in. This happens during distcheck, as well
    + # as a few other cases that most people won't use ;)
    + if os.path.dirname(__file__) != '':
    + os.chdir(os.path.dirname(__file__))
    +
    + main()
    +
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/plugin_pack.spec.in Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,115 @@
    +#
    +# pidgin_major_ver and pidgin_minor_ver should be defined to match the minimum
    +# Pidgin API version _required_ to build Plugin Pack
    +# Due to the way Pidgin checks plugin versions, we need to also ensure that
    +# the correct minimum version of Pidgin is Require:'d based on what version of
    +# the Pidgin headers we actually build with.
    +#
    +
    +%define pidgin_major_ver 2
    +%define pidgin_minor_ver 0
    +%define pidgin_next_major_ver %(echo $((%{pidgin_major_ver}+1)))
    +%define pidgin_build_minor_ver %(pkg-config --modversion pidgin | awk -F. '{ print $2 }')
    +
    +Summary: Plugin Pack for libpurple and derived IM clients
    +Name: @PACKAGE@
    +Version: @VERSION@
    +
    +Release: 0%{?pidgindist:.%{pidgindist}}
    +License: GPL
    +Group: Applications/Internet
    +
    +URL: http://plugins.guifications.org/
    +Source0: %{name}-%{version}.tar.bz2
    +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
    +
    +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
    +%else
    +BuildRequires: gtk2-devel
    +%endif
    +Requires: libpurple >= %{pidgin_major_ver}.%{pidgin_build_minor_ver}, libpurple < %{pidgin_next_major_ver}
    +
    +%package -n pidgin-plugin_pack
    +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
    +
    +%description -n pidgin-plugin_pack
    +All the other plugins for Pidgin
    +
    +%prep
    +%setup -q
    +
    +%build
    +%configure
    +make %{?_smp_mflags}
    +
    +%install
    +rm -rf $RPM_BUILD_ROOT
    +make install DESTDIR=$RPM_BUILD_ROOT
    +
    +rm -f $RPM_BUILD_ROOT%{_libdir}/purple-2/*.la $RPM_BUILD_ROOT%{_libdir}/purple-2/*.a
    +rm -f $RPM_BUILD_ROOT%{_libdir}/pidgin/*.la $RPM_BUILD_ROOT%{_libdir}/pidgin/*.a
    +%find_lang plugin_pack
    +
    +%clean
    +rm -rf $RPM_BUILD_ROOT
    +
    +%files -f plugin_pack.lang
    +%defattr(-,root,root,-)
    +%doc AUTHORS ChangeLog COPYING README
    +%{_libdir}/purple-2/*.so
    +
    +%files -n pidgin-plugin_pack -f plugin_pack.lang
    +%defattr(-,root,root,-)
    +%doc AUTHORS ChangeLog COPYING README
    +%{_libdir}/pidgin/*.so
    +%{_datadir}/pixmaps/pidgin/plugin_pack
    +
    +
    +%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
    +- Use tar.bz2 for source
    +- Split into pidgin- and purple- RPMs
    +
    +* Tue Dec 5 2006 John Bailey <rekkanoryo@rekkanoryo.org>
    +- Update the URL to match our new website
    +
    +* Thu Oct 19 2006 Stu Tomlinson <stu@nosnilmot.com>
    +- Removed locale from %%files, that's what %%find_lang is for
    +- Fixed finding translations
    +- Fixed %%s in %%changelog
    +- Package xmms pixmaps
    +- Add xmms-devel buildrequires
    +
    +* Sun Nov 11 2005 Peter Lawler <bleeter from users.sf.net>
    +- Added locale to %%files
    +- Enabled %%find_lang
    +
    +* Thu Nov 03 2005 Stu Tomlinson <stu@nosnilmot.com>
    +- Fix it again
    +
    +* Wed Nov 02 2005 Peter Lawler <bleeter@users.sf.net>
    +- Fixed up the Mandrivel .so rename
    +
    +* Tue Nov 01 2005 Stu Tomlinson <stu@nosnilmot.com>
    +- Fix it
    +
    +* Tue Nov 01 2005 Peter Lawler <bleeter@users.sf.net>
    +- Initial Spec File for Plugin Pack
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/po/Makefile.mingw Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,68 @@
    +# Makefile.mingw
    +#
    +# Description: Makefile to generate mo files
    +#
    +
    +PACKAGE = plugin_pack
    +
    +##
    +## PATHS
    +##
    +
    +srcdir = .
    +GAIM_TOP = ../../..
    +GAIM_INSTALL_DIR = $(GAIM_TOP)/win32-install-dir
    +LOCALEDIR = $(GAIM_INSTALL_DIR)/locale
    +GTK_BIN = $(GAIM_TOP)/../win32-dev/gtk_2_0/bin
    +
    +##
    +## TOOLS
    +##
    +
    +GMSGFMT = $(GTK_BIN)/msgfmt
    +
    +
    +.SUFFIXES:
    +.SUFFIXES: .po .gmo
    +
    +
    +##
    +## SOURCES, OBJECTS
    +##
    +
    +CATALOGS = $(patsubst %.po,%.gmo,$(wildcard *.po))
    +
    +##
    +## RULES
    +##
    +
    +.po.gmo:
    + rm -f $@ && $(GMSGFMT) --statistics -o $@ $<
    +
    +
    +##
    +## TARGETS
    +##
    +
    +all: $(CATALOGS)
    +
    +install: all
    + mkdir -p $(LOCALEDIR)
    + @catalogs='$(CATALOGS)'; \
    + for cat in $$catalogs; do \
    + cat=`basename $$cat`; \
    + lang=`echo $$cat | sed 's/\.gmo$$//'`; \
    + dir=$(LOCALEDIR)/$$lang/LC_MESSAGES; \
    + mkdir -p $$dir; \
    + if test -r $$cat; then \
    + cp $$cat $$dir/$(PACKAGE).mo; \
    + echo "installing $$cat as $$dir/$(PACKAGE).mo"; \
    + else \
    + cp $(srcdir)/$$cat $$dir/$(PACKAGE).mo; \
    + echo "installing $(srcdir)/$$cat as" \
    + "$$dir/$(PACKAGE).mo"; \
    + fi; \
    + done
    +
    +clean:
    + rm -f *.gmo
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/po/POTFILES.in Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,96 @@
    +album/album.c
    +album/album-ui.c
    +autoprofile/autoaway.c
    +autoprofile/autoprofile.c
    +autoprofile/autoreply.c
    +autoprofile/comp_countdownup.c
    +autoprofile/comp_executable.c
    +autoprofile/comp_http.c
    +autoprofile/comp_logstats.c
    +autoprofile/comp_logstats_gtk.c
    +autoprofile/comp_quotation.c
    +autoprofile/comp_rss.c
    +autoprofile/comp_textfile.c
    +autoprofile/comp_timestamp.c
    +autoprofile/comp_uptime.c
    +autoprofile/gtk_actions.c
    +autoprofile/gtk_away_msgs.c
    +autoprofile/gtk_widget.c
    +autoprofile/preferences.c
    +autoreply/autoreply.c
    +awaynotify/awaynotify.c
    +bash/bash.c
    +bit/bit.c
    +blistops/blistops.c
    +buddytime/buddytime.c
    +buddytime/gtkbuddytime.c
    +chronic/chronic.c
    +colorize/colorize.c
    +common/gtk_template.c
    +common/purple_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
    +google/google.c
    +gRIM/gRIM.c
    +groupmsg/groupmsg.c
    +hideconv/hideconv.c
    +highlight/highlight.c
    +ignorance/ignorance.c
    +ignorance/interface.c
    +ignorance/support.c
    +ignore/ignore.c
    +infopane/infopane.c
    +infopane/infopane.c
    +irchelper/irchelper.c
    +irc-more/irc-more.c
    +irssi/datechange.c
    +irssi/irssi.c
    +irssi/lastlog.c
    +irssi/layout.c
    +irssi/window.c
    +lastseen/lastseen.c
    +listhandler/aim_blt_files.c
    +listhandler/alias_xml_files.c
    +listhandler/alias_xml_files.c
    +listhandler/gen_xml_files.c
    +listhandler/listhandler.c
    +listhandler/migrate.c
    +listhandler/purple_blist_xml.c
    +listhandler/purple_blist_xml.c
    +listlog/listlog.c
    +msglen/msglen.c
    +mystatusbox/mystatusbox.c
    +napster/napster.c
    +nicksaid/nicksaid.c
    +nomobility/nomobility.c
    +oldlogger/oldlogger.c
    +plonkers/plonkers.c
    +schedule/pidgin-schedule.c
    +schedule/schedule.c
    +sepandtab/sepandtab.c
    +showoffline/showoffline.c
    +simfix/simfix.c
    +slashexec/slashexec.c
    +smartear/gtksmartear.c
    +smartear/smartear.c
    +snpp/snpp.c
    +splitter/splitter.c
    +sslinfo/sslinfo.c
    +stocker/stocker.c
    +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
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/po/POTFILES.skip Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,1 @@
    +plugin_pack.py
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/po/check_po.pl Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,380 @@
    +#!/usr/bin/perl -w
    +#
    +# check_po.pl - check po file translations for likely errors
    +#
    +# Written by David W. Pfitzner dwp@mso.anu.edu.au
    +# This script is hereby placed in the Public Domain.
    +#
    +# Various checks on po file translations:
    +# - printf-style format strings;
    +# - differences in trailing newlines;
    +# - empty (non-fuzzy) msgid;
    +# - likely whitespace errors on joining multi-line entries
    +# Ignores all fuzzy entries.
    +#
    +# Options:
    +# -x Don't do standard checks above (eg, just check one of below).
    +# -n Check newlines within strings; ie, that have equal numbers
    +# of newlines in msgstr and msgid. (Optional because this may
    +# happen legitimately.)
    +# -w Check leading whitespace. Sometimes whitespace is simply
    +# spacing (eg, for widget labels etc), or punctuation differences,
    +# so this may be ok.
    +# -W Check trailing whitespace. See -w above.
    +# -p Check trailing punctuation.
    +# -c Check capitalization of first non-whitespace character
    +# (only if [a-zA-Z]).
    +# -e Check on empty (c.q. new) msgstr
    +#
    +# Reads stdin (or filename args, via <>), writes any problems to stdout.
    +#
    +# Modified by Davide Pagnin nightmare@freeciv.it to support plural forms
    +#
    +# Version: 0.41 (2002-06-06)
    +
    +use strict;
    +use vars qw($opt_c $opt_n $opt_p $opt_w $opt_W $opt_x $opt_e);
    +use Getopt::Std;
    +
    +getopts('cnpwWxe');
    +
    +# Globals, for current po entry:
    +#
    +# Note that msgid and msgstr have newlines represented by the
    +# two characters '\' and 'n' (and similarly for other escapes).
    +
    +my @amsgid; # lines exactly as in input
    +my @amsgstr;
    +my $entryline; # lineno where entry starts
    +my $msgid; # lines joined by ""
    +my $msgstr;
    +my $is_fuzzy;
    +my $is_cformat;
    +my $state; # From constant values below.
    +my $did_print; # Whether we have printed this entry, to
    + # print only once for multiple problems.
    +
    +use constant S_LOOKING_START => 0; # looking for start of entry
    +use constant S_DOING_MSGID => 1; # doing msgid part
    +use constant S_DOING_MSGSTR => 2; # doing msgstr part
    +
    +# Initialize or reinitalize globals to prepare for new entry:
    +sub new_entry {
    + @amsgid = ();
    + @amsgstr = ();
    + $msgid = undef;
    + $msgstr = undef;
    + $entryline = 0;
    + $is_fuzzy = 0;
    + $is_cformat = 0;
    + $did_print = 0;
    + $state = S_LOOKING_START;
    +}
    +
    +# Nicely print either a "msgid" or "msgstr" (name is one of these)
    +# with given array of data.
    +sub print_one {
    + my $name = shift;
    + print " $name \"", join("\"\n \"", @_), "\"\n";
    +}
    +
    +# Print a problem (args like print()), preceeded by entry unless
    +# we have already printed that: label, and msgid and msgstr.
    +#
    +sub print_problem {
    + unless ($did_print) {
    + print "ENTRY:", ($ARGV eq "-" ? "" : " ($ARGV, line $entryline)"), "\n";
    + print_one("msgid", @amsgid);
    + print_one("msgstr", @amsgstr);
    + $did_print = 1;
    + }
    + print "*** ", @_;
    +}
    +
    +# Check final newline: probably, translations should end in a newline
    +# if and only if the original string does.
    +# (See also check_trailing_whitespace and check_num_newlines below.)
    +#
    +sub check_trailing_newlines {
    + if ($opt_x) { return; }
    +
    + my ($ichar, $schar);
    +
    + $ichar = (length($msgid)>=2) ? substr($msgid, -2, 2) : "";
    + $schar = (length($msgstr)>=2) ? substr($msgstr, -2, 2) : "";
    +
    + if ($ichar eq "\\n" && $schar ne "\\n") {
    + print_problem "Missing trailing newline\n";
    + }
    + if ($ichar ne "\\n" && $schar eq "\\n") {
    + print_problem "Extra trailing newline\n";
    + }
    +
    +}
    +
    +# Check leading whitespace. In general, any leading whitespace should
    +# be the same in msgstr and msgid -- but not always.
    +#
    +sub check_leading_whitespace {
    + unless ($opt_w) { return; }
    +
    + my ($id, $str);
    +
    + if ($msgid =~ m/^(\s+)/) {
    + $id = $1;
    + } else {
    + $id = "";
    + }
    + if ($msgstr =~ m/^(\s+)/) {
    + $str = $1;
    + } else {
    + $str = "";
    + }
    + if ($id ne $str) {
    + print_problem "Different leading whitespace\n";
    + }
    +}
    +
    +# Check trailing whitespace. In general, any trailing whitespace should
    +# be the same in msgstr and msgid -- but not always.
    +#
    +sub check_trailing_whitespace {
    + unless ($opt_W) { return; }
    +
    + my ($id, $str);
    +
    + if ($msgid =~ m/((?:\s|\\n)+)$/) {
    + $id = $1;
    + } else {
    + $id = "";
    + }
    + if ($msgstr =~ m/((?:\s|\\n)+)$/) {
    + $str = $1;
    + } else {
    + $str = "";
    + }
    + if ($id ne $str) {
    + print_problem "Different trailing whitespace\n";
    + }
    +}
    +
    +# Check equal numbers of newlines. In general ... etc.
    +#
    +sub check_num_newlines {
    + unless ($opt_n) { return; }
    +
    + my $num_i = ($msgid =~ m(\\n)g);
    + my $num_s = ($msgstr =~ m(\\n)g);
    +
    + if ($num_i != $num_s) {
    + print_problem "Mismatch in newline count\n";
    + }
    +
    +}
    +
    +# Check capitalization of first non-whitespace character (for [a-zA-Z]
    +# only). In general ... etc.
    +#
    +sub check_leading_capitalization {
    + unless ($opt_c) { return; }
    +
    + my ($id, $str);
    +
    + if ($msgid =~ m/^\s*([a-zA-Z])/) {
    + $id = $1;
    + }
    + if ($msgstr =~ m/^\s*([a-zA-Z])/) {
    + $str = $1;
    + }
    + if (defined($id) && defined($str)) {
    + if (($id =~ /^[a-z]$/ && $str =~ /^[A-Z]$/) ||
    + ($id =~ /^[A-Z]$/ && $str =~ /^[a-z]$/)) {
    + print_problem "Different leading capitalization\n";
    + }
    + }
    +}
    +
    +# Check trailing 'punctuation' characters (ignoring trailing whitespace).
    +# In general .. etc.
    +#
    +sub check_trailing_punctuation {
    + unless ($opt_p) { return; }
    +
    + my ($id, $str);
    +
    + # Might want more characters:
    + if ($msgid =~ m/([\\\.\/\,\!\?\"\'\:\;])+(?:\s|\\n)*$/) {
    + $id = $1;
    + } else {
    + $id = "";
    + }
    + if ($msgstr =~ m/([\\\.\/\,\!\?\"\'\:\;])+(?:\s|\\n)*$/) {
    + $str = $1;
    + } else {
    + $str = "";
    + }
    + ##print "$id $str\n";
    + if ($id ne $str) {
    + print_problem "Different trailing punctuation\n";
    + }
    +}
    +
    +# Check that multiline strings have whitespace separation, since
    +# otherwise, eg:
    +# msgstr "this is a multiline"
    +# "string"
    +# expands to:
    +# "this is a multilinestring"
    +#
    +sub check_whitespace_joins {
    + if ($opt_x) { return; }
    +
    + my $ok = 1;
    + my $i = 0;
    +
    + foreach my $aref (\@amsgid, \@amsgstr) {
    + my $prev = undef;
    + LINE:
    + foreach my $line (@$aref) {
    + if (defined($prev)
    + && length($prev)
    + && $prev !~ /\s$/
    + && $prev !~ /\\n$/
    + && $line !~ /^\s/
    + && $line !~ /^\\n/)
    + {
    + $ok = 0;
    + last LINE;
    + }
    + $prev = $line;
    + }
    + if (!$ok) {
    + print_problem("Possible non-whitespace line-join problem in ",
    + ($i==0 ? "msgid" : "msgstr"), " \n");
    + }
    + $i++;
    + }
    +}
    +
    +# Check printf-style format entries.
    +# Non-trivial, because translation strings may use format specifiers
    +# out of order, or skip some specifiers etc. Also gettext marks
    +# anything with '%' as cformat, though not all are.
    +#
    +sub check_cformat {
    + unless ($is_cformat) { return; }
    + if ($opt_x) { return; }
    +
    + my (@iform, @sform);
    + @iform = ($msgid =~ m/\%[0-9\.\$]*[a-z]/g);
    + @sform = ($msgstr =~ m/\%[0-9\.\$]*[a-z]/g);
    +
    + ##print join("::", @iform), "\n";
    + ##print join("::", @sform), "\n";
    +
    + my $js; # index in sform
    + my $j; # index into iform
    + SFORM:
    + for ($js=0; $js < @sform; $js++) {
    + my $sf = $sform[$js];
    + my $sf_orig = $sf;
    + if ($sf =~ s/^\%([0-9]+)\$(.*[a-z])$/\%$2/) {
    + $j = $1-1;
    + } else {
    + $j = $js;
    + }
    + if ($j > $#iform) {
    + print_problem("Format number mismatch for $sf_orig [msgstr:",
    + ($js+1), "]\n");
    + next SFORM;
    + }
    + my $if = $iform[$j];
    + if ($sf ne $if) {
    + print_problem("Format mismatch: $sf_orig [msgstr:", ($js+1), "]",
    + " vs $if [msgid:", ($j+1), "]\n");
    + }
    + }
    +}
    +
    +# Run all individual checks on current entry, reporting any problems.
    +sub check_entry {
    + if ($is_fuzzy) {
    + return;
    + }
    + $msgid = join("", @amsgid);
    + $msgstr = join("", @amsgstr);
    +
    + unless ($opt_x) {
    + if (length($msgid)==0) {
    + print_problem "Zero length msgid\n";
    + }
    + }
    + if (length($msgstr)==0) {
    + unless ($opt_e) { return; }
    + print_problem "Untranslated msgid\n";
    + }
    + check_cformat;
    + check_whitespace_joins;
    + check_num_newlines;
    + check_leading_whitespace;
    + check_trailing_newlines;
    + check_trailing_whitespace;
    + check_leading_capitalization;
    + check_trailing_punctuation;
    +}
    +
    +new_entry;
    +
    +LINE:
    +while(<>) {
    + if ( m(^\s*$) ) {
    + if ($state==S_DOING_MSGSTR) {
    + check_entry;
    + new_entry;
    + }
    + next LINE;
    + }
    + if ( m(^\#, fuzzy) ) {
    + $is_fuzzy = 1;
    + }
    + if ( m(^\#, .*c-format) ) {
    + # .* is because can have fuzzy, c-format
    + $is_cformat = 1;
    + }
    + if ( m(^\#) ) {
    + next LINE;
    + }
    + if ( m(^msgid \"(.*)\"$) ) {
    + $entryline = $.;
    + @amsgid = ($1);
    + $state = S_DOING_MSGID;
    + next LINE;
    + }
    + if ( m(^msgid_plural \"(.*)\"$) ) {
    + $entryline = $.;
    + @amsgid = ($1);
    + $state = S_DOING_MSGID;
    + next LINE;
    + }
    + if ( m(^msgstr \"(.*)\"$) ) {
    + @amsgstr = ($1);
    + $state = S_DOING_MSGSTR;
    + next LINE;
    + }
    + if ( m(^msgstr\[[0-2]\] \"(.*)\"$) ) {
    + @amsgstr = ($1);
    + $state = S_DOING_MSGSTR;
    + next LINE;
    + }
    + if ( m(^\"(.*)\"$) ) {
    + if ($state==S_DOING_MSGID) {
    + push @amsgid, $1;
    + } elsif($state==S_DOING_MSGSTR) {
    + push @amsgstr, $1;
    + } else {
    + die "Looking at string $_ in bad state $state,";
    + }
    + next LINE;
    + }
    + die "Unexpected at $.: ", $_;
    +}
    --- /dev/null Thu Jan 01 00:00:00 1970 +0000
    +++ b/po/en_AU.po Thu Aug 27 19:46:46 2009 -0400
    @@ -0,0 +1,3514 @@
    +# English/AU translation of plugin_pack.
    +# Copyright (C) 2005 THE PACKAGE'S COPYRIGHT HOLDER
    +# This file is distributed under the same license as the PACKAGE package.
    +# Peter Lawler <trans@six-by-nine.com.au>, 2005.
    +#
    +#, fuzzy
    +msgid ""
    +msgstr ""
    +"Project-Id-Version: plugin_pack 0.1cvs\n"
    +"Report-Msgid-Bugs-To: \n"
    +"POT-Creation-Date: 2008-12-21 00:20-0500\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"
    +"MIME-Version: 1.0\n"
    +"Content-Type: text/plain; charset=UTF-8\n"
    +"Content-Transfer-Encoding: 8bit\n"
    +
    +#: ../album/album.c:269
    +msgid "Album"
    +msgstr ""
    +
    +#: ../album/album.c:270
    +msgid "Archives buddy icons."
    +msgstr ""
    +
    +#: ../album/album.c:271
    +msgid "Enable this plugin to automatically archive all buddy icons."
    +msgstr ""
    +
    +#: ../album/album-ui.c:300
    +msgid ""
    +"<span size='larger' weight='bold'>Unrecognized file type</span>\n"
    +"\n"
    +"Defaulting to PNG."
    +msgstr ""
    +
    +#: ../album/album-ui.c:316
    +#, c-format
    +msgid ""
    +"<span size='larger' weight='bold'>Error saving image</span>\n"
    +"\n"
    +"%s"
    +msgstr ""
    +
    +#: ../album/album-ui.c:352
    +msgid "Save Image"
    +msgstr ""
    +
    +#. Label
    +#: ../album/album-ui.c:612
    +#, c-format
    +msgid ""
    +"%x\n"
    +"%X"
    +msgstr ""
    +
    +#. Label
    +#: ../album/album-ui.c:721
    +msgid "No icons were found."
    +msgstr ""
    +
    +#: ../album/album-ui.c:964
    +#, c-format
    +msgid "Buddy Icons used by %s"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1021
    +#, c-format
    +msgid "Small (%1$ux%1$u)"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1025
    +#, c-format
    +msgid "Medium (%1$ux%1$u)"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1029
    +#, c-format
    +msgid "Large (%1$ux%1$u)"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1133
    +msgid "_Name"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1138
    +msgid "_Account"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1146
    +msgid "View Buddy Icons..."
    +msgstr ""
    +
    +#: ../album/album-ui.c:1148
    +msgid ""
    +"Please enter the screen name or alias of the person whose icon album you "
    +"want to view."
    +msgstr ""
    +
    +#: ../album/album-ui.c:1150 ../autoprofile/comp_logstats_gtk.c:133
    +msgid "OK"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1151 ../autoprofile/comp_logstats_gtk.c:134
    +#: ../autoprofile/comp_quotation.c:217 ../autoprofile/gtk_widget.c:322
    +#: ../groupmsg/groupmsg.c:112 ../timelog/timelog.c:130
    +msgid "Cancel"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1160
    +msgid "View Buddy Icons"
    +msgstr ""
    +
    +#: ../album/album-ui.c:1217
    +msgid "_View Buddy Icons"
    +msgstr ""
    +
    +#: ../autoprofile/autoaway.c:104 ../autoprofile/autoreply.c:281
    +msgid "This preference is disabled"
    +msgstr ""
    +
    +#: ../autoprofile/autoaway.c:105 ../autoprofile/autoreply.c:282
    +msgid ""
    +"This preference currently has no effect because AutoProfile is in use. To "
    +"modify this behavior, use the AutoProfile configuration menu."
    +msgstr ""
    +
    +#. type
    +#. ui_requirement
    +#. flags
    +#. dependencies
    +#. priority
    +#: ../autoprofile/autoprofile.c:142
    +msgid "gtk-kluge-autoprofile"
    +msgstr ""
    +
    +#. id
    +#: ../autoprofile/autoprofile.c:143
    +msgid "AutoProfile"
    +msgstr ""
    +
    +#. name
    +#. version
    +#: ../autoprofile/autoprofile.c:145
    +msgid "User profile and status message content generator"
    +msgstr ""
    +
    +#. summary
    +#. description
    +#: ../autoprofile/autoprofile.c:147
    +msgid ""
    +"Allows user to place dynamic text into profiles\n"
    +"and status messages, with the text automatically\n"
    +"updated whenever content changes"
    +msgstr ""
    +
    +#. author
    +#: ../autoprofile/autoprofile.c:151
    +msgid ""
    +"Casey Ho <casey at hkn-berkeley-edu>\n"
    +"\t\t\taim:caseyho"
    +msgstr ""
    +
    +#: ../autoprofile/autoprofile.c:153
    +msgid "http://autoprofile.sourceforge.net/"
    +msgstr ""
    +
    +#: ../autoprofile/autoprofile.c:834
    +msgid "Say the magic word if you want me to talk more!"
    +msgstr ""
    +
    +#: ../autoprofile/autoprofile.c:836
    +msgid "please"
    +msgstr ""
    +
    +#: ../autoprofile/autoprofile.c:844
    +msgid ""
    +"Get AutoProfile for Purple at <a href=\"http://autoprofile.sourceforge.net/"
    +"\">autoprofile.sourceforge.net</a><br><br>[Timestamp]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:278
    +msgid "Start/end time"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:284
    +msgid "Year: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:297
    +msgid "Month: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:310
    +msgid "Day: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:323
    +msgid "Hour: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:336
    +msgid "Minutes: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:349
    +msgid "Seconds: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:368
    +msgid "Which way"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:372
    +msgid "Count down to stop date"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:375
    +msgid "Count time since start date"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:382
    +msgid "Days"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:384
    +msgid "Hours"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:386
    +msgid "Minutes"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:388
    +msgid "Seconds"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:392
    +msgid "Largest units displayed"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:394
    +msgid "Smallest units displayed"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:428
    +msgid "Countdown timer"
    +msgstr ""
    +
    +#: ../autoprofile/comp_countdownup.c:429
    +msgid "Given a date, shows amount of time until it (or since it)"
    +msgstr ""
    +
    +#: ../autoprofile/comp_executable.c:50
    +msgid "[ERROR: command failed to execute]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_executable.c:125
    +msgid "Specify the command line you wish to execute"
    +msgstr ""
    +
    +#: ../autoprofile/comp_executable.c:146
    +msgid "Max characters to read from output: "
    +msgstr ""
    +
    +#: ../autoprofile/comp_executable.c:159
    +msgid "Command Line"
    +msgstr ""
    +
    +#: ../autoprofile/comp_executable.c:160
    +msgid "Reproduces standard output of running a program on the command line"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:39
    +msgid "[AutoProfile error: Invalid URL or no internet connection]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:53
    +msgid "[AutoProfile error: No URL specified]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:151
    +msgid "Select URL with source content"
    +msgstr ""
    +
    +#. Update Now!
    +#: ../autoprofile/comp_http.c:167
    +msgid "Fetch page now!"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:176 ../autoprofile/preferences.c:656
    +msgid "Delay"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:186
    +msgid "minutes between page fetches"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:194
    +msgid "Webpage"
    +msgstr ""
    +
    +#: ../autoprofile/comp_http.c:195
    +msgid "Data fetched from an internet URL using HTTP"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1021
    +msgid "logs"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1022
    +msgid "log"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1023
    +msgid "stat"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1024
    +msgid "stats"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1025
    +msgid "logstats"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1026
    +msgid "log statistics"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1032
    +msgid "Purple log statistics"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats.c:1033
    +msgid "Display various statistics about your message and system logs"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:123
    +#: ../autoprofile/comp_logstats_gtk.c:329
    +msgid "Alias"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:129
    +msgid "Add Alias"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:131
    +msgid "Type in the alias that you use"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:180
    +msgid "Aliases"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:180
    +msgid "What this list is for"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:181
    +msgid ""
    +"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."
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:233
    +msgid ""
    +"%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%"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:298
    +msgid "Add alias"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:302
    +msgid "Delete alias"
    +msgstr ""
    +
    +#: ../autoprofile/comp_logstats_gtk.c:306
    +msgid "?"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:112
    +msgid "Fortune files"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:113
    +msgid "A quick definition of a fortune file"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:114
    +msgid ""
    +"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 "
    +"theinternet."
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:136
    +msgid "Select fortune file to import quotes from"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:147
    +msgid "Interpret bracketed text (such as \"<br>\") as HTML tags"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:204 ../autoprofile/comp_quotation.c:240
    +msgid "Unable to edit quote"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:205 ../autoprofile/comp_quotation.c:241
    +#: ../autoprofile/comp_quotation.c:304
    +msgid "No quote is currently selected"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:213
    +msgid "Edit quote"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:216
    +msgid "Save"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:303
    +msgid "Unable to delete quote"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:323
    +msgid "Delete all quotes?"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:334
    +msgid "Delete all quotes"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:343
    +msgid "Import quotes from from fortune file"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:349
    +msgid "What is a fortune file?"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:430
    +msgid "Size"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:439 ../autoprofile/comp_quotation.c:592
    +msgid "Quotes"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:467
    +msgid "New quote"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:472
    +msgid "Edit"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:477 ../autoprofile/gtk_widget.c:396
    +msgid "Delete"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:482
    +msgid "More..."
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:494
    +msgid "Change quote every "
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:504
    +msgid "hours (0: always show a new quote)"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:507
    +msgid "Change quote now"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:535
    +msgid "[ERROR: no quotes available]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_quotation.c:593
    +msgid "Displays a quotation from a provided selection"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:52
    +msgid "[ERROR: Invalid entry number]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:57
    +msgid "[ERROR: No data, invalid URL/account?]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:65
    +msgid "[ERROR: Insufficient number of entries]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:356
    +msgid ""
    +"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"
    +"\n"
    +"Time 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%"
    +msgstr ""
    +
    +#. Dropdown
    +#: ../autoprofile/comp_rss.c:402
    +msgid "Xanga"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:404
    +msgid "LiveJournal"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:406
    +msgid "RSS 2.0"
    +msgstr ""
    +
    +#. Username/URL fields
    +#: ../autoprofile/comp_rss.c:417
    +msgid "Username:"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:419
    +msgid "URL of feed:"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:446
    +msgid "Minutes between checks for updates:"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:467
    +msgid "RSS / Blogs"
    +msgstr ""
    +
    +#: ../autoprofile/comp_rss.c:468
    +msgid "Information taken from an RSS feed (Xanga and LiveJournal capable)"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:45
    +msgid "[ERROR: File does not exist]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:49
    +msgid "[ERROR: Unable to open file]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:123
    +msgid "iTunes"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:123
    +msgid "Current song in iTunes"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:124
    +msgid ""
    +"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"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:135
    +msgid "XMMS"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:135
    +msgid "Current song in XMMS"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:136
    +#, c-format
    +msgid ""
    +"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"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:146
    +msgid "Windows Media Player"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:147
    +msgid "Current song in Windows Media Player"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:148
    +msgid ""
    +"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"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:155
    +msgid "iTunes/Winamp/Foobar/Apollo/QCD"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:156
    +msgid "Current song in iTunes/Winamp/Foobar/Apollo/QCD"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:157
    +msgid ""
    +"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."
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:174
    +msgid "Select text file with source content"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:195
    +msgid "Max characters to read from file:"
    +msgstr ""
    +
    +#. Windows
    +#: ../autoprofile/comp_textfile.c:202
    +msgid "Windows users: Play the current song in:"
    +msgstr ""
    +
    +#. *nix
    +#: ../autoprofile/comp_textfile.c:222
    +msgid "*nix users: Play the current song in:"
    +msgstr ""
    +
    +#. OS X
    +#: ../autoprofile/comp_textfile.c:235
    +msgid "OS X users: Play the current song in:"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:257
    +msgid "Text File / Songs"
    +msgstr ""
    +
    +#: ../autoprofile/comp_textfile.c:258
    +msgid ""
    +"Copies text from file that external programs (e.g. XMMS, Winamp, iTunes) can "
    +"modify on a regular basis"
    +msgstr ""
    +
    +#: ../autoprofile/comp_timestamp.c:102
    +msgid ""
    +"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%"
    +msgstr ""
    +
    +#: ../autoprofile/comp_timestamp.c:132
    +msgid "Timestamp"
    +msgstr ""
    +
    +#: ../autoprofile/comp_timestamp.c:133
    +msgid "Displays custom text showing when message was created"
    +msgstr ""
    +
    +#: ../autoprofile/comp_uptime.c:38
    +msgid "uptime"
    +msgstr ""
    +
    +#: ../autoprofile/comp_uptime.c:82
    +msgid "[ERROR: failed to execute uptime command]"
    +msgstr ""
    +
    +#: ../autoprofile/comp_uptime.c:89
    +msgid "Uptime"
    +msgstr ""
    +
    +#: ../autoprofile/comp_uptime.c:90
    +msgid "Show how long your computer has been running"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:49
    +msgid "Edit Profile Accounts"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:58
    +msgid ""
    +"<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."
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:161 ../autoprofile/gtk_widget.c:137
    +msgid "<b>Preview</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:165 ../autoprofile/gtk_widget.c:140
    +msgid "Refresh"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:192
    +msgid ""
    +"<b>Edit</b> (Drag widgets into profile / Use shift+enter to insert a new "
    +"line)"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:209
    +msgid "Revert"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:211
    +msgid "Save profile"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:269 ../autoprofile/gtk_actions.c:326
    +msgid "Edit Content"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:284
    +msgid "Widgets"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:286
    +msgid "Info/profile"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:328
    +msgid "Preferences"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_actions.c:330
    +msgid "Show summary"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:132
    +msgid "no updates made to profile"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:135
    +msgid "no updates made to status"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:151
    +msgid "waiting for new profile content"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:154
    +msgid "waiting for new status content"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:188
    +#, c-format
    +msgid "next profile update in %d seconds"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:191
    +#, c-format
    +msgid "next status update in %d seconds"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:238
    +msgid "AutoProfile Summary"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:260 ../schedule/pidgin-schedule.c:222
    +msgid "Time"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:265
    +msgid "Type"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:273
    +msgid "Text"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:311
    +msgid "Queue new messages while away"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:316
    +msgid "Play sounds while away"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:329
    +msgid "Hide summary now"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:368
    +msgid "<b>User profile</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:371
    +msgid "<b>Away message</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:374
    +msgid "<b>Available message</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:377
    +msgid "<b>Status message</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_away_msgs.c:380
    +msgid "<b>Other</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:171
    +msgid "<b>Configuration</b>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:177
    +msgid "No options available for this component"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:211
    +msgid "<b><u>Basic info</u></b><br>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:214
    +msgid ""
    +"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>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:220
    +msgid ""
    +"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>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:225
    +msgid ""
    +"<b>To edit your profile:</b> Use the \"Info/profile\" tab in this window.<br>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:230
    +msgid ""
    +"<b>To edit your available/away/status message:</b> Use the regular Purple "
    +"interface built into the bottom of the buddy list.<br><br>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:235
    +msgid "<b><u>Advanced Tips</u></b><br>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:238
    +msgid ""
    +"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>"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:268 ../autoprofile/gtk_widget.c:285
    +msgid "Unable to change name"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:269
    +msgid "The specified widget no longer exists."
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:286
    +msgid "The widget name you have specified is already in use."
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:318
    +msgid "Rename Widget"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:319
    +msgid "Enter a new name for this widget."
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:321 ../autoprofile/gtk_widget.c:390
    +msgid "Rename"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:385
    +msgid "New Widget"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:494
    +msgid "Widget"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:638
    +msgid "Select a widget type"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:644
    +msgid "Create widget"
    +msgstr ""
    +
    +#: ../autoprofile/gtk_widget.c:672
    +msgid "Widget type"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:47
    +#, c-format
    +msgid "<span weight=\"bold\" size=\"larger\">AutoProfile %s</span>"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:70
    +msgid ""
    +"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>"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:77
    +msgid "<u>DOCUMENTATION / HELP</u><br>"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:79
    +msgid ""
    +"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>"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:85
    +msgid "<br><u>ABOUT</u><br>"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:88
    +msgid "Developers"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:95
    +msgid "Contributors/Patchers"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:103
    +msgid "Website"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:296
    +msgid "Screen Name"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:319
    +msgid "AutoProfile sets user info"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:327
    +msgid "Protocol"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:490
    +msgid "Accounts that do not support user-specified profiles are not shown"
    +msgstr ""
    +
    +#. ---------- Update frequency ----------
    +#: ../autoprofile/preferences.c:530
    +msgid "Update frequency"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:535
    +msgid "Minimum number of seconds between updates"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:541
    +msgid ""
    +"WARNING: Using values below 60 seconds may increase the frequency\n"
    +"of rate limiting errors"
    +msgstr ""
    +
    +#. ----------- Auto-away stuff ------------
    +#: ../autoprofile/preferences.c:548
    +msgid "Auto-away"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:550
    +msgid "Change status when idle"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:555
    +msgid "Minutes before changing status:"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:563
    +msgid "Change status to:"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:629 ../autoprofile/preferences.c:724
    +msgid "General"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:631
    +msgid "Auto-reply:"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:633 ../autoreply/autoreply.c:407
    +msgid "Never"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:634
    +msgid "When away"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:635
    +msgid "When both away and idle"
    +msgstr ""
    +
    +#. ---------- Auto-responses ----------
    +#: ../autoprofile/preferences.c:642
    +msgid "Dynamic auto-responses"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:648
    +msgid "Allow users to request more auto-responses"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:660
    +msgid "seconds between auto-responses"
    +msgstr ""
    +
    +#. Auto-response message string
    +#: ../autoprofile/preferences.c:668
    +msgid "Message sent with first autoresponse:"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:679
    +msgid "Request trigger message:"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:726
    +msgid "User info/profiles"
    +msgstr ""
    +
    +#: ../autoprofile/preferences.c:728
    +msgid "Auto-reply"
    +msgstr ""
    +
    +#. XXX: There should be a way to reset to the default/account-default autoreply
    +#: ../autoreply/autoreply.c:231
    +#, c-format
    +msgid "Set autoreply message for %s"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:233
    +msgid "Set Autoreply Message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:234
    +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:238
    +msgid "_Save"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:239 ../listhandler/aim_blt_files.c:297
    +#: ../listhandler/aim_blt_files.c:461 ../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/purple_blist_xml.c:229
    +msgid "_Cancel"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:255
    +msgid "Set _Autoreply Message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:268
    +msgid "Autoreply message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:270
    +msgid "Turn off autoreply"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:375
    +msgid "Send autoreply messages when"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:379
    +msgid "When my account is _away"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:383
    +msgid "When my account is _idle"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:387
    +msgid "_Default reply"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:394
    +msgid ""
    +"Autoreply Prefix\n"
    +"(only when necessary)"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:398
    +msgid "Do not autoreply when invisible."
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:401
    +msgid "Status message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:405
    +msgid "Autoreply with status message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:409
    +msgid "Always when there is a status message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:411
    +msgid "Only when there's no autoreply message"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:416
    +msgid "Delay between autoreplies"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:420
    +msgid "_Minimum delay (mins)"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:424
    +msgid "Times to send autoreplies"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:428
    +msgid "Ma_ximum count"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:486
    +msgid "Autoreply"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:487
    +msgid "Autoreply for all the protocols"
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:488
    +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 "
    +"specific autoreply message for a particular buddy, right click on the buddy "
    +"in the buddy-list window. To set autoreply messages for some accounts, go to "
    +"the `Advanced' tab of the account edit dialog."
    +msgstr ""
    +
    +#: ../autoreply/autoreply.c:498
    +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:503
    +msgid "This is an autoreply: "
    +msgstr ""
    +
    +#: ../awaynotify/awaynotify.c:184
    +#, c-format
    +msgid "%s is away: %s"
    +msgstr ""
    +
    +#: ../awaynotify/awaynotify.c:214
    +#, c-format
    +msgid "%s is no longer away."
    +msgstr ""
    +
    +#: ../awaynotify/awaynotify.c:261
    +msgid "Away State Notification"
    +msgstr ""
    +
    +#: ../awaynotify/awaynotify.c:263
    +msgid ""
    +"Notifies in a conversation window when a buddy goes or returns from away"
    +msgstr ""
    +
    +#: ../bash/bash.c:97
    +msgid ""
    +"bash [n]: sends a link to a bash.org quote. Specify a number for n and it "
    +"will send a link to the quote with the specified number."
    +msgstr ""
    +
    +#: ../bash/bash.c:101
    +msgid ""
    +"qdb [n]: sends a link to a qdb.us quote. Specify a number for n and it will "
    +"send a link to the quite with the specified number."
    +msgstr ""
    +
    +#: ../bash/bash.c:161
    +msgid "bash.org"
    +msgstr ""
    +
    +#: ../bash/bash.c:163
    +msgid "Generates links for quotes at bash.org"
    +msgstr ""
    +
    +#: ../bash/bash.c:165
    +msgid ""
    +"Generates links for quotes at bash.org or allows the user to specify a "
    +"quote. Provides the /bash command."
    +msgstr ""
    +
    +#: ../bit/bit.c:116
    +#, c-format
    +msgid "Unable to locate the buddy icon cache directory %s"
    +msgstr ""
    +
    +#. buddy icon structs currently suck, I think
    +#. it's impossible to tell from a filename which buddy it's associated with
    +#. without going through every file, and the blist...
    +#. ... a huge hash type table *may help*, but I'd consider it highly inefficient
    +#. then again, some of the stuff in here ain't exactly a TGV either
    +#: ../bit/bit.c:118 ../bit/bit.c:127 ../bit/bit.c:172
    +msgid "Destroy Unused Icons"
    +msgstr ""
    +
    +#: ../bit/bit.c:118
    +msgid "Unable to locate"
    +msgstr ""
    +
    +#: ../bit/bit.c:125
    +#, c-format
    +msgid "Unable to read the buddy icon cache directory %s"
    +msgstr ""
    +
    +#: ../bit/bit.c:127
    +msgid "Unable to read"
    +msgstr ""
    +
    +#: ../bit/bit.c:176
    +msgid "Flush Buddy Icons"
    +msgstr ""
    +
    +#: ../bit/bit.c:180
    +msgid "Refresh Buddy Icons"
    +msgstr ""
    +
    +#: ../bit/bit.c:230
    +msgid "Buddy Icon Tools"
    +msgstr ""
    +
    +#: ../bit/bit.c:231
    +msgid "Tools to manipulate buddy icons. *DANGEROUS*"
    +msgstr ""
    +
    +#: ../bit/bit.c:232
    +msgid ""
    +"Whilst working on Purple 2.0.0, I found a need to destroy all my buddies' "
    +"buddy icons. There's nothing to do these functions in Purple, so here they "
    +"are. Completely, thoroughly untested."
    +msgstr ""
    +
    +#: ../blistops/blistops.c:230
    +msgid "Hide the buddy list when it is created"
    +msgstr ""
    +
    +#: ../blistops/blistops.c:234
    +msgid "Hide the menu in the buddy list window"
    +msgstr ""
    +
    +#: ../blistops/blistops.c:238
    +msgid "Stretch the buddyname if the buddy has no buddyicon."
    +msgstr ""
    +
    +#: ../blistops/blistops.c:242
    +msgid "Show email addresses for all the buddies."
    +msgstr ""
    +
    +#: ../blistops/blistops.c:299
    +msgid "Buddy List Options"
    +msgstr ""
    +
    +#: ../blistops/blistops.c:300 ../blistops/blistops.c:301
    +msgid "Gives extended options to the buddy list"
    +msgstr ""
    +
    +#: ../buddytime/buddytime.c:216
    +#, c-format
    +msgid "Remote Local Time: %s (%.4g hour behind)"
    +msgid_plural "Remote Local Time: %s (%.4g hours behind)"
    +msgstr[0] ""
    +msgstr[1] ""
    +
    +#: ../buddytime/buddytime.c:223
    +#, c-format
    +msgid "Remote Local Time: %s (%.4g hour ahead)"
    +msgid_plural "Remote Local Time: %s (%.4g hours ahead)"
    +msgstr[0] ""
    +msgstr[1] ""
    +
    +#: ../buddytime/buddytime.c:404
    +msgid "Failed to load the Buddy Timezone UI."
    +msgstr ""
    +
    +#: ../buddytime/buddytime.c:485
    +msgid "Buddy Time"
    +msgstr ""
    +
    +#: ../buddytime/buddytime.c:486 ../buddytime/buddytime.c:487
    +msgid "Quickly see the local time of a buddy"
    +msgstr ""
    +
    +#: ../buddytime/gtkbuddytime.c:76
    +#, c-format
    +msgid ""
    +"%s\n"
    +"<b>Local Time:</b> %s (%.4g hour behind)"
    +msgid_plural ""
    +"%s\n"
    +"<b>Local Time:</b> %s (%.4g hours behind)"
    +msgstr[0] ""
    +msgstr[1] ""
    +
    +#: ../buddytime/gtkbuddytime.c:83
    +#, c-format
    +msgid ""
    +"%s\n"
    +"<b>Local Time:</b> %s (%.4g hour ahead)"
    +msgid_plural ""
    +"%s\n"
    +"<b>Local Time:</b> %s (%.4g hours ahead)"
    +msgstr[0] ""
    +msgstr[1] ""
    +
    +#: ../buddytime/gtkbuddytime.c:145
    +msgid "Buddy Time (Pidgin UI)"
    +msgstr ""
    +
    +#: ../buddytime/gtkbuddytime.c:146 ../buddytime/gtkbuddytime.c:147
    +msgid "Pidgin user interface for the Buddy Time plugin."
    +msgstr ""
    +
    +#: ../chronic/chronic.c:103
    +msgid "Chronic"
    +msgstr ""
    +
    +#: ../chronic/chronic.c:104
    +msgid "Sound playing triggers"
    +msgstr ""
    +
    +#: ../chronic/chronic.c:105
    +msgid ""
    +"Allows buddies to remotely trigger sound playing in your instance of Purple "
    +"with {S &lt;sound&gt;. Inspired by #guifications channel resident "
    +"EvilDennisR and ancient versions of AOL. THIS PLUGIN IS NOT YET "
    +"FUNCTIONAL! IT IS USELESS!"
    +msgstr ""
    +
    +#: ../colorize/colorize.c:281
    +msgid "Colorize"
    +msgstr ""
    +
    +#: ../colorize/colorize.c:282
    +msgid "Colorizes outgoing message text."
    +msgstr ""
    +
    +#: ../colorize/colorize.c:283
    +msgid ""
    +"Colorizes outgoing message text to a gradient of specified starting and "
    +"ending RGB values."
    +msgstr ""
    +
    +#: ../common/gtk_template.c:84 ../common/purple_template.c:79
    +msgid "unnamed"
    +msgstr ""
    +
    +#: ../common/gtk_template.c:85 ../common/purple_template.c:80
    +msgid "summary"
    +msgstr ""
    +
    +#: ../common/gtk_template.c:86 ../common/purple_template.c:81
    +msgid "description"
    +msgstr ""
    +
    +#: ../convbadger/convbadger.c:201
    +msgid "Conversation Badger"
    +msgstr ""
    +
    +#: ../convbadger/convbadger.c:202 ../convbadger/convbadger.c:203
    +msgid "Badges conversations with the protocol icon."
    +msgstr ""
    +
    +#: ../dewysiwygification/dewysiwygification.c:100
    +msgid "DeWYSIWYGification Plugin"
    +msgstr ""
    +
    +#: ../dewysiwygification/dewysiwygification.c:102
    +msgid "Lets you type in HTML without it being escaped to entities."
    +msgstr ""
    +
    +#: ../dewysiwygification/dewysiwygification.c:103
    +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:310
    +msgid ""
    +"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"
    +msgstr ""
    +
    +#: ../dice/dice.c:379
    +msgid "Dice"
    +msgstr ""
    +
    +#: ../dice/dice.c:380
    +msgid "Rolls dice in a chat or im"
    +msgstr ""
    +
    +#: ../dice/dice.c:381
    +msgid ""
    +"Adds a command (/dice) to roll an arbitrary number of dice with an arbitrary "
    +"number of sides. Now supports dice notation! /help dice for details"
    +msgstr ""
    +
    +#: ../difftopic/difftopic.c:138
    +#, c-format
    +msgid "<BR>Topic changed from: <BR>%s<BR>To:<BR>%s"
    +msgstr ""
    +
    +#: ../difftopic/difftopic.c:219
    +msgid "DiffTopic"
    +msgstr ""
    +
    +#: ../difftopic/difftopic.c:220 ../difftopic/difftopic.c:221
    +msgid "Show the old topic when the topic in a chat room changes."
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:331
    +msgid "8ball: sends a random 8ball message"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:332
    +msgid "sgball: sends a random Stargate Ball message"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:333
    +msgid "fullcrap: sends random fooling blabber"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:334
    +msgid "bollocks: sends random middle-manager bollocks"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:415
    +msgid "Magic 8 Ball"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:416
    +msgid "Provides Magic 8-ball like functionality"
    +msgstr ""
    +
    +#: ../eight_ball/eight_ball.c:417
    +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 ""
    +
    +#. Print a header at the beginning of the log
    +#: ../enhancedhist/enhancedhist.c:242
    +#, c-format
    +msgid "<b>Conversation with %s on %s:</b><br>"
    +msgstr ""
    +
    +#. heading for the more general options
    +#: ../enhancedhist/enhancedhist.c:292
    +msgid "Display Options"
    +msgstr ""
    +
    +#. the integer pref for the number of logs to display
    +#: ../enhancedhist/enhancedhist.c:295
    +msgid "Number of previous conversations to display:"
    +msgstr ""
    +
    +#. the boolean preferences
    +#: ../enhancedhist/enhancedhist.c:299
    +msgid "Show dates with text"
    +msgstr ""
    +
    +#: ../enhancedhist/enhancedhist.c:300
    +msgid "Show logs for IMs"
    +msgstr ""
    +
    +#: ../enhancedhist/enhancedhist.c:301
    +msgid "Show logs for chats"
    +msgstr ""
    +
    +#. heading for the age limit options
    +#: ../enhancedhist/enhancedhist.c:304
    +msgid "Age Limit for Logs (0 to disable):"
    +msgstr ""
    +
    +#: ../enhancedhist/enhancedhist.c:407
    +msgid "Enhanced History"
    +msgstr ""
    +
    +#: ../enhancedhist/enhancedhist.c:408
    +msgid "An enhanced version of the history plugin."
    +msgstr ""
    +
    +#: ../enhancedhist/enhancedhist.c:409
    +msgid ""
    +"An enhanced versoin of the history plugin. Grants ability to select the "
    +"number of previous conversations to show instead of just one."
    +msgstr ""
    +
    +#: ../findip/findip.c:55
    +msgid "Looked up IP: 127.0.0.1\n"
    +msgstr ""
    +
    +#: ../findip/findip.c:59
    +msgid "Yo! What's your IP?"
    +msgstr ""
    +
    +#: ../findip/findip.c:78
    +msgid "Looking up the IP ...\n"
    +msgstr ""
    +
    +#: ../findip/findip.c:91 ../findip/findip.c:170
    +msgid "Find IP"
    +msgstr ""
    +
    +#: ../findip/findip.c:119
    +msgid "Notify the user that you are trying to get the IP"
    +msgstr ""
    +
    +#: ../findip/findip.c:171
    +msgid "Find the IP of a person in the buddylist."
    +msgstr ""
    +
    +#: ../findip/findip.c:172
    +msgid "Find the IP of a person in the buddylist. This doesn't really work."
    +msgstr ""
    +
    +#: ../flip/flip.c:60
    +msgid "Outputs the results of flipping a coin"
    +msgstr ""
    +
    +#: ../flip/flip.c:113
    +msgid "Coin Flip"
    +msgstr ""
    +
    +#: ../flip/flip.c:114
    +msgid "Flips a coin and outputs the result"
    +msgstr ""
    +
    +#: ../flip/flip.c:115
    +msgid ""
    +"Adds a command (/flip) to flip a coin and outputs the result in the active "
    +"conversation"
    +msgstr ""
    +
    +#: ../google/google.c:267
    +msgid "Returns the url for a Google I'm feeling lucky search"
    +msgstr ""
    +
    +#: ../google/google.c:320
    +msgid "Google"
    +msgstr ""
    +
    +#: ../google/google.c:321
    +msgid "Returns the url for a Google \"I'm feeling lucky\" search"
    +msgstr ""
    +
    +#. should be completely mad and see if user has only one buddy (not a chat)
    +#. * on the blist and pluralise if appropriate
    +#: ../gRIM/gRIM.c:277
    +msgid ""
    +"gRIM: rim your pals\n"
    +"/rim &lt;duration-in-secs&gt; &lt;filename&gt;"
    +msgstr ""
    +
    +#: ../gRIM/gRIM.c:344
    +msgid "gRIM"
    +msgstr ""
    +
    +#: ../gRIM/gRIM.c:345
    +msgid "A completely stupid and pointless plugin"
    +msgstr ""
    +
    +#: ../gRIM/gRIM.c:346
    +#, fuzzy
    +msgid ""
    +"Adds commands to annoy buddies with. Inspired by a dumb IRC convo and Red "
    +"Dwarf."
    +msgstr ""
    +"Adds a command (/rim) to annoy Bruces and Sheilas with. Inspired by a dumb "
    +"IRC convo and Red Dwarf."
    +
    +#: ../groupmsg/groupmsg.c:96
    +#, c-format
    +msgid "There are no buddies online in group %s"
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:104
    +#, c-format
    +msgid ""
    +"Your message will be sent to these buddies:\n"
    +"%s"
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:107
    +msgid "Spam"
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:108
    +msgid "Please enter the message to send"
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:111
    +msgid "Send"
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:183
    +msgid "Group IM"
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:184
    +msgid "Send an IM to a group of buddies."
    +msgstr ""
    +
    +#: ../groupmsg/groupmsg.c:185
    +msgid "Adds the option to send an IM to every online buddy in a group."
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:110 ../hideconv/hideconv.c:137
    +msgid "/Options"
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:118
    +msgid "_Hide Conversation"
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:124
    +msgid "Show Hidden Conversations"
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:237
    +msgid "Show All Hidden Conversations"
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:240
    +msgid "Hide All Conversations"
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:286
    +msgid "Hide Conversation"
    +msgstr ""
    +
    +#: ../hideconv/hideconv.c:287 ../hideconv/hideconv.c:288
    +msgid "Hide conversations without closing them."
    +msgstr ""
    +
    +#: ../highlight/highlight.c:74
    +msgid "Highlight History"
    +msgstr ""
    +
    +#: ../highlight/highlight.c:248
    +msgid ""
    +"/highlight history: shows the list of highlighted sentences from the "
    +"history.\n"
    +"/highlight clear: clears the history.\n"
    +"/highlight +&lt;word&gt;: adds &lt;word&gt; to the highlight word list for "
    +"this conversation only.\n"
    +"/highlight -&lt;word&gt;: removes &lt;word&gt; from the highlight word list "
    +"for this conversation only.\n"
    +msgstr ""
    +
    +#: ../highlight/highlight.c:276
    +msgid ""
    +"Words to highlight on\n"
    +"(separate words by space)"
    +msgstr ""
    +
    +#: ../highlight/highlight.c:333 ../nicksaid/nicksaid.c:574
    +msgid "Highlight"
    +msgstr ""
    +
    +#: ../highlight/highlight.c:334 ../highlight/highlight.c:335
    +msgid "Support for highlighting words."
    +msgstr ""
    +
    +#: ../ignorance/ignorance.c:391
    +#, c-format
    +msgid "Successfully removed %s from %s"
    +msgstr ""
    +
    +#: ../ignorance/ignorance.c:396
    +#, c-format
    +msgid "Unable to remove %s from %s\n"
    +msgstr ""
    +
    +#: ../ignorance/ignorance.c:485
    +#, c-format
    +msgid "Assigned user %s to %s"
    +msgstr ""
    +
    +#: ../ignorance/ignorance.c:489
    +#, c-format
    +msgid "Unable to assign user %s to %s - may already be there"
    +msgstr ""
    +
    +#: ../ignorance/ignorance.c:1202
    +msgid "Ignorance"
    +msgstr ""
    +
    +#: ../ignorance/ignorance.c:1204 ../ignorance/ignorance.c:1206
    +msgid ""
    +"Allows you to manage lists of users with various levels of allowable "
    +"activity."
    +msgstr ""
    +
    +#: ../ignorance/interface.c:78
    +msgid "Create new rule"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:85
    +msgid "Create new group"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:92
    +msgid "Save changes"
    +msgstr ""
    +
    +#. XXX: The stock-icon for levelDel doesn't show, because the text is
    +#. * set from callback.c. Can we do with just `Remove' for the text
    +#. * and not updating as the selection in the tree changes?
    +#.
    +#: ../ignorance/interface.c:103
    +msgid "Remove rule"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:117
    +msgid "Name: "
    +msgstr ""
    +
    +#: ../ignorance/interface.c:125
    +msgid "Filter: "
    +msgstr ""
    +
    +#: ../ignorance/interface.c:137
    +msgid "Enabled"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:143
    +msgid "Regular Expression"
    +msgstr ""
    +
    +#. repeat
    +#: ../ignorance/interface.c:148 ../xmmsremote/xmmsremote.c:412
    +msgid "Repeat"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:168 ../ignorance/interface.c:257
    +msgid "Filter"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:172 ../ignore/ignore.c:303
    +msgid "Ignore"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:176 ../schedule/pidgin-schedule.c:256
    +msgid "Send Message"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:185
    +msgid "Play sound"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:198
    +msgid "Browse"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:203
    +msgid "Execute command"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:212
    +msgid "Take action"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:226
    +msgid "IM Text"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:232
    +msgid "Chat Text"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:239
    +msgid "User names"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:245
    +msgid "Enter/Leave"
    +msgstr ""
    +
    +#: ../ignorance/interface.c:251
    +msgid "Invitations"
    +msgstr ""
    +
    +#: ../ignorance/support.c:105 ../ignorance/support.c:129
    +#, c-format
    +msgid "Couldn't find pixmap file: %s"
    +msgstr ""
    +
    +#: ../ignore/ignore.c:131
    +msgid "Ignore Rules"
    +msgstr ""
    +
    +#: ../ignore/ignore.c:131
    +msgid "The following are the current ignore rules"
    +msgstr ""
    +
    +#: ../ignore/ignore.c:132
    +msgid "(Dear God! You are not ignoring any one!)"
    +msgstr ""
    +
    +#: ../ignore/ignore.c:234
    +msgid ""
    +"ignore [-c] [+&lt;ignore&gt; -&lt;unignore&gt;]<br>Examples:<br> 'ignore "
    +"+StupidBot -NotABot' \t - (in a chat) Starts ignoring StupidBot, and removes "
    +"NotABot from ignore list.<br> 'ignore -c +AnotherBot' \t - (in a chat) "
    +"Starts ignoring AnotherBot, but only in chats.<br> 'ignore +' \t - (in an "
    +"IM) Starts ignoring this person.<br> 'ignore -' \t - (in an IM) Starts "
    +"unignoring this person.<br> 'ignore' \t - Lists the current ignore rules."
    +msgstr ""
    +
    +#: ../ignore/ignore.c:305
    +msgid ""
    +"Flexible plugin to selectively ignore people. Please do not use if you have "
    +"amnesia."
    +msgstr ""
    +
    +#: ../ignore/ignore.c:307
    +msgid ""
    +"Flexible plugin to selectively ignore people. See '/help ignore' for more "
    +"help.\n"
    +"Please do not use if you have amnesia."
    +msgstr ""
    +
    +#: ../infopane/infopane.c:141 ../infopane/infopane.c:253
    +msgid "Libpurple and Pidgin are too old!\n"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:142
    +msgid "Incompatible Plugin"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:143
    +msgid "You need to update Pidgin!"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:144
    +msgid ""
    +"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:174
    +msgid "Position of the infopane ('top', 'bottom' or 'none')"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:178
    +msgid "Show icon in the tabs"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:182
    +msgid "Always show the tab"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:242
    +msgid "Infopane Options"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:243 ../infopane/infopane.c:244
    +msgid "Allow customizing the details information in conversation windows."
    +msgstr ""
    +
    +#: ../infopane/infopane.c:255
    +msgid "Incompatible Plugin! - Check plugin details!"
    +msgstr ""
    +
    +#: ../infopane/infopane.c:256 ../infopane/infopane.c:257
    +msgid "This plugin is NOT compatible with this version of Pidgin!"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:964
    +msgid "You have been added to the access list."
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:966
    +#, c-format
    +msgid "You have been added to the access list with an access level of %s."
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1000
    +msgid "You have been removed from the access list."
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1109
    +msgid "NickServ Authentication Error"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1110
    +msgid "Error authenticating with NickServ"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1111 ../irchelper/irchelper.c:1137
    +#: ../irchelper/irchelper.c:1164
    +msgid "Check your password."
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1135
    +msgid "GameSurge Authentication Error"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1136
    +msgid "Error authenticating with AuthServ"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1162
    +msgid "QuakeNet Authentication Error"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1163
    +msgid "Error authenticating with Q"
    +msgstr ""
    +
    +#. Register protocol preferences.
    +#: ../irchelper/irchelper.c:1202
    +msgid "Auth name"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1205
    +msgid "Nick password"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1209
    +msgid "Disconnect ghosts (Duplicate nicknames)"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1213
    +msgid "Operator password"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1289
    +msgid "IRC Helper"
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1290
    +msgid "Handles the rough edges of the IRC protocol."
    +msgstr ""
    +
    +#: ../irchelper/irchelper.c:1291
    +msgid ""
    +"- Transparent authentication with a variety of services.\n"
    +"- Suppression of various useless messages"
    +msgstr ""
    +
    +#. specify our help string and register our command
    +#: ../irc-more/irc-more.c:227
    +msgid "notice target message: Send a notice to the specified target."
    +msgstr ""
    +
    +#. Alphabetize the option label strings
    +#: ../irc-more/irc-more.c:249
    +msgid "CTCP Version reply"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:252
    +msgid "Default Quit Message"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:255
    +msgid "Default Part Message"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:258
    +msgid "Set User Modes On Connect"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:261
    +msgid "Unset User Modes On Connect"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:283
    +msgid "Seconds to wait before rejoining"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:344
    +msgid "IRC More"
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:345
    +msgid "Adds additional IRC features."
    +msgstr ""
    +
    +#: ../irc-more/irc-more.c:346
    +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:80
    +#, c-format
    +msgid "Day changed to %s"
    +msgstr ""
    +
    +#: ../irssi/datechange.c:91
    +msgid "Happy New Year"
    +msgstr ""
    +
    +#: ../irssi/irssi.c:61
    +msgid "Enable Features:"
    +msgstr ""
    +
    +#: ../irssi/irssi.c:64
    +msgid "Text Formatting"
    +msgstr ""
    +
    +#: ../irssi/irssi.c:67
    +msgid "Date Change Notification"
    +msgstr ""
    +
    +#: ../irssi/irssi.c:70
    +msgid "Happy New Year Message"
    +msgstr ""
    +
    +#. set these here to allow for translations of the strings
    +#: ../irssi/irssi.c:130
    +msgid "Irssi Features"
    +msgstr ""
    +
    +#: ../irssi/irssi.c:131
    +msgid "Implements features of the irssi IRC client for use in Pidgin."
    +msgstr ""
    +
    +#: ../irssi/irssi.c:133
    +msgid ""
    +"Implements some features of the IRC client irssi to be used in Purple. It "
    +"lets you know in all open conversations when the day has changed, adds the "
    +"lastlog command, adds the window command, etc. The day changed message is "
    +"not logged."
    +msgstr ""
    +
    +#. XXX: This should probably be moved into outputting directly in the
    +#. * conversation window.
    +#.
    +#: ../irssi/lastlog.c:75
    +msgid "Lastlog"
    +msgstr ""
    +
    +#: ../irssi/lastlog.c:75
    +msgid "Lastlog output"
    +msgstr ""
    +
    +#. XXX: Translators: DO NOT TRANSLATE "lastlog" or the HTML tags below
    +#: ../irssi/lastlog.c:102
    +msgid ""
    +"<pre>lastlog &lt;string&gt;: Shows, from the current conversation's history, "
    +"all messages containing the word or words specified in string. It will be "
    +"an exact match, including whitespace and special characters."
    +msgstr ""
    +
    +#.
    +#. * XXX: Translators: DO NOT TRANSLATE the first "layout" or the "\nsave"
    +#. * or "reset" at the beginning of the last line below, or the HTML tags.
    +#.
    +#: ../irssi/layout.c:329
    +msgid ""
    +"<pre>layout &lt;save|reset&gt;: Remember the layout of the current "
    +"conversations to reopen them when Purple is restarted.\n"
    +"save - saves the current layout\n"
    +"reset - clears the current saved layout\n"
    +"</pre>"
    +msgstr ""
    +
    +#: ../irssi/window.c:73
    +msgid "Invalid window specified."
    +msgstr ""
    +
    +#: ../irssi/window.c:115
    +msgid "Invalid argument!"
    +msgstr ""
    +
    +#: ../irssi/window.c:120
    +msgid "Unknown Error!"
    +msgstr ""
    +
    +#.
    +#. * XXX: Translators: DO NOT TRANSLATE the first occurance of the word
    +#. * "window" below, or "close", "next", "previous", "left", or "right"
    +#. * at the *beginning* of the lines below! The options to /window are
    +#. * NOT going to be translatable. Also, please don't translate the HTML
    +#. * tags.
    +#.
    +#: ../irssi/window.c:148
    +msgid ""
    +"<pre>window &lt;option&gt;: Operations for windows (tabs). Valid options "
    +"are:\n"
    +"close - closes the current conversation\n"
    +"next - move to the next conversation\n"
    +"previous - move to the previous conversation\n"
    +"left - move one conversation to the left\n"
    +"right - move one conversation to the right\n"
    +"&lt;number&gt; - go to tab <number>\n"
    +"</pre>"
    +msgstr ""
    +
    +#. same thing as above, except for the /win command
    +#: ../irssi/window.c:164
    +msgid ""
    +"<pre>win: THis command is synonymous with /window. Try /help window for "
    +"further details.</pre>"
    +msgstr ""
    +
    +#. Last seen
    +#. Last said
    +#. Signed on
    +#. Signed off
    +#: ../lastseen/lastseen.c:160
    +msgid ""
    +"\n"
    +"<b>Last Seen</b>: "
    +msgstr ""
    +
    +#: ../lastseen/lastseen.c:161
    +msgid ""
    +"\n"
    +"<b>Last Said</b>: "
    +msgstr ""
    +
    +#: ../lastseen/lastseen.c:162
    +msgid ""
    +"\n"
    +"<b>Signed On</b>: "
    +msgstr ""
    +
    +#: ../lastseen/lastseen.c:163
    +msgid ""
    +"\n"
    +"<b>Signed Off</b>: "
    +msgstr ""
    +
    +#: ../lastseen/lastseen.c:241
    +msgid "Last Seen"
    +msgstr ""
    +
    +#: ../lastseen/lastseen.c:242
    +msgid "Record when a buddy was last seen."
    +msgstr ""
    +
    +#: ../lastseen/lastseen.c:243
    +msgid ""
    +"Logs the time of a last received message, what they said, when they logged "
    +"in, and when they logged out, for buddies on your buddy list."
    +msgstr ""
    +
    +#. create a field
    +#: ../listhandler/aim_blt_files.c:283 ../listhandler/aim_blt_files.c:449
    +#: ../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
    +#: ../listhandler/purple_blist_xml.c:221 ../schedule/pidgin-schedule.c:284
    +msgid "Account"
    +msgstr ""
    +
    +#. and finally we can create the request
    +#: ../listhandler/aim_blt_files.c:293
    +msgid "List Handler: Importing"
    +msgstr ""
    +
    +#: ../listhandler/aim_blt_files.c:294 ../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:295 ../listhandler/alias_xml_files.c:267
    +#: ../listhandler/gen_xml_files.c:161 ../listhandler/purple_blist_xml.c:228
    +msgid "_Import"
    +msgstr ""
    +
    +#: ../listhandler/aim_blt_files.c:420
    +msgid "Save AIM .blt File"
    +msgstr ""
    +
    +#. and finally we can create the request
    +#: ../listhandler/aim_blt_files.c:459
    +msgid "List Handler: Exporting"
    +msgstr ""
    +
    +#: ../listhandler/aim_blt_files.c:460 ../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:461 ../listhandler/alias_xml_files.c:210
    +#: ../listhandler/gen_xml_files.c:355
    +msgid "_Export"
    +msgstr ""
    +
    +#: ../listhandler/aim_blt_files.c:474
    +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/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 ""
    +
    +#: ../listhandler/listhandler.c:39
    +msgid "Copy Buddies From One Account to Another"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:43
    +msgid "Import Alias List File"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:47
    +msgid "Import AIM Buddy List File (.blt)"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:51
    +msgid "Import Generic Buddy List File (.xml)"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:55
    +msgid "Import A blist.xml From libpurple"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:59
    +msgid "Export AIM Buddy List File"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:63
    +msgid "Export Alias List File"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:67
    +msgid "Export Generic Buddy List File"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:114
    +msgid "List Handler"
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:116
    +msgid "Provides numerous user-requested list-handling capabilities."
    +msgstr ""
    +
    +#: ../listhandler/listhandler.c:119
    +msgid ""
    +"Provides numerous user-requested list-handling capabilities, such as "
    +"importing and exporting of AIM .blt files and generic protocol-agnostic XML ."
    +"blist files, as well as direct copying of buddies from one account to "
    +"another."
    +msgstr ""
    +
    +#. and finally we can create the request
    +#: ../listhandler/migrate.c:142 ../listhandler/migrate.c:181
    +msgid "Listhandler - Copying"
    +msgstr ""
    +
    +#: ../listhandler/migrate.c:143
    +msgid "Choose the account to add buddies to:"
    +msgstr ""
    +
    +#: ../listhandler/migrate.c:144 ../schedule/pidgin-schedule.c:576
    +msgid "_Add"
    +msgstr ""
    +
    +#: ../listhandler/migrate.c:182
    +msgid "Choose the account to copy from:"
    +msgstr ""
    +
    +#: ../listhandler/migrate.c:183
    +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 ""
    +
    +#: ../listlog/listlog.c:131
    +msgid "Chat User List Logging"
    +msgstr ""
    +
    +#: ../listlog/listlog.c:132 ../listlog/listlog.c:133
    +msgid "Logs the list of users present when you join a chat."
    +msgstr ""
    +
    +#: ../msglen/msglen.c:251
    +msgid "Message Length"
    +msgstr ""
    +
    +#: ../msglen/msglen.c:252
    +msgid "Shows the length of your current message in the menu tray"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:377
    +msgid "All"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:380 ../nicksaid/nicksaid.c:256
    +msgid "None"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:383
    +msgid "Out of sync ones"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:388
    +msgid "Toggle icon selectors"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:391
    +msgid "Toggle global selector"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:499
    +msgid "Hide global status selector"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:502
    +msgid "Hide icon-selectors"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:559
    +msgid "Mystatusbox (Show Statusboxes)"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:560
    +msgid "Hide/Show the per-account statusboxes"
    +msgstr ""
    +
    +#: ../mystatusbox/mystatusbox.c:561
    +msgid ""
    +"You can show all the per-account statusboxes, hide all of them, or just show "
    +"the ones that are in a different status from the global status. For ease of "
    +"use, you can bind keyboard shortcuts for the menu items."
    +msgstr ""
    +
    +#: ../napster/napster.c:286
    +msgid "Unable to read header from server"
    +msgstr ""
    +
    +#: ../napster/napster.c:300
    +#, c-format
    +msgid "Unable to read message from server: %s. Command is %hd, length is %hd."
    +msgstr ""
    +
    +#: ../napster/napster.c:316
    +msgid "Unknown server error."
    +msgstr ""
    +
    +#: ../napster/napster.c:365
    +#, c-format
    +msgid "users: %s, files: %s, size: %sGB"
    +msgstr ""
    +
    +#. MSG_SERVER_HOTLIST_ERROR
    +#: ../napster/napster.c:376
    +#, c-format
    +msgid "Unable to add \"%s\" to your Napster hotlist"
    +msgstr ""
    +
    +#. MSG_SERVER_DISCONNECTING
    +#. we have been kicked off =^(
    +#: ../napster/napster.c:383
    +msgid "You were disconnected from the server."
    +msgstr ""
    +
    +#. MSG_CLIENT_WHOIS
    +#: ../napster/napster.c:440
    +#, c-format
    +msgid "%s requested your information"
    +msgstr ""
    +
    +#: ../napster/napster.c:450
    +msgid "Napster User Info:"
    +msgstr ""
    +
    +#. MSG_SERVER_GHOST
    +#. Looks like someone logged in as us! =-O
    +#: ../napster/napster.c:478
    +msgid "You have signed on from another location."
    +msgstr ""
    +
    +#. MSG_CLIENT_PING
    +#: ../napster/napster.c:482
    +#, c-format
    +msgid "%s requested a PING"
    +msgstr ""
    +
    +#: ../napster/napster.c:529 ../napster/napster.c:565
    +msgid "Unable to connect."
    +msgstr ""
    +
    +#: ../napster/napster.c:558
    +msgid "Connecting"
    +msgstr ""
    +
    +#: ../napster/napster.c:614
    +msgid "_Group:"
    +msgstr ""
    +
    +#. *< type
    +#. *< ui_requirement
    +#. *< flags
    +#. *< dependencies
    +#. *< priority
    +#. *< id
    +#: ../napster/napster.c:715
    +msgid "Napster"
    +msgstr ""
    +
    +#. *< name
    +#. *< version
    +#. * summary
    +#. * description
    +#: ../napster/napster.c:718 ../napster/napster.c:720
    +msgid "NAPSTER Protocol Plugin"
    +msgstr ""
    +
    +#: ../napster/napster.c:747 ../snpp/snpp.c:586
    +msgid "Server"
    +msgstr ""
    +
    +#: ../napster/napster.c:750 ../snpp/snpp.c:589
    +msgid "Port"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:232 ../nicksaid/nicksaid.c:659
    +msgid "Nicksaid"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:232
    +msgid "List of highlighted messages:"
    +msgstr ""
    +
    +#. next
    +#: ../nicksaid/nicksaid.c:264 ../xmmsremote/xmmsremote.c:399
    +#: ../xmmsremote/xmmsremote.c:494
    +msgid "Next"
    +msgstr ""
    +
    +#. previous
    +#: ../nicksaid/nicksaid.c:269 ../xmmsremote/xmmsremote.c:404
    +#: ../xmmsremote/xmmsremote.c:514
    +msgid "Previous"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:294
    +msgid "Clear History"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:299
    +msgid "Show All"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:578
    +msgid ""
    +"_Words to highlight on\n"
    +"(separate the words with a blank space)"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:581
    +msgid "Number of displayed characters"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:585
    +msgid ""
    +"_Set the number of characters displayed\n"
    +"in the nicksaid menu"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:590
    +msgid "Display who said your name in the nicksaid menu"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:594
    +msgid "Display _timestamps in the nicksaid menu"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:598
    +msgid "_Display _datestamps in the nicksaid menu"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:602
    +msgid "Allow displaying in a separate dialog"
    +msgstr ""
    +
    +#: ../nicksaid/nicksaid.c:660 ../nicksaid/nicksaid.c:661
    +msgid "Record when someone said your nick in a chat."
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:52
    +msgid "There are no messages in the queue."
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:59
    +#, c-format
    +msgid "%d. %s"
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:145
    +#, c-format
    +msgid "Cancelled message to %s, they are currently mobile."
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:205
    +msgid "Delete failed: no message number given!"
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:213
    +#, c-format
    +msgid "Delete failed: no messaged numbered %d!"
    +msgstr ""
    +
    +#. commands
    +#: ../nomobility/nomobility.c:245
    +#, c-format
    +msgid ""
    +"%s &lt;[clear][clear][delete][send]&gt;\n"
    +"clear Clears all queued messages\n"
    +"delete # Deletes the message numbered #\n"
    +"list Lists all queued messages\n"
    +"sendall Sends all queued messages\n"
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:308
    +msgid "No Mobility"
    +msgstr ""
    +
    +#: ../nomobility/nomobility.c:309
    +msgid "Stops you from messaging mobile users"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:272
    +#, c-format
    +msgid "IM Sessions with %s\n"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:296
    +#, c-format
    +msgid "(%s) %s <AUTO-REPLY>: %s\n"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:389
    +#, c-format
    +msgid "IM Sessions with %s"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:423
    +#, c-format
    +msgid ""
    +"<FONT COLOR=\"#16569E\" sml=\"%s\">(%s) <B>%s &lt;AUTO-REPLY&gt;:</B></FONT> "
    +"%s<BR>\n"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:425
    +#, c-format
    +msgid ""
    +"<FONT COLOR=\"#A82F2F\" sml=\"%s\">(%s) <B>%s &lt;AUTO-REPLY&gt;:</B></FONT> "
    +"%s<BR>\n"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:452
    +msgid "Old plain text"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:457
    +msgid "Old HTML"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:515
    +msgid "Old Logger"
    +msgstr ""
    +
    +#: ../oldlogger/oldlogger.c:516 ../oldlogger/oldlogger.c:517
    +msgid "Re-implements the legacy, deficient, logging"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:228
    +msgid "Ignored Plonkers"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:230
    +msgid "Plonkers singular format:"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:233
    +msgid "Plonkers plural format:"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:237
    +msgid "Plonking"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:238
    +msgid "Plonked singular plural:"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:241
    +msgid "Plonked plural format:"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:247
    +msgid "Format information"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:252
    +msgid "%P: List of plonkers"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:255
    +msgid "%N: Number of plonkers"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:273
    +msgid ""
    +"<pre>plonkers;\n"
    +"Tell people in a chat what you really think of them\n"
    +"</pre>"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:342
    +msgid "/me has identified %N plonker: %P."
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:344
    +msgid "/me has identified %N plonkers: %P."
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:347 ../plonkers/plonkers.c:349
    +msgid "/me plonks: %P."
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:351
    +msgid "Plonkers"
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:352
    +msgid "Tell plonkers what you really think."
    +msgstr ""
    +
    +#: ../plonkers/plonkers.c:353
    +msgid ""
    +"Plonkers is a small plugin that lets you announce to a chat room your "
    +"current list of ignores, as well as providing other pointless ignore and "
    +"privacy tools for dealing with idiots. The name is inspired by the British/"
    +"Irish word for 'idiots.'"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:89
    +msgid "Schedule List"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:147
    +msgid "Every month"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:148
    +msgid "January"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:149
    +msgid "February"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:150
    +msgid "March"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:151
    +msgid "April"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:152
    +msgid "May"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:153
    +msgid "June"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:154
    +msgid "July"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:155
    +msgid "August"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:156
    +msgid "September"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:157
    +msgid "October"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:158
    +msgid "November"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:159
    +msgid "December"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:162 ../schedule/pidgin-schedule.c:218
    +msgid "Everyday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:163
    +msgid "Sunday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:164
    +msgid "Monday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:165
    +msgid "Tuesday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:166
    +msgid "Wednesday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:167
    +msgid "Thursday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:168
    +msgid "Friday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:169
    +msgid "Saturday"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:177
    +msgid "Select Date and Time"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:192
    +msgid "Month"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:199
    +msgid "Year"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:202
    +msgid "Every Year"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:206
    +msgid "Day"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:214
    +msgid "Date"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:266
    +msgid "_Send message to a friend"
    +msgstr ""
    +
    +#. XXX: set the formatting to default send-message format
    +#: ../schedule/pidgin-schedule.c:279
    +msgid "Buddy"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:288
    +msgid "Message"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:299
    +msgid "Popup Dialog"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:304
    +msgid "_Popup a reminder dialog with message"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:398
    +msgid "Name"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:580
    +msgid "_Delete"
    +msgstr ""
    +
    +#. XXX: submit the patch to Purple for making the mnemonics work
    +#: ../schedule/pidgin-schedule.c:645
    +msgid "New Schedule"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:648
    +msgid "List of Schedules"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:709 ../schedule/schedule.c:256
    +msgid "Schedule"
    +msgstr ""
    +
    +#: ../schedule/pidgin-schedule.c:710 ../schedule/pidgin-schedule.c:711
    +msgid "Schedule reminders at specified times."
    +msgstr ""
    +
    +#: ../schedule/schedule.c:379
    +msgid "list of schedules"
    +msgstr ""
    +
    +#: ../sepandtab/sepandtab.c:90
    +msgid "Separate IM, group Chats"
    +msgstr ""
    +
    +#: ../sepandtab/sepandtab.c:92
    +msgid "Separate Chats, group IMs"
    +msgstr ""
    +
    +#: ../sepandtab/sepandtab.c:95
    +msgid "Group by Type, Separate by Protocol"
    +msgstr ""
    +
    +#: ../sepandtab/sepandtab.c:150
    +msgid "Separate And Tab"
    +msgstr ""
    +
    +#: ../sepandtab/sepandtab.c:151
    +msgid "Adds two placement functions for separating and tabbing"
    +msgstr ""
    +
    +#: ../sepandtab/sepandtab.c:152
    +msgid ""
    +"Adds two new placement functions.\n"
    +"\n"
    +"One separates IMs and groups chats in tabs\n"
    +"The other separates chats and groups IMs in tabs"
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:72
    +msgid "Hide when offline"
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:75
    +msgid "Show when offline"
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:86
    +msgid "Plugin deprecated"
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:87
    +msgid "Show Offline plugin deprecated"
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:88
    +msgid ""
    +"This plugin has been deprecated as of Pidgin 2.3.0 which\n"
    +"includes the same functionality."
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:139
    +msgid "Show Offline"
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:140
    +msgid "Show specific buddies while offline."
    +msgstr ""
    +
    +#: ../showoffline/showoffline.c:141
    +msgid ""
    +"Adds the option to show specific buddies in your buddy list when they are "
    +"offline, even with \"Show Offline Buddies\" turned off."
    +msgstr ""
    +
    +#: ../simfix/simfix.c:134
    +msgid "SIM-fix"
    +msgstr ""
    +
    +#: ../simfix/simfix.c:135
    +msgid "Fix messages from broken SIM clients."
    +msgstr ""
    +
    +#: ../simfix/simfix.c:136
    +msgid ""
    +"Fixes messages received from broken SIM clients by stripping HTML from them. "
    +"The buddy must be on your list and set as a SIM user."
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:174
    +#, c-format
    +msgid "Unable to parse \"%s\""
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:185
    +#, c-format
    +msgid "Parse error message: %s"
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:215
    +#, c-format
    +msgid "Unable to execute \"%s\""
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:226
    +#, c-format
    +msgid "Execute error message: %s"
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:295
    +msgid "There was an error executing your command."
    +msgstr ""
    +
    +#. I really want to eventually make this cleaner, like by making it
    +#. * change the actual message that gets printed to the conv window...
    +#: ../slashexec/slashexec.c:362
    +#, c-format
    +msgid "The following text was sent: %s"
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:409
    +msgid ""
    +"exec [-o] &lt;command&gt;, runs the command.\n"
    +"If the -o flag is used then output is sent to thecurrent conversation; "
    +"otherwise it is printed to the current text box."
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:460
    +msgid "Execute commands starting with: "
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:464
    +msgid "/exec Command (/exec someCommand)"
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:468
    +msgid "Exclamation point (!someCommand)"
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:526
    +msgid "/exec a la UNIX IRC CLI"
    +msgstr ""
    +
    +#: ../slashexec/slashexec.c:527
    +msgid ""
    +"A plugin that adds the /exec command line interpreter like most UNIX/Linux "
    +"IRC clients have. Also included is the ability to execute commands with an "
    +"exclamation point (!uptime, for instance).\n"
    +msgstr ""
    +
    +#: ../smartear/gtksmartear.c:51
    +msgid "SmartEar Options"
    +msgstr ""
    +
    +#: ../smartear/gtksmartear.c:112
    +msgid "SmartEar"
    +msgstr ""
    +
    +#: ../smartear/gtksmartear.c:113
    +msgid "The GTK+ (Pidgin) component of the SmartEar plugin suite"
    +msgstr ""
    +
    +#: ../smartear/gtksmartear.c:114
    +msgid ""
    +"This plugin provides the Pidgin interface to the SmartEar plugin suite's "
    +"functionality. The suite allows you to specify sounds per-buddy, per-"
    +"contact, or per-group for specific events."
    +msgstr ""
    +
    +#: ../smartear/smartear.c:261
    +msgid "Smart Ear - Hidden Core Plugin"
    +msgstr ""
    +
    +#: ../smartear/smartear.c:262 ../smartear/smartear.c:263
    +msgid "The Core component of the Smart Ear plugins"
    +msgstr ""
    +
    +#: ../snpp/snpp.c:353
    +msgid "Couldn't connect to SNPP server"
    +msgstr ""
    +
    +#: ../snpp/snpp.c:594
    +msgid "SNPP"
    +msgstr ""
    +
    +#: ../snpp/snpp.c:595
    +msgid "SNPP Plugin"
    +msgstr ""
    +
    +#: ../snpp/snpp.c:597
    +msgid ""
    +"Allows libpurple to send messages over the Simple Network Paging Protocol "
    +"(SNPP)."
    +msgstr ""
    +
    +#: ../splitter/splitter.c:181
    +msgid "Unable to send message: The message is too large."
    +msgstr ""
    +
    +#: ../splitter/splitter.c:184 ../splitter/splitter.c:197
    +#, c-format
    +msgid "Unable to send message to %s."
    +msgstr ""
    +
    +#: ../splitter/splitter.c:185
    +msgid "The message is too large."
    +msgstr ""
    +
    +#: ../splitter/splitter.c:194
    +msgid "Unable to send message."
    +msgstr ""
    +
    +#: ../splitter/splitter.c:553
    +msgid "Message Splitter"
    +msgstr ""
    +
    +#: ../splitter/splitter.c:554
    +msgid ""
    +"Splits a large outgoing message into smaller messages of a specified size."
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:67
    +#, c-format
    +msgid "<b>Name:</b> %s<br>"
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:74
    +#, c-format
    +msgid "<b>Version:</b> %s<br>"
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:81
    +#, c-format
    +msgid "<b>Author:</b> %s<br>"
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:86 ../sslinfo/sslinfo.c:142
    +msgid "SSL Info"
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:97
    +msgid "Get SSL info"
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:143
    +msgid "Displays info about your currently loaded SSL plugin"
    +msgstr ""
    +
    +#: ../sslinfo/sslinfo.c:144
    +msgid "Displays info about your currently loaded SSL plugin."
    +msgstr ""
    +
    +#: ../stocker/stocker.c:403
    +msgid "Stocker"
    +msgstr ""
    +
    +#: ../stocker/stocker.c:404
    +msgid "A stock ticker"
    +msgstr ""
    +
    +#: ../stocker/stocker.c:406
    +msgid ""
    +"Adds a stock ticker similar to the one in the Windows AIM client to the "
    +"bottom of the buddy list."
    +msgstr ""
    +
    +#. *********************************
    +#. * symbols frame
    +#. ********************************
    +#: ../stocker/stocker_prefs.c:240
    +msgid "Symbols"
    +msgstr ""
    +
    +#: ../stocker/stocker_prefs.c:252
    +msgid "Symbol:"
    +msgstr ""
    +
    +#. *********************************
    +#. * options frame
    +#. ********************************
    +#: ../stocker/stocker_prefs.c:314
    +msgid "Options"
    +msgstr ""
    +
    +#: ../switchspell/switchspell.c:154
    +msgid "Spe_ll Check"
    +msgstr ""
    +
    +#: ../switchspell/switchspell.c:357
    +msgid "Switch Spell"
    +msgstr ""
    +
    +#: ../switchspell/switchspell.c:358 ../switchspell/switchspell.c:359
    +msgid "Switch Spell Checker Language"
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:172
    +msgid "_Talkfilters"
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:185 ../talkfilters/talkfilters.c:345
    +msgid "(None)"
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:339
    +msgid "Talk Filters"
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:342
    +msgid "Active filter:"
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:403
    +msgid "GNU Talk Filters"
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:405
    +msgid "Translates text in outgoing messages into humorous dialects."
    +msgstr ""
    +
    +#: ../talkfilters/talkfilters.c:407
    +msgid ""
    +"The GNU Talk Filters are filter programs that convert ordinary English text "
    +"into text that mimics a stereotyped or otherwise humorous dialect. These "
    +"filters have been in the public domain for many years, and have been made "
    +"available as a single integrated package. The filters include austro, b1ff, "
    +"brooklyn, chef, cockney, drawl, dubya, fudd, funetak, jethro, jive, kraut, "
    +"pansy, pirate, postmodern, redneck, valspeak, and warez."
    +msgstr ""
    +
    +#: ../timelog/log-widget.c:226
    +#, c-format
    +msgid "Conversation in %s on %s"
    +msgstr ""
    +
    +#: ../timelog/log-widget.c:228
    +#, c-format
    +msgid "Conversation with %s on %s"
    +msgstr ""
    +
    +#. No logs were found.
    +#: ../timelog/log-widget.c:290
    +msgid "No logs were found"
    +msgstr ""
    +
    +#: ../timelog/range-widget.c:231
    +msgid "Start Time"
    +msgstr ""
    +
    +#: ../timelog/range-widget.c:235
    +msgid "End Time"
    +msgstr ""
    +
    +#: ../timelog/range-widget.c:335
    +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
    +#. * desc
    +#: ../timelog/timelog.c:171 ../timelog/timelog.c:173
    +msgid "Allows the viewing of Pidgin logs within a specific time range"
    +msgstr ""
    +
    +#: ../timelog/timelog.h:27
    +msgid "TimeLog"
    +msgstr ""
    +
    +#: ../xchat-chats/xchat-chats.c:490
    +msgid "XChat Chats"
    +msgstr ""
    +
    +#: ../xchat-chats/xchat-chats.c:491
    +msgid "XChat-like chats with Pidgin"
    +msgstr ""
    +
    +#: ../xchat-chats/xchat-chats.c:492
    +msgid "You can chat in Pidgin using XChat's indented view."
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:368
    +msgid "Please start XMMS"
    +msgstr ""
    +
    +#. play
    +#: ../xmmsremote/xmmsremote.c:376 ../xmmsremote/xmmsremote.c:509
    +msgid "Play"
    +msgstr ""
    +
    +#. pause
    +#: ../xmmsremote/xmmsremote.c:383 ../xmmsremote/xmmsremote.c:504
    +msgid "Pause"
    +msgstr ""
    +
    +#. stop
    +#: ../xmmsremote/xmmsremote.c:392 ../xmmsremote/xmmsremote.c:499
    +msgid "Stop"
    +msgstr ""
    +
    +#. shuffle
    +#: ../xmmsremote/xmmsremote.c:416
    +msgid "Shuffle"
    +msgstr ""
    +
    +#. playlist
    +#: ../xmmsremote/xmmsremote.c:424
    +msgid "Playlist"
    +msgstr ""
    +
    +#. title
    +#: ../xmmsremote/xmmsremote.c:434
    +msgid "Display title"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:484 ../xmmsremote/xmmsremote.c:994
    +msgid "XMMS Remote Control Options"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:527
    +msgid "XMMS Volume Control"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:623 ../xmmsremote/xmmsremote.c:1145
    +msgid "XMMS Remote Control"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:753
    +msgid "XMMS is not running"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:785
    +msgid "unknown argument"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:819
    +msgid "Info"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:821
    +msgid "Info Format:"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:831
    +msgid "%T: Song title"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:834
    +#, c-format
    +msgid "%C: Number of channels"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:841
    +msgid "%P: Current song playlist number"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:844
    +msgid "%L: Total songs in the playlist"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:851
    +msgid "%t: Total time"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:854
    +#, c-format
    +msgid "%e: Elapsed time"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:861
    +msgid "%r: Remaining time"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:864
    +msgid "%V: Current volume"
    +msgstr ""
    +
    +#: ../xmmsremote/xmmsremote.c:871
    +#, c-format
    +msgid "%f: Frequency in Hz"
    +msgstr ""