pidgin/purple-plugin-pack

merge of '1e18671feafd8f1a223c5f7d2264f23846304991'
org.guifications.plugins
2007-10-22, rlaager
a287983b15f6
merge of '1e18671feafd8f1a223c5f7d2264f23846304991'
and '232732c662117f3311e7e111493f92ceeddbfd85'
--- a/.mtn-ignore Mon Oct 22 04:40:46 2007 -0400
+++ b/.mtn-ignore Mon Oct 22 05:00:47 2007 -0400
@@ -4,7 +4,9 @@
.+\.spec
.*\.dll
aclocal.m4
-config.(guess|log|status|sub)
+buddytime/gtktimezonetest
+buddytime/recursetest
+config.(cache|guess|log|status|sub)
configure
depcomp
install-sh
@@ -14,6 +16,8 @@
ltmain.sh
missing
po/Makefile.in.in
+po/missing
+po/notexist
po/POTFILES
po/stamp-it
po/.+\.pot
--- a/AUTHORS Mon Oct 22 04:40:46 2007 -0400
+++ b/AUTHORS Mon Oct 22 05:00:47 2007 -0400
@@ -9,6 +9,7 @@
Daniel Atallah <datallah@users.sourceforge.net>
Sadrul H Chowdhury <sadrul@users.sourceforge.net>
Richard Laager <rlaager@guifications.org>
+Martijn van Oosterhout <kleptog@svana.org>
Translators
===========
--- a/ChangeLog Mon Oct 22 04:40:46 2007 -0400
+++ b/ChangeLog Mon Oct 22 05:00:47 2007 -0400
@@ -26,6 +26,8 @@
builds. Pidgin will have persistent conversations soon.
* Added dewysiwygification plugin
* Added timelog plugin, from Jon Oberheide's gaim-timelog
+ * Merged buddytimezone from the buddytools package into the existing
+ (incomplete) buddytime plugin
Version 2.1.1: 8/19/07
* Fixed lack of .build, .pidgin-plugin, and Makefile.mingw for convbadger
--- a/buddytime/Makefile.am Mon Oct 22 04:40:46 2007 -0400
+++ b/buddytime/Makefile.am Mon Oct 22 05:00:47 2007 -0400
@@ -1,23 +1,45 @@
-EXTRA_DIST = .incomplete .pidgin-plugin
+EXTRA_DIST = .incomplete .purple-plugin .pidgin-plugin
+
+noinst_PROGRAMS = recursetest
-buddytimedir = $(PIDGIN_LIBDIR)
-
-buddytime_la_LDFLAGS = -module -avoid-version
+recursetest_SOURCES = \
+ recurse.c \
+ recursetest.c
if HAVE_PIDGIN
-buddytime_LTLIBRARIES = buddytime.la
+noinst_PROGRAMS += gtktimezonetest
+
+gtktimezonetest_SOURCES = \
+ gtktimezone.c \
+ gtktimezonetest.c \
+ recurse.c
-buddytime_la_SOURCES = \
- buddytime.c
+gtktimezonetest_LDADD = \
+ $(GTK_LIBS)
-buddytime_la_LIBADD = \
+gtkbuddytimedir = $(PIDGIN_LIBDIR)
+gtkbuddytime_la_LDFLAGS = -module -avoid-version
+gtkbuddytime_LTLIBRARIES = gtkbuddytime.la
+gtkbuddytime_la_SOURCES = \
+ gtkbuddytime.c
+gtkbuddytime_la_LIBADD = \
$(PIDGIN_LIBS) \
$(GLIB_LIBS) \
$(GTK_LIBS)
endif
+buddytimedir = $(PURPLE_LIBDIR)
+buddytime_la_LDFLAGS = -module -avoid-version
+buddytime_LTLIBRARIES = buddytime.la
+buddytime_la_SOURCES = \
+ buddytime.c
+buddytime_la_LIBADD = \
+ $(PURPLE_LIBS) \
+ $(GLIB_LIBS)
+
+
AM_CPPFLAGS = \
-DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
-DDATADIR=\"$(PIDGIN_DATADIR)\" \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddyedit.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,429 @@
+/*************************************************************************
+ * Buddy Edit Module
+ *
+ * A Gaim plugin that adds an edit to to buddies allowing you to change
+ * various details you can't normally change. It also provides a mechanism
+ * for subsequent plugins to add themselves to that dialog.
+ *
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Some code copyright (C) 2006, Richard Laager <rlaager@users.sf.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *************************************************************************/
+
+#define GAIM_PLUGINS
+#define PLUGIN "core-kleptog-buddyedit"
+
+#include <glib.h>
+#include <string.h>
+
+#include "gaim-compat.h"
+
+#include "notify.h"
+#include "plugin.h"
+#include "util.h"
+#include "version.h"
+
+#include "debug.h" /* Debug output functions */
+#include "request.h" /* Requests stuff */
+
+static GaimPlugin *plugin_self;
+
+static void
+buddyedit_editcomplete_cb(GaimBlistNode * data, GaimRequestFields * fields)
+{
+ gboolean blist_destroy = FALSE;
+
+ GaimBlistNode *olddata = data; /* Keep pointer in case we need to destroy it */
+
+ /* Account detail stuff */
+ switch (data->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ {
+ GaimBuddy *buddy = (GaimBuddy *) data;
+ GaimAccount *account = gaim_request_fields_get_account(fields, "account");
+ const char *name = gaim_request_fields_get_string(fields, "name");
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+
+ /* If any details changes, create the buddy */
+ if((account != buddy->account) || strcmp(name, buddy->name))
+ {
+ GHashTable *tmp;
+ GaimBuddy *newbuddy = gaim_buddy_new(account, name, alias);
+ gaim_blist_add_buddy(newbuddy, NULL, NULL, data); /* Copy it to correct location */
+
+ /* Now this is ugly, but we want to copy the settings and avoid issues with memory management */
+ tmp = ((GaimBlistNode *) buddy)->settings;
+ ((GaimBlistNode *) buddy)->settings = ((GaimBlistNode *) newbuddy)->settings;
+ ((GaimBlistNode *) newbuddy)->settings = tmp;
+
+ blist_destroy = TRUE;
+ data = (GaimBlistNode *) newbuddy;
+ }
+ else
+ gaim_blist_alias_buddy(buddy, alias);
+ break;
+ }
+ case GAIM_BLIST_CONTACT_NODE:
+ {
+ GaimContact *contact = (GaimContact *) data;
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+ gaim_contact_set_alias(contact, alias);
+ break;
+ }
+ case GAIM_BLIST_GROUP_NODE:
+ {
+ GaimGroup *group = (GaimGroup *) data;
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+ gaim_blist_rename_group(group, alias);
+ break;
+ }
+ case GAIM_BLIST_CHAT_NODE:
+ {
+ GaimChat *chat = (GaimChat *) data;
+ gboolean new_chat = FALSE;
+
+ GaimConnection *gc;
+ GList *list = NULL, *tmp;
+ gc = gaim_account_get_connection(chat->account);
+ if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
+ list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
+
+ GaimAccount *newaccount = gaim_request_fields_get_account(fields, "account");
+
+ /* In Gaim2 each prot_chat_entry has a field "required". We use
+ * this to determine if a field is important enough to recreate
+ * the chat if it changes. Non-required fields we jsut change
+ * in-situ. In Gaim1.5 this field doesn't exist so we always
+ * recreate */
+#if GAIM_MAJOR_VERSION >= 2
+ if(newaccount != chat->account)
+ new_chat = TRUE;
+ else
+ {
+ const char *oldvalue, *newvalue;
+
+ for (tmp = g_list_first(list); tmp && !new_chat; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+ if(!pce->required) /* Only checking required fields at this point */
+ continue;
+ if(pce->is_int)
+ continue; /* Not yet */
+ oldvalue = g_hash_table_lookup(chat->components, pce->identifier);
+ newvalue = gaim_request_fields_get_string(fields, pce->identifier);
+
+ if(oldvalue == NULL)
+ oldvalue = "";
+ if(newvalue == NULL)
+ newvalue = "";
+ if(strcmp(oldvalue, newvalue) != 0)
+ new_chat = TRUE;
+ }
+ }
+#else
+ new_chat = TRUE;
+#endif
+
+ if(new_chat)
+ {
+ const char *oldvalue, *newvalue;
+ GHashTable *components =
+ g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+
+ for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+
+ if(pce->is_int)
+ {
+ oldvalue = g_hash_table_lookup(chat->components, pce->identifier);
+ g_hash_table_replace(components, g_strdup(pce->identifier),
+ g_strdup(oldvalue));
+ }
+ else
+ {
+ newvalue = gaim_request_fields_get_string(fields, pce->identifier);
+ g_hash_table_replace(components, g_strdup(pce->identifier),
+ g_strdup(newvalue));
+ }
+ }
+ GaimChat *newchat = gaim_chat_new(newaccount, NULL, components);
+ gaim_blist_add_chat(newchat, NULL, data); /* Copy it to correct location */
+ data = (GaimBlistNode *) newchat;
+ blist_destroy = TRUE;
+ }
+ else /* Just updating values in old chat */
+ {
+ const char *newvalue;
+ for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+#if GAIM_MAJOR_VERSION >= 2
+ if(pce->required)
+ continue;
+#endif
+ if(pce->is_int)
+ {
+ /* Do nothing, yet */
+ }
+ else
+ {
+ newvalue = gaim_request_fields_get_string(fields, pce->identifier);
+ g_hash_table_replace(chat->components, g_strdup(pce->identifier),
+ g_strdup(newvalue));
+ }
+ }
+ }
+
+ const char *alias = gaim_request_fields_get_string(fields, "alias");
+ gaim_blist_alias_chat(chat, alias);
+ break;
+ }
+ case GAIM_BLIST_OTHER_NODE:
+ break;
+ }
+
+ gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-submit-fields", fields, data);
+
+ if(blist_destroy)
+ {
+ if(olddata->type == GAIM_BLIST_BUDDY_NODE)
+ gaim_blist_remove_buddy((GaimBuddy *) olddata);
+ else if(olddata->type == GAIM_BLIST_CHAT_NODE)
+ gaim_blist_remove_chat((GaimChat *) olddata);
+ }
+
+ /* Save any changes */
+ gaim_blist_schedule_save();
+}
+
+static GaimAccount *buddyedit_account_filter_func_data;
+
+static gboolean
+buddyedit_account_filter_func(GaimAccount * account)
+{
+ GaimPluginProtocolInfo *gppi1 =
+ GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(account)->prpl);
+ GaimPluginProtocolInfo *gppi2 =
+ GAIM_PLUGIN_PROTOCOL_INFO(gaim_account_get_connection(buddyedit_account_filter_func_data)->
+ prpl);
+
+ return gppi1 == gppi2;
+}
+
+/* Node is either a contact or a buddy */
+static void
+buddy_edit_cb(GaimBlistNode * node, gpointer data)
+{
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN, "buddy_edit_cb(%p)\n", node);
+ GaimRequestFields *fields;
+ GaimRequestField *field;
+ GaimRequestFieldGroup *group;
+ char *request_title = NULL;
+
+ fields = gaim_request_fields_new();
+
+ switch (node->type)
+ {
+ case GAIM_BLIST_BUDDY_NODE:
+ {
+ GaimBuddy *buddy = (GaimBuddy *) node;
+ group = gaim_request_field_group_new("Buddy Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_account_new("account", "Account", buddy->account);
+ gaim_request_field_account_set_show_all(field, TRUE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_string_new("name", "Name", buddy->name, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ field = gaim_request_field_string_new("alias", "Alias", buddy->alias, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Buddy";
+ break;
+ }
+ case GAIM_BLIST_CONTACT_NODE:
+ {
+ GaimContact *contact = (GaimContact *) node;
+ group = gaim_request_field_group_new("Contact Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_string_new("alias", "Alias", contact->alias, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Contact";
+ break;
+ }
+ case GAIM_BLIST_GROUP_NODE:
+ {
+ GaimGroup *grp = (GaimGroup *) node;
+ group = gaim_request_field_group_new("Group Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_string_new("alias", "Name", grp->name, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Group";
+ break;
+ }
+ case GAIM_BLIST_CHAT_NODE:
+ {
+ GaimChat *chat = (GaimChat *) node;
+ group = gaim_request_field_group_new("Chat Details");
+ gaim_request_fields_add_group(fields, group);
+
+ field = gaim_request_field_account_new("account", "Account", chat->account);
+ gaim_request_field_account_set_filter(field, buddyedit_account_filter_func);
+ buddyedit_account_filter_func_data = chat->account;
+ gaim_request_field_group_add_field(group, field);
+ GaimConnection *gc;
+ GList *list = NULL, *tmp;
+ gc = gaim_account_get_connection(chat->account);
+ if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL)
+ list = GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info(gc);
+
+ for (tmp = g_list_first(list); tmp; tmp = g_list_next(tmp))
+ {
+ struct proto_chat_entry *pce = tmp->data;
+ const char *value;
+#if GAIM_MAJOR_VERSION >= 2
+ gaim_debug(GAIM_DEBUG_INFO, PLUGIN,
+ "identifier=%s, label=%s, is_int=%d, required=%d\n", pce->identifier,
+ pce->label, pce->is_int, pce->required);
+#endif
+ if(pce->is_int)
+ continue; /* Not yet */
+ value = g_hash_table_lookup(chat->components, pce->identifier);
+ field = gaim_request_field_string_new(pce->identifier, pce->label, value, FALSE);
+#if GAIM_MAJOR_VERSION >= 2
+ gaim_request_field_set_required(field, pce->required);
+#endif
+ gaim_request_field_group_add_field(group, field);
+ }
+ field = gaim_request_field_string_new("alias", "Alias", chat->alias, FALSE);
+ gaim_request_field_group_add_field(group, field);
+
+ request_title = "Edit Chat";
+ break;
+ }
+ default:
+ request_title = "Edit";
+ break;
+ }
+
+ gaim_signal_emit(gaim_blist_get_handle(), PLUGIN "-create-fields", fields, node);
+
+ gaim_request_fields(plugin_self,
+ request_title, NULL, NULL,
+ fields, "OK", G_CALLBACK(buddyedit_editcomplete_cb), "Cancel", NULL, node);
+}
+
+static void
+buddy_menu_cb(GaimBlistNode * node, GList ** menu, void *data)
+{
+#if GAIM_MAJOR_VERSION < 2
+ GaimBlistNodeAction *action;
+#else
+ GaimMenuAction *action;
+#endif
+
+ switch (node->type)
+ {
+ /* These are the types we handle */
+ case GAIM_BLIST_BUDDY_NODE:
+ case GAIM_BLIST_CONTACT_NODE:
+ case GAIM_BLIST_GROUP_NODE:
+ case GAIM_BLIST_CHAT_NODE:
+ break;
+
+ case GAIM_BLIST_OTHER_NODE:
+ default:
+ return;
+ }
+
+#if GAIM_MAJOR_VERSION < 2
+ action = gaim_blist_node_action_new("Edit...", buddy_edit_cb, NULL);
+#else
+ action = gaim_menu_action_new("Edit...", GAIM_CALLBACK(buddy_edit_cb), NULL, NULL);
+#endif
+ *menu = g_list_append(*menu, action);
+}
+
+static gboolean
+plugin_load(GaimPlugin * plugin)
+{
+
+ plugin_self = plugin;
+
+ void *blist_handle = gaim_blist_get_handle();
+
+ gaim_signal_register(blist_handle, PLUGIN "-create-fields", /* Called when about to create dialog */
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST));
+
+ gaim_signal_register(blist_handle, PLUGIN "-submit-fields", /* Called when dialog submitted */
+ gaim_marshal_VOID__POINTER_POINTER, NULL, 2, /* (FieldList*,BlistNode*) */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_TYPE_POINTER), /* FieldList */
+ gaim_value_new(GAIM_TYPE_SUBTYPE, GAIM_SUBTYPE_BLIST));
+
+ gaim_signal_connect(blist_handle, "blist-node-extended-menu", plugin,
+ GAIM_CALLBACK(buddy_menu_cb), NULL);
+
+ return TRUE;
+}
+
+
+static GaimPluginInfo info = {
+ GAIM_PLUGIN_MAGIC,
+ GAIM_MAJOR_VERSION,
+ GAIM_MINOR_VERSION,
+ GAIM_PLUGIN_STANDARD,
+ NULL,
+ 0,
+ NULL,
+ GAIM_PRIORITY_DEFAULT,
+
+ PLUGIN,
+ "Buddy Edit Module",
+ G_STRINGIFY(PLUGIN_VERSION),
+
+ "Enable editing of buddy properties",
+ "A plugin that adds an edit to to buddies allowing you to change various details you can't normally change. "
+ "It also provides a mechanism for subsequent plugins to add themselves to that dialog. ",
+ "Martijn van Oosterhout <kleptog@svana.org>",
+ "http://svana.org/kleptog/gaim/",
+
+ plugin_load,
+ NULL,
+ NULL,
+
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+static void
+init_plugin(GaimPlugin * plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(buddyedit, init_plugin, info);
--- a/buddytime/buddytime.c Mon Oct 22 04:40:46 2007 -0400
+++ b/buddytime/buddytime.c Mon Oct 22 05:00:47 2007 -0400
@@ -1,6 +1,13 @@
/*
- * Plugin Name - Summary
- * Copyright (C) 2004
+ * Buddy Time - Displays a buddy's local time
+ *
+ * A libpurple plugin that allows you to configure a timezone on a per-contact
+ * basis so it can display the localtime of your contact when a conversation
+ * starts. Convenient if you deal with contacts from many parts of the
+ * world.
+ *
+ * Copyright (C) 2006-2007, Richard Laager <rlaager@pidgin.im>
+ * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
@@ -14,247 +21,468 @@
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02111-1301, USA.
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
*/
-
#include "../common/pp_internal.h"
-#define PLUGIN_ID "gtk-plugin_pack-buddytime"
-#define PLUGIN_STATIC_NAME "buddytime"
-#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>"
+#include "buddytime.h"
+#define PLUGIN_STATIC_NAME CORE_PLUGIN_STATIC_NAME
+#define PLUGIN_ID CORE_PLUGIN_ID
-#include <gdk/gdk.h>
-#include <gtk/gtk.h>
+#define SETTING_NAME "timezone"
+#define CONTROL_NAME PLUGIN_ID "-" SETTING_NAME
-#include <blist.h>
-#include <gtkutils.h>
-#include <gtkplugin.h>
-#include <request.h>
+#include <glib.h>
+#include <errno.h>
+#include <ctype.h>
+#include <string.h>
-/******************************************************************************
- * Defines
- *****************************************************************************/
-#define BT_NODE_SETTING "bt-timezone"
+#include "conversation.h"
+#include "core.h"
+#include "debug.h"
+#include "notify.h"
+#include "plugin.h"
+#include "request.h"
+#include "util.h"
+#include "value.h"
+#include "version.h"
-/******************************************************************************
- * Structures
- *****************************************************************************/
-typedef struct {
- PurpleBlistNode *node;
- PurpleRequestField *timezone;
- gpointer handle;
-} BTDialog;
+#include "gtkblist.h"
+
+#include "recurse.h"
+
+#define TIMEZONE_FLAG ((void*)1)
+#define DISABLED_FLAG ((void*)2)
+
+BuddyTimeUiOps *ui_ops = NULL;
+
+static PurplePlugin *plugin_self;
-typedef struct {
- GtkWidget *ebox;
- GtkWidget *label;
- PurpleConversation *conv;
- guint timeout;
-} BTWidget;
-
-/******************************************************************************
- * Globals
- *****************************************************************************/
-static GList *dialogs = NULL;
-static GList *widgets = NULL;
+/* Resolve specifies what the return value should mean:
+ *
+ * If TRUE, it's for display, we want to know the *effect* thus hiding the
+ * "none" value and going to going level to find the default
+ *
+ * If false, we only want what the user enter, thus the string "none" if
+ * that's what it is
+ *
+ * data is here so we can use this as a callback for IPC
+ */
+static const char *
+buddy_get_timezone(PurpleBlistNode * node, gboolean resolve, void *data)
+{
+ PurpleBlistNode *datanode = NULL;
+ const char *timezone;
-/******************************************************************************
- * Main Stuff
- *****************************************************************************/
-static BTWidget *
-bt_widget_new(PurpleConversation *conv) {
- BTWidget *ret;
+ switch (node->type)
+ {
+ case PURPLE_BLIST_BUDDY_NODE:
+ datanode = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) node);
+ break;
+ case PURPLE_BLIST_CONTACT_NODE:
+ datanode = node;
+ break;
+ case PURPLE_BLIST_GROUP_NODE:
+ datanode = node;
+ break;
+ default:
+ return NULL;
+ }
+
+ timezone = purple_blist_node_get_string(datanode, SETTING_NAME);
- g_return_val_if_fail(conv, NULL);
+ if (!resolve)
+ return timezone;
- ret = g_new0(BTWidget, 1);
- ret->conv = conv;
+ /* The effect of "none" is to stop recursion */
+ if (timezone && strcmp(timezone, "none") == 0)
+ return NULL;
+
+ if (timezone)
+ return timezone;
- ret->ebox = gtk_event_box_new();
+ if (datanode->type == PURPLE_BLIST_CONTACT_NODE)
+ {
+ /* There is no purple_blist_contact_get_group(), though there probably should be */
+ datanode = datanode->parent;
+ timezone = purple_blist_node_get_string(datanode, SETTING_NAME);
+ }
- ret->label = gtk_label_new("label");
- gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label);
+ if (timezone && strcmp(timezone, "none") == 0)
+ return NULL;
+
+ return timezone;
}
-/******************************************************************************
- * Blist Stuff
- *****************************************************************************/
-static void
-bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) {
- BTDialog *dialog = (BTDialog *)data;
+/* Calcuates the difference between two struct tm's. */
+static double
+timezone_calc_difference(struct tm *remote_tm, struct tm *tmp_tm)
+{
+ int hours_diff = 0;
+ int minutes_diff = 0;
- dialogs = g_list_remove(dialogs, dialog);
- g_free(dialog);
+ /* Note this only works because the times are
+ * known to be within 24 hours of each other! */
+ if (remote_tm->tm_mday != tmp_tm->tm_mday)
+ hours_diff = 24;
+
+ hours_diff += (remote_tm->tm_hour - tmp_tm->tm_hour);
+ minutes_diff = (remote_tm->tm_min - tmp_tm->tm_min);
+
+ return (double)minutes_diff / 60.0 + hours_diff;
}
-static void
-bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) {
- BTDialog *dialog = (BTDialog *)data;
+/* data is here so we can use this as a callback for IPC */
+static int
+timezone_get_time(const char *timezone, struct tm *tm, double *diff, void *data)
+{
+ time_t now;
+ struct tm *tm_tmp;
+#ifdef PRIVATE_TZLIB
+ struct state *tzinfo = timezone_load(timezone);
+
+ if(!tzinfo)
+ return -1;
+
+ time(&now);
+ localsub(&now, 0, tm, tzinfo);
+ free(tzinfo);
+#else
+ const gchar *old_tz;
+
+ /* Store the current TZ value. */
+ old_tz = g_getenv("TZ");
- dialogs = g_list_remove(dialogs, dialog);
- g_free(dialog);
+ g_setenv("TZ", timezone, TRUE);
+
+ time(&now);
+ tm_tmp = localtime(&now);
+ *tm = *tm_tmp; /* Must copy, localtime uses local buffer */
+
+ /* Reset the old TZ value. */
+ if (old_tz == NULL)
+ g_unsetenv("TZ");
+ else
+ g_setenv("TZ", old_tz, TRUE);
+#endif
+
+ /* Calculate user's localtime, and compare. If same, no output */
+ tm_tmp = localtime(&now);
+
+ if (tm_tmp->tm_hour == tm->tm_hour && tm_tmp->tm_min == tm->tm_min)
+ return 1;
+
+ *diff = timezone_calc_difference(tm, tm_tmp);
+ return 0;
}
static void
-bt_show_dialog(PurpleBlistNode *node) {
- BTDialog *dialog;
- PurpleRequestFields *fields;
- PurpleRequestFieldGroup *group;
- PurpleAccount *account = NULL;
- gint current = 0;
+timezone_createconv_cb(PurpleConversation * conv, void *data)
+{
+ const char *name;
+ PurpleBuddy *buddy;
+ struct tm tm;
+ const char *timezone;
+ double diff;
+ int ret;
+
+ if (purple_conversation_get_type(conv) != PURPLE_CONV_TYPE_IM)
+ return;
- dialog = g_new0(BTDialog, 1);
+ name = purple_conversation_get_name(conv);
+ buddy = purple_find_buddy(purple_conversation_get_account(conv), name);
+ if (!buddy)
+ return;
- if(!dialog)
- return;
+ timezone = buddy_get_timezone((PurpleBlistNode *) buddy, TRUE, NULL);
- dialog->node = node;
+ if (!timezone)
+ return;
+
+ ret = timezone_get_time(timezone, &tm, &diff, NULL);
- current = purple_blist_node_get_int(node, BT_NODE_SETTING);
-
- /* TODO: set account from node */
+ if (ret == 0)
+ {
+ const char *text = purple_time_format(&tm);
- /* build the request fields */
- fields = purple_request_fields_new();
- group = purple_request_field_group_new(NULL);
- purple_request_fields_add_group(fields, group);
+ char *str;
+ if (diff < 0)
+ {
+ diff = 0 - diff;
+ str = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
+ "Remote Local Time: %s (%.4g hour behind)",
+ "Remote Local Time: %s (%.4g hours behind)", diff),
+ text, diff);
+ }
+ else
+ {
+ str = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
+ "Remote Local Time: %s (%.4g hour ahead)",
+ "Remote Local Time: %s (%.4g hours ahead)", diff),
+ text, diff);
+ }
- dialog->timezone = purple_request_field_choice_new("timezone",
- _("_Timezone"), 1);
- purple_request_field_group_add_field(group, dialog->timezone);
+ purple_conversation_write(conv, PLUGIN_STATIC_NAME, str, PURPLE_MESSAGE_SYSTEM, time(NULL));
- purple_request_field_choice_add(dialog->timezone, _("Clear setting"));
+ g_free(str);
+ }
+}
- purple_request_field_choice_add(dialog->timezone, _("GMT-12"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-11"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-10"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-9"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-8"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-7"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-6"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-5"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-4"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-3"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-2"));
- purple_request_field_choice_add(dialog->timezone, _("GMT-1"));
- purple_request_field_choice_add(dialog->timezone, _("GMT"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+1"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+2"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+3"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+4"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+5"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+6"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+7"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+8"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+9"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+10"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+11"));
- purple_request_field_choice_add(dialog->timezone, _("GMT+12"));
+#if 0
+static void
+buddytimezone_submitfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data)
+{
+ PurpleBlistNode *node;
+ PurpleRequestField *list;
+
+ /* timezone stuff */
+ purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_submitfields_cb(%p,%p)\n", fields, data);
+
+ switch (data->type)
+ {
+ case PURPLE_BLIST_BUDDY_NODE:
+ node = (PurpleBlistNode *) purple_buddy_get_contact((PurpleBuddy *) data);
+ break;
+ case PURPLE_BLIST_CONTACT_NODE:
+ case PURPLE_BLIST_GROUP_NODE:
+ /* code handles either case */
+ node = data;
+ break;
+ case PURPLE_BLIST_CHAT_NODE:
+ case PURPLE_BLIST_OTHER_NODE:
+ default:
+ /* Not applicable */
+ return;
+ }
-// purple_request_field_choice_set_default_value(dialog->timezone, current);
-// purple_request_field_coice_set_value(dialog->timezone, current);
+ list = purple_request_fields_get_field(fields, CONTROL_NAME);
+ if (ui_ops != NULL && ui_ops->get_timezone_menu_selection != NULL)
+ {
+ const char *seldata = ui_ops->get_timezone_menu_selection(list->ui_data);
+ if (seldata == NULL)
+ purple_blist_node_remove_setting(node, SETTING_NAME);
+ else
+ purple_blist_node_set_string(node, SETTING_NAME, seldata);
+ }
+ else
+ {
+ const GList *sellist;
+ void *seldata = NULL;
+ sellist = purple_request_field_list_get_selected(list);
+ if (sellist)
+ seldata = purple_request_field_list_get_data(list, sellist->data);
- /* TODO: set who from blist node */
- dialog->handle =
- purple_request_fields(NULL, _("Select timezone"),
- NULL, "foo", fields,
- _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb),
- _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb),
- account, NULL /* who */, NULL, dialog);
-
- dialogs = g_list_append(dialogs, dialog);
+ /* Otherwise, it's fixed value and this means deletion */
+ if (seldata == TIMEZONE_FLAG)
+ purple_blist_node_set_string(node, SETTING_NAME, sellist->data);
+ else if (seldata == DISABLED_FLAG)
+ purple_blist_node_set_string(node, SETTING_NAME, "none");
+ else
+ purple_blist_node_remove_setting(node, SETTING_NAME);
+ }
+}
+
+static int
+buddy_add_timezone_cb(char *filename, void *data)
+{
+ PurpleRequestField *field = (PurpleRequestField *) data;
+ if (isupper(filename[0]))
+ purple_request_field_list_add(field, filename, TIMEZONE_FLAG);
+ return 0;
}
static void
-bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) {
- bt_show_dialog(node);
+buddytimezone_createfields_cb(PurpleRequestFields * fields, PurpleBlistNode * data)
+{
+ purple_debug(PURPLE_DEBUG_INFO, PLUGIN_STATIC_NAME, "buddytimezone_createfields_cb(%p,%p)\n", fields, data);
+ PurpleRequestField *field;
+ PurpleRequestFieldGroup *group;
+ const char *timezone;
+ gboolean is_default;
+
+ switch (data->type)
+ {
+ case PURPLE_BLIST_BUDDY_NODE:
+ case PURPLE_BLIST_CONTACT_NODE:
+ is_default = FALSE;
+ break;
+ case PURPLE_BLIST_GROUP_NODE:
+ is_default = TRUE;
+ break;
+ case PURPLE_BLIST_CHAT_NODE:
+ case PURPLE_BLIST_OTHER_NODE:
+ default:
+ /* Not applicable */
+ return;
+ }
+
+ group = purple_request_field_group_new(NULL);
+ purple_request_fields_add_group(fields, group);
+
+ timezone = buddy_get_timezone(data, FALSE, NULL);
+
+ if (ui_ops != NULL && ui_ops->create_menu)
+ {
+ field =
+ purple_request_field_new(CONTROL_NAME,
+ is_default ? "Default timezone for group" : "Timezone of contact",
+ PURPLE_REQUEST_FIELD_LIST);
+ field->ui_data = ui_ops->create_menu(timezone);
+ }
+ else
+ {
+ field =
+ purple_request_field_list_new(CONTROL_NAME,
+ is_default ? "Default timezone for group" :
+ "Timezone of contact (type to select)");
+ purple_request_field_list_set_multi_select(field, FALSE);
+ purple_request_field_list_add(field, "<Default>", "");
+ purple_request_field_list_add(field, "<Disabled>", DISABLED_FLAG);
+
+ recurse_directory("/usr/share/zoneinfo/", buddy_add_timezone_cb, field);
+
+ if (timezone)
+ {
+ if (strcmp(timezone, "none") == 0)
+ purple_request_field_list_add_selected(field, "<Disabled>");
+ else
+ purple_request_field_list_add_selected(field, timezone);
+ }
+ else
+ purple_request_field_list_add_selected(field, "<Default>");
+ }
+
+ purple_request_field_group_add_field(group, field);
+}
+#endif
+
+static void
+marshal_POINTER__POINTER_BOOL(PurpleCallback cb, va_list args, void *data,
+ void **return_val)
+{
+ gpointer ret_val;
+ void *arg1 = va_arg(args, void *);
+ gboolean arg2 = va_arg(args, gboolean);
+
+ ret_val = ((gpointer (*)(void *, gboolean, void *))cb)(arg1, arg2, data);
+
+ if (return_val != NULL)
+ *return_val = ret_val;
}
static void
-bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) {
- PurpleMenuAction *action;
-
- if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
- return;
+marshal_POINTER__POINTER_POINTER_POINTER(PurpleCallback cb, va_list args, void *data,
+ void **return_val)
+{
+ gpointer ret_val;
+ void *arg1 = va_arg(args, void *);
+ void *arg2 = va_arg(args, void *);
+ void *arg3 = va_arg(args, void *);
- /* ignore chats and groups */
- if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node))
- return;
-
- action = purple_menu_action_new(_("Timezone"),
- PURPLE_CALLBACK(bt_edit_timezone_cb),
- NULL,
- NULL);
- (*menu) = g_list_append(*menu, action);
+ ret_val = ((gpointer (*)(void *, void *, void *, void *))cb)(arg1, arg2, arg3, data);
+
+ if (return_val != NULL)
+ *return_val = ret_val;
}
-/******************************************************************************
- * Conversation stuff
- *****************************************************************************/
+static gboolean
+load_ui_plugin(gpointer data)
+{
+ char *ui_plugin_id;
+ PurplePlugin *ui_plugin;
+
+ ui_plugin_id = g_strconcat(purple_core_get_ui(), "-", PLUGIN_STATIC_NAME, NULL);
+ ui_plugin = purple_plugins_find_with_id(ui_plugin_id);
+ if (ui_plugin != NULL)
+ {
+ if (!purple_plugin_load(ui_plugin))
+ {
+ purple_notify_error(ui_plugin, NULL, _("Failed to load the Buddy Timezone UI."),
+ ui_plugin->error ? ui_plugin->error : "");
+ }
+ }
+
+ g_free(ui_plugin_id);
+
+ return FALSE;
+}
-/******************************************************************************
- * Plugin Stuff
- *****************************************************************************/
static gboolean
-plugin_load(PurplePlugin *plugin) {
- purple_signal_connect(purple_blist_get_handle(),
- "blist-node-extended-menu",
- plugin,
- PURPLE_CALLBACK(bt_blist_drawing_menu_cb),
- NULL);
+plugin_load(PurplePlugin * plugin)
+{
+ plugin_self = plugin;
+
+ purple_signal_connect(purple_conversations_get_handle(), "conversation-created", plugin,
+ PURPLE_CALLBACK(timezone_createconv_cb), NULL);
+
+ purple_plugin_ipc_register(plugin, BUDDYTIME_BUDDY_GET_TIMEZONE,
+ PURPLE_CALLBACK(buddy_get_timezone),
+ marshal_POINTER__POINTER_BOOL,
+ purple_value_new(PURPLE_TYPE_STRING),
+ 2,
+ purple_value_new(PURPLE_TYPE_SUBTYPE,
+ PURPLE_SUBTYPE_BLIST_NODE),
+ purple_value_new(PURPLE_TYPE_BOOLEAN));
+
+ purple_plugin_ipc_register(plugin, BUDDYTIME_TIMEZONE_GET_TIME,
+ PURPLE_CALLBACK(timezone_get_time),
+ marshal_POINTER__POINTER_POINTER_POINTER,
+ purple_value_new(PURPLE_TYPE_INT),
+ 2,
+ purple_value_new(PURPLE_TYPE_POINTER),
+ purple_value_new(PURPLE_TYPE_POINTER));
+
+ /* This is done as an idle callback to avoid an infinite loop
+ * when we try to load the UI plugin which depends on this plugin
+ * which isn't officially loaded yet. */
+ purple_timeout_add(0, load_ui_plugin, NULL);
return TRUE;
}
-static gboolean
-plugin_unload(PurplePlugin *plugin) {
- return TRUE;
-}
-
-static PurplePluginInfo info = {
- PURPLE_PLUGIN_MAGIC, /* Magic */
- PURPLE_MAJOR_VERSION, /* Purple Major Version */
- PURPLE_MINOR_VERSION, /* Purple Minor Version */
- PURPLE_PLUGIN_STANDARD, /* plugin type */
- PIDGIN_PLUGIN_TYPE, /* ui requirement */
- 0, /* flags */
- NULL, /* dependencies */
- PURPLE_PRIORITY_DEFAULT, /* priority */
-
- PLUGIN_ID, /* plugin id */
- NULL, /* name */
- PP_VERSION, /* version */
- NULL, /* summary */
- NULL, /* description */
- PLUGIN_AUTHOR, /* author */
- PP_WEBSITE, /* website */
-
- plugin_load, /* load */
- plugin_unload, /* unload */
- NULL, /* destroy */
-
- NULL, /* ui_info */
- NULL, /* extra_info */
- NULL, /* prefs_info */
- NULL, /* actions */
- NULL, /* reserved 1 */
- NULL, /* reserved 2 */
- NULL, /* reserved 3 */
- NULL /* reserved 4 */
+static PurplePluginInfo info =
+{
+ PURPLE_PLUGIN_MAGIC,
+ PURPLE_MAJOR_VERSION,
+ 0,
+ PURPLE_PLUGIN_STANDARD, /**< type */
+ NULL, /**< ui_requirement */
+ 0, /**< flags */
+ NULL, /**< dependencies */
+ PURPLE_PRIORITY_DEFAULT, /**< priority */
+ PLUGIN_ID, /**< id */
+ NULL, /**< name */
+ PP_VERSION, /**< version */
+ NULL, /**< summary */
+ NULL, /**< description */
+ PLUGIN_AUTHOR, /**< author */
+ PP_WEBSITE, /**< homepage */
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL, /**< actions */
+ NULL, /**< reserved 1 */
+ NULL, /**< reserved 2 */
+ NULL, /**< reserved 3 */
+ NULL /**< reserved 4 */
};
static void
-init_plugin(PurplePlugin *plugin) {
+init_plugin(PurplePlugin * plugin)
+{
#ifdef ENABLE_NLS
bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
#endif /* ENABLE_NLS */
info.name = _("Buddy Time");
- info.summary = _("summary");
- info.description = _("description");
+ info.summary = _("Quickly see the local time of a buddy");
+ info.description = _("Quickly see the local time of a buddy");
}
-PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
+PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddytime.c.old Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,260 @@
+/*
+ * Plugin Name - Summary
+ * Copyright (C) 2004
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02111-1301, USA.
+ */
+
+#include "../common/pp_internal.h"
+
+#define PLUGIN_ID "gtk-plugin_pack-buddytime"
+#define PLUGIN_STATIC_NAME "buddytime"
+#define PLUGIN_AUTHOR "Gary Kramlich <grim@reaperworld.com>"
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include <blist.h>
+#include <gtkutils.h>
+#include <gtkplugin.h>
+#include <request.h>
+
+/******************************************************************************
+ * Defines
+ *****************************************************************************/
+#define BT_NODE_SETTING "bt-timezone"
+
+/******************************************************************************
+ * Structures
+ *****************************************************************************/
+typedef struct {
+ PurpleBlistNode *node;
+ PurpleRequestField *timezone;
+ gpointer handle;
+} BTDialog;
+
+typedef struct {
+ GtkWidget *ebox;
+ GtkWidget *label;
+ PurpleConversation *conv;
+ guint timeout;
+} BTWidget;
+
+/******************************************************************************
+ * Globals
+ *****************************************************************************/
+static GList *dialogs = NULL;
+static GList *widgets = NULL;
+
+/******************************************************************************
+ * Main Stuff
+ *****************************************************************************/
+static BTWidget *
+bt_widget_new(PurpleConversation *conv) {
+ BTWidget *ret;
+
+ g_return_val_if_fail(conv, NULL);
+
+ ret = g_new0(BTWidget, 1);
+ ret->conv = conv;
+
+ ret->ebox = gtk_event_box_new();
+
+ ret->label = gtk_label_new("label");
+ gtk_container_add(GTK_CONTAINER(ret->ebox), ret->label);
+}
+
+/******************************************************************************
+ * Blist Stuff
+ *****************************************************************************/
+static void
+bt_dialog_ok_cb(gpointer data, PurpleRequestFields *fields) {
+ BTDialog *dialog = (BTDialog *)data;
+
+ dialogs = g_list_remove(dialogs, dialog);
+ g_free(dialog);
+}
+
+static void
+bt_dialog_cancel_cb(gpointer data, PurpleRequestFields *fields) {
+ BTDialog *dialog = (BTDialog *)data;
+
+ dialogs = g_list_remove(dialogs, dialog);
+ g_free(dialog);
+}
+
+static void
+bt_show_dialog(PurpleBlistNode *node) {
+ BTDialog *dialog;
+ PurpleRequestFields *fields;
+ PurpleRequestFieldGroup *group;
+ PurpleAccount *account = NULL;
+ gint current = 0;
+
+ dialog = g_new0(BTDialog, 1);
+
+ if(!dialog)
+ return;
+
+ dialog->node = node;
+
+ current = purple_blist_node_get_int(node, BT_NODE_SETTING);
+
+ /* TODO: set account from node */
+
+ /* build the request fields */
+ fields = purple_request_fields_new();
+ group = purple_request_field_group_new(NULL);
+ purple_request_fields_add_group(fields, group);
+
+ dialog->timezone = purple_request_field_choice_new("timezone",
+ _("_Timezone"), 1);
+ purple_request_field_group_add_field(group, dialog->timezone);
+
+ purple_request_field_choice_add(dialog->timezone, _("Clear setting"));
+
+ purple_request_field_choice_add(dialog->timezone, _("GMT-12"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-11"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-10"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-9"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-8"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-7"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-6"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-5"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-4"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-3"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-2"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT-1"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+1"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+2"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+3"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+4"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+5"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+6"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+7"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+8"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+9"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+10"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+11"));
+ purple_request_field_choice_add(dialog->timezone, _("GMT+12"));
+
+// purple_request_field_choice_set_default_value(dialog->timezone, current);
+// purple_request_field_coice_set_value(dialog->timezone, current);
+
+ /* TODO: set who from blist node */
+ dialog->handle =
+ purple_request_fields(NULL, _("Select timezone"),
+ NULL, "foo", fields,
+ _("OK"), PURPLE_CALLBACK(bt_dialog_ok_cb),
+ _("Cancel"), PURPLE_CALLBACK(bt_dialog_cancel_cb),
+ account, NULL /* who */, NULL, dialog);
+
+ dialogs = g_list_append(dialogs, dialog);
+}
+
+static void
+bt_edit_timezone_cb(PurpleBlistNode *node, gpointer data) {
+ bt_show_dialog(node);
+}
+
+static void
+bt_blist_drawing_menu_cb(PurpleBlistNode *node, GList **menu) {
+ PurpleMenuAction *action;
+
+ if (purple_blist_node_get_flags(node) & PURPLE_BLIST_NODE_FLAG_NO_SAVE)
+ return;
+
+ /* ignore chats and groups */
+ if(PURPLE_BLIST_NODE_IS_CHAT(node) || PURPLE_BLIST_NODE_IS_GROUP(node))
+ return;
+
+ action = purple_menu_action_new(_("Timezone"),
+ PURPLE_CALLBACK(bt_edit_timezone_cb),
+ NULL,
+ NULL);
+ (*menu) = g_list_append(*menu, action);
+}
+
+/******************************************************************************
+ * Conversation stuff
+ *****************************************************************************/
+
+
+/******************************************************************************
+ * Plugin Stuff
+ *****************************************************************************/
+static gboolean
+plugin_load(PurplePlugin *plugin) {
+ purple_signal_connect(purple_blist_get_handle(),
+ "blist-node-extended-menu",
+ plugin,
+ PURPLE_CALLBACK(bt_blist_drawing_menu_cb),
+ NULL);
+
+ return TRUE;
+}
+
+static gboolean
+plugin_unload(PurplePlugin *plugin) {
+ return TRUE;
+}
+
+static PurplePluginInfo info = {
+ PURPLE_PLUGIN_MAGIC, /* Magic */
+ PURPLE_MAJOR_VERSION, /* Purple Major Version */
+ PURPLE_MINOR_VERSION, /* Purple Minor Version */
+ PURPLE_PLUGIN_STANDARD, /* plugin type */
+ PIDGIN_PLUGIN_TYPE, /* ui requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+
+ PLUGIN_ID, /* plugin id */
+ NULL, /* name */
+ PP_VERSION, /* version */
+ NULL, /* summary */
+ NULL, /* description */
+ PLUGIN_AUTHOR, /* author */
+ PP_WEBSITE, /* website */
+
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+
+ NULL, /* ui_info */
+ NULL, /* extra_info */
+ NULL, /* prefs_info */
+ NULL, /* actions */
+ NULL, /* reserved 1 */
+ NULL, /* reserved 2 */
+ NULL, /* reserved 3 */
+ NULL /* reserved 4 */
+};
+
+static void
+init_plugin(PurplePlugin *plugin) {
+#ifdef ENABLE_NLS
+ bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+#endif /* ENABLE_NLS */
+
+ info.name = _("Buddy Time");
+ info.summary = _("summary");
+ info.description = _("description");
+}
+
+PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/buddytime.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,52 @@
+/*
+ * Buddy Time - Displays a buddy's local time
+ *
+ * A libpurple plugin that allows you to configure a timezone on a per-contact
+ * basis so it can display the localtime of your contact when a conversation
+ * starts. Convenient if you deal with contacts from many parts of the
+ * world.
+ *
+ * Copyright (C) 2006-2007, Richard Laager <rlaager@users.sf.net>
+ * Copyright (C) 2006, Martijn van Oosterhout <kleptog@svana.org>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+#ifndef _BUDDYTIME_H_
+#define _BUDDYTIME_H_
+
+#define CORE_PLUGIN_STATIC_NAME "buddytime"
+#define CORE_PLUGIN_ID "core-kleptog-" CORE_PLUGIN_STATIC_NAME
+
+#define PLUGIN_AUTHOR "Martijn van Oosterhout <kleptog@svana.org>" \
+ "\n\t\t\tRichard Laager <rlaager@pidgin.im>"
+
+#define BUDDYTIME_BUDDY_GET_TIMEZONE "buddy_get_timezone"
+#define BUDDYTIME_TIMEZONE_GET_TIME "timezone_get_time"
+
+typedef struct _BuddyTimeUiOps BuddyTimeUiOps;
+
+struct _BuddyTimeUiOps
+{
+ void *(*create_menu)(const char *selected); /**< Creates a timezone menu. */
+ const char * (*get_timezone_menu_selection)(void *ui_data); /**< Retrieves the menu setting. */
+
+ void (*_buddytime_reserved1)(void);
+ void (*_buddytime_reserved2)(void);
+ void (*_buddytime_reserved3)(void);
+ void (*_buddytime_reserved4)(void);
+};
+
+#endif /* _BUDDYTIME_H_ */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtkbuddytime.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ */
+#include "../common/pp_internal.h"
+
+#define PLUGIN_STATIC_NAME "gtkbuddytime"
+#define PLUGIN_ID PIDGIN_UI "-buddytime"
+
+#include "buddytime.h"
+
+#include <glib.h>
+
+#include "plugin.h"
+#include "version.h"
+
+#include "gtkblist.h"
+#include "gtkplugin.h"
+
+PurplePlugin *core_plugin = NULL;
+
+static void
+buddytimezone_tooltip_cb(PurpleBlistNode * node, char **text, gboolean full, void *data)
+{
+ char *newtext;
+ const char *timezone;
+ struct tm tm;
+ double diff;
+ int ret;
+
+ if (!full)
+ return;
+
+ timezone = purple_plugin_ipc_call(core_plugin, BUDDYTIME_BUDDY_GET_TIMEZONE,
+ NULL, node, TRUE);
+
+ if (!timezone)
+ return;
+
+ ret = GPOINTER_TO_INT(purple_plugin_ipc_call(core_plugin, BUDDYTIME_TIMEZONE_GET_TIME,
+ NULL, timezone, &tm, &diff));
+ if (ret < 0)
+ newtext = g_strdup_printf("%s\n<b>Timezone:</b> %s (error)", *text, timezone);
+ else if (ret == 0)
+ {
+ const char *timetext = purple_time_format(&tm);
+
+ if (diff < 0)
+ {
+ diff = 0 - diff;
+ newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
+ "%s\n<b>Local Time:</b> %s (%.4g hour behind)",
+ "%s\n<b>Local Time:</b> %s (%.4g hours behind)", diff),
+ *text, timetext, diff);
+ }
+ else
+ {
+ newtext = g_strdup_printf(dngettext(GETTEXT_PACKAGE,
+ "%s\n<b>Local Time:</b> %s (%.4g hour ahead)",
+ "%s\n<b>Local Time:</b> %s (%.4g hours ahead)", diff),
+ *text, timetext, diff); }
+ }
+ else
+ return;
+
+ g_free(*text);
+ *text = newtext;
+}
+
+static gboolean
+plugin_load(PurplePlugin * plugin)
+{
+ purple_signal_connect(pidgin_blist_get_handle(), "drawing-tooltip", plugin,
+ PURPLE_CALLBACK(buddytimezone_tooltip_cb), NULL);
+
+ core_plugin = purple_plugins_find_with_id(CORE_PLUGIN_ID);
+
+ return (core_plugin != NULL);
+}
+
+static PurplePluginInfo info =
+{
+ PURPLE_PLUGIN_MAGIC,
+ PURPLE_MAJOR_VERSION,
+ 0,
+ PURPLE_PLUGIN_STANDARD, /**< type */
+ PIDGIN_PLUGIN_TYPE, /**< ui_requirement */
+ PURPLE_PLUGIN_FLAG_INVISIBLE, /**< flags */
+ NULL, /**< dependencies */
+ PURPLE_PRIORITY_DEFAULT, /**< priority */
+ PLUGIN_ID, /**< id */
+ NULL, /**< name */
+ PP_VERSION, /**< version */
+ NULL, /**< summary */
+ NULL, /**< description */
+ PLUGIN_AUTHOR, /**< author */
+ PP_WEBSITE, /**< homepage */
+ plugin_load, /**< load */
+ NULL, /**< unload */
+ NULL, /**< destroy */
+ NULL, /**< ui_info */
+ NULL, /**< extra_info */
+ NULL, /**< prefs_info */
+ NULL, /**< actions */
+ NULL, /**< reserved 1 */
+ NULL, /**< reserved 2 */
+ NULL, /**< reserved 3 */
+ NULL /**< reserved 4 */
+};
+
+static void
+init_plugin(PurplePlugin * plugin)
+{
+ info.dependencies = g_list_append(info.dependencies, CORE_PLUGIN_ID);
+
+#ifdef ENABLE_NLS
+ bindtextdomain(GETTEXT_PACKAGE, PP_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+#endif /* ENABLE_NLS */
+
+ info.name = _("Buddy Time (Pidgin UI)");
+ info.summary = _("Pidgin user interface for the Buddy Time plugin.");
+ info.description = _("Pidgin user interface for the Buddy Time plugin.");
+}
+
+PURPLE_INIT_PLUGIN(PLUGIN_STATIC_NAME, init_plugin, info);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtktimezone.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,237 @@
+/*************************************************************************
+ * GTK Timezone widget module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * This module creates the GTK widget used to select timezones. It's here to
+ * clearly seperate the GTK stuff from the plugin itself.
+ *************************************************************************/
+
+#include <gtk/gtk.h>
+#include <string.h>
+#include <ctype.h>
+#include "recurse.h"
+
+#define DISABLED_STRING "<Disabled>"
+#define DEFAULT_STRING "<Default>"
+#define MORE_STRING "More..."
+
+struct nodestate
+{
+ GtkWidget *submenu;
+ gchar *string;
+};
+
+struct state
+{
+ GtkWidget *base;
+ GtkWidget *extra;
+ int currdepth;
+ struct nodestate stack[4];
+};
+
+static inline const char *
+menuitem_get_label(GtkMenuItem * menuitem)
+{
+ return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
+}
+
+static GtkMenuItem *
+menu_get_first_menuitem(GtkWidget * menu)
+{
+ GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
+ GtkMenuItem *selection = GTK_MENU_ITEM(g_list_nth_data(list, 0));
+ g_list_free(list);
+ return selection;
+}
+
+static int
+menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu)
+{
+ const char *label = menuitem_get_label(menuitem);
+
+ if(label[0] == '<')
+ {
+ GtkWidget *selection;
+ gchar *selstring;
+
+ if(strcmp(label, DEFAULT_STRING) == 0)
+ selstring = "";
+ else if(strcmp(label, DISABLED_STRING) == 0)
+ selstring = "none";
+
+ selection = GTK_WIDGET(menu_get_first_menuitem(menu));
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ char *str = g_strdup(label);
+
+ GtkWidget *parent;
+
+ for (;;)
+ {
+ GtkMenuItem *parentitem;
+ const char *label2;
+ char *temp;
+
+ parent = gtk_widget_get_parent(GTK_WIDGET(menuitem));
+ if(menu == parent)
+ break;
+
+ parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent)));
+ label2 = menuitem_get_label(parentitem);
+ if(strcmp(label2, MORE_STRING) != 0)
+ {
+ temp = g_strconcat(label2, "/", str, NULL);
+ g_free(str);
+ str = temp;
+ }
+
+ menuitem = parentitem;
+ }
+ {
+ GtkLabel *label;
+
+ GtkMenuItem *selection = menu_get_first_menuitem(menu);
+ GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu)));
+
+ label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection)));
+
+ gtk_label_set_text(label, str);
+ gtk_widget_show(GTK_WIDGET(selection));
+ gtk_option_menu_set_history(optionmenu, 0);
+
+ printf("optionmenu=%p, menu=%p, menuitem=%p, label=%p\n", optionmenu, menu, selection,
+ label);
+ g_free(str);
+ }
+ }
+ return 0;
+}
+
+static int
+make_menu_cb(char *path, struct state *state)
+{
+ int i, j;
+
+ char **elements;
+
+ /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */
+ if(!isupper(path[0]))
+ return 0;
+
+ elements = g_strsplit(path, "/", 4);
+
+ for (i = 0; i < state->currdepth && state->stack[i].string; i++)
+ {
+ if(strcmp(elements[i], state->stack[i].string) != 0)
+ break;
+ }
+ /* i is now the index of the first non-matching element, so free the rest */
+ for (j = i; j < state->currdepth; j++)
+ g_free(state->stack[j].string);
+ state->currdepth = i;
+
+ while (elements[i])
+ {
+ GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu;
+ GtkWidget *menuitem;
+
+ if(i == 0 && elements[1] == NULL)
+ parent = state->extra;
+
+ menuitem = gtk_menu_item_new_with_label(elements[i]);
+ gtk_menu_append(parent, menuitem);
+
+ if(elements[i + 1] != NULL) /* Has submenu */
+ {
+ state->stack[i].submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu);
+
+ state->currdepth++;
+ state->stack[i].string = g_strdup(elements[i]);
+ }
+ else
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb),
+ state->base);
+
+ i++;
+ }
+ g_strfreev(elements);
+ return 0;
+}
+
+void *
+make_timezone_menu(const char *selected)
+{
+ int i;
+ struct state state;
+
+ GtkWidget *menu;
+ GtkWidget *optionmenu, *menuitem, *selection;
+
+ if(selected == NULL)
+ selected = "";
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label(selected);
+ gtk_menu_append(menu, menuitem);
+ selection = menuitem;
+
+ menuitem = gtk_menu_item_new_with_label(DISABLED_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(MORE_STRING);
+ gtk_menu_append(menu, menuitem);
+
+ state.base = menu;
+ state.extra = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra);
+
+ state.currdepth = 0;
+
+ recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state);
+
+ for (i = 0; i < state.currdepth; i++)
+ g_free(state.stack[i].string);
+
+ optionmenu = gtk_option_menu_new();
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
+ gtk_widget_show_all(optionmenu);
+
+ if(strcmp(selected, "") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2);
+ gtk_widget_hide(selection);
+ }
+ else if(strcmp(selected, "none") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1);
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0);
+ }
+
+ return optionmenu;
+}
+
+const char *
+get_timezone_menu_selection(void *widget)
+{
+ GtkOptionMenu *menu = GTK_OPTION_MENU(widget);
+
+ int sel = gtk_option_menu_get_history(menu);
+ if(sel == 2) /* Default */
+ return NULL;
+ if(sel == 1) /* Disabled */
+ return "none";
+
+ GtkLabel *l = GTK_LABEL(gtk_bin_get_child(GTK_BIN(menu)));
+ return gtk_label_get_text(l);
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/gtktimezonetest.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,343 @@
+/*************************************************************************
+ * GTK Timezone test program
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * A test program to play with different ways that user could select from
+ * the huge list of timezones. Eventually things tested here should migrate
+ * to the module itself.
+ *************************************************************************/
+
+#include <gtk/gtk.h>
+#include <string.h>
+#include <ctype.h>
+#include "recurse.h"
+
+#define PACKAGE "Hello World"
+#define VERSION "0.1"
+
+#define DISABLED_STRING "<Disabled>"
+#define DEFAULT_STRING "<Default>"
+#define MORE_STRING "More..."
+/*
+ * Terminate the main loop.
+ */
+static void
+on_destroy(GtkWidget * widget, gpointer data)
+{
+ gtk_main_quit();
+}
+
+enum
+{
+ STRING_COLUMN,
+ N_COLUMNS
+};
+
+struct nodestate
+{
+#ifdef USE_COMBOBOX
+ GtkTreeIter iter;
+#else
+ GtkWidget *submenu;
+#endif
+ gchar *string;
+};
+
+struct state
+{
+#ifdef USE_COMBOBOX
+ GtkTreeStore *store;
+ GtkTreeIter *extra;
+#else
+ GtkWidget *base;
+ GtkWidget *extra;
+#endif
+ int currdepth;
+ struct nodestate stack[4];
+};
+
+static inline const char *
+menuitem_get_label(GtkMenuItem * menuitem)
+{
+ return gtk_label_get_text(GTK_LABEL(gtk_bin_get_child(GTK_BIN(menuitem))));
+}
+
+static GtkWidget *
+menu_get_first_menuitem(GtkMenu * menu)
+{
+ GList *list = gtk_container_get_children(GTK_CONTAINER(menu));
+ GtkWidget *selection = GTK_WIDGET(g_list_nth_data(list, 0));
+ g_list_free(list);
+ return selection;
+}
+
+int
+menu_select_cb(GtkMenuItem * menuitem, GtkWidget * menu)
+{
+ const char *label = menuitem_get_label(menuitem);
+
+// printf( "menuitem = %s(%p), menu = %s(%p)\n", G_OBJECT_TYPE_NAME(menuitem), menuitem, G_OBJECT_TYPE_NAME(menu), menu );
+
+ if(label[0] == '<')
+ {
+ GtkWidget *selection;
+ gchar *selstring;
+
+ if(strcmp(label, DEFAULT_STRING) == 0)
+ selstring = "";
+ else if(strcmp(label, DISABLED_STRING) == 0)
+ selstring = "none";
+
+ selection = menu_get_first_menuitem(GTK_MENU(menu));
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ char *str = g_strdup(label);
+
+ GtkWidget *parent;
+
+ for (;;)
+ {
+ GtkMenuItem *parentitem;
+ const char *label2;
+ char *temp;
+
+ parent = gtk_widget_get_parent(GTK_WIDGET(menuitem));
+// printf( "parent = %s(%p)\n", G_OBJECT_TYPE_NAME(parent), parent);
+ if(menu == parent)
+ break;
+
+ parentitem = GTK_MENU_ITEM(gtk_menu_get_attach_widget(GTK_MENU(parent)));
+// printf( "parentitem = %s(%p)\n", G_OBJECT_TYPE_NAME(parentitem), parentitem);
+ label2 = menuitem_get_label(parentitem);
+ if(strcmp(label2, MORE_STRING) != 0)
+ {
+ temp = g_strconcat(label2, "/", str, NULL);
+ g_free(str);
+ str = temp;
+ }
+
+ menuitem = parentitem;
+ }
+ {
+ GtkLabel *label;
+
+ GtkWidget *selection = menu_get_first_menuitem(GTK_MENU(menu));
+ GtkOptionMenu *optionmenu = GTK_OPTION_MENU(gtk_menu_get_attach_widget(GTK_MENU(menu)));
+
+ label = GTK_LABEL(gtk_bin_get_child(GTK_BIN(selection)));
+
+ gtk_label_set_text(label, str);
+ gtk_widget_show(GTK_WIDGET(selection));
+ gtk_option_menu_set_history(optionmenu, 0);
+ g_free(str);
+ }
+ }
+ return 0;
+}
+
+int
+make_menu_cb(char *path, struct state *state)
+{
+ int i, j;
+
+ char **elements;
+
+ /* Here we ignore strings not beginning with uppercase, since they are auxilliary files, not timezones */
+ if(!isupper(path[0]))
+ return 0;
+
+ elements = g_strsplit(path, "/", 4);
+
+ for (i = 0; i < state->currdepth && state->stack[i].string; i++)
+ {
+ if(strcmp(elements[i], state->stack[i].string) != 0)
+ break;
+ }
+ /* i is now the index of the first non-matching element, so free the rest */
+ for (j = i; j < state->currdepth; j++)
+ g_free(state->stack[j].string);
+ state->currdepth = i;
+
+ while (elements[i])
+ {
+#ifdef USE_COMBOBOX
+ GtkTreeIter *parent = (i == 0) ? NULL : &state->stack[i - 1].iter;
+#else
+ GtkWidget *parent = (i == 0) ? state->base : state->stack[i - 1].submenu;
+ GtkWidget *menuitem;
+#endif
+
+ if(i == 0 && elements[1] == NULL)
+ parent = state->extra;
+
+#ifdef USE_COMBOBOX
+ gtk_tree_store_append(state->store, &state->stack[i].iter, parent);
+ gtk_tree_store_set(state->store, &state->stack[i].iter, STRING_COLUMN, elements[i], -1);
+ state->stack[i].string = g_strdup(elements[i]);
+ state->currdepth++;
+#else
+ menuitem = gtk_menu_item_new_with_label(elements[i]);
+ gtk_menu_append(parent, menuitem);
+
+ if(elements[i + 1] != NULL) /* Has submenu */
+ {
+ state->stack[i].submenu = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state->stack[i].submenu);
+
+ state->currdepth++;
+ state->stack[i].string = g_strdup(elements[i]);
+ }
+ else
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb),
+ state->base);
+
+#endif
+
+ i++;
+ }
+ g_strfreev(elements);
+ return 0;
+}
+
+GtkWidget *
+make_menu2(char *selected)
+{
+ int i;
+ struct state state;
+
+#ifdef USE_COMBOBOX
+ GtkTreeStore *store = gtk_tree_store_new(N_COLUMNS, /* Total number of columns */
+ G_TYPE_STRING); /* Timezone */
+ GtkWidget *tree;
+
+ GtkCellRenderer *renderer;
+ GtkTreeIter iter1, iter2;
+
+ gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */
+ gtk_tree_store_set(store, &iter1, STRING_COLUMN, DISABLED_STRING, -1);
+ gtk_tree_store_append(store, &iter1, NULL); /* Acquire an iterator */
+ gtk_tree_store_set(store, &iter1, STRING_COLUMN, DEFAULT_STRING, -1);
+ gtk_tree_store_append(store, &iter2, &iter1);
+ gtk_tree_store_set(store, &iter1, STRING_COLUMN, MORE_STRING, -1);
+
+ state.store = store;
+ state.extra = &iter1;
+#else
+
+ GtkWidget *menu;
+ GtkWidget *optionmenu, *menuitem, *selection;
+
+ menu = gtk_menu_new();
+ menuitem = gtk_menu_item_new_with_label(selected);
+ gtk_menu_append(menu, menuitem);
+ selection = menuitem;
+
+ menuitem = gtk_menu_item_new_with_label(DISABLED_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(DEFAULT_STRING);
+ g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(menu_select_cb), menu);
+ gtk_menu_append(menu, menuitem);
+ menuitem = gtk_menu_item_new_with_label(MORE_STRING);
+ gtk_menu_append(menu, menuitem);
+
+ state.base = menu;
+ state.extra = gtk_menu_new();
+ gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), state.extra);
+#endif
+ state.currdepth = 0;
+
+ recurse_directory("/usr/share/zoneinfo", (DirRecurseMatch) make_menu_cb, &state);
+
+ for (i = 0; i < state.currdepth; i++)
+ g_free(state.stack[i].string);
+
+#ifdef USE_COMBOBOX
+ tree = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+
+ renderer = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(tree), renderer, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(tree), renderer, "text", 0, NULL);
+
+ gtk_widget_show_all(tree);
+ return tree;
+#else
+ optionmenu = gtk_option_menu_new();
+ gtk_option_menu_set_menu(GTK_OPTION_MENU(optionmenu), menu);
+ gtk_widget_show_all(optionmenu);
+
+ if(strcmp(selected, "") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 2);
+ gtk_widget_hide(selection);
+ }
+ else if(strcmp(selected, "none") == 0)
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 1);
+ gtk_widget_hide(selection);
+ }
+ else
+ {
+ gtk_option_menu_set_history(GTK_OPTION_MENU(optionmenu), 0);
+ }
+
+ return optionmenu;
+#endif
+
+}
+
+int
+main(int argc, char *argv[])
+{
+ GtkWidget *window;
+ GtkWidget *label;
+ GtkWidget *menu;
+ GtkWidget *frame;
+
+ gtk_init(&argc, &argv);
+
+ /* create the main, top level, window */
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+
+ /* give the window a 20px wide border */
+ gtk_container_set_border_width(GTK_CONTAINER(window), 20);
+
+ /* give it the title */
+ gtk_window_set_title(GTK_WINDOW(window), PACKAGE " " VERSION);
+
+ /* open it a bit wider so that both the label and title show up */
+ gtk_window_set_default_size(GTK_WINDOW(window), 200, 50);
+
+ /* Connect the destroy event of the window with our on_destroy function
+ * When the window is about to be destroyed we get a notificaiton and
+ * stop the main GTK loop
+ */
+ g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(on_destroy), NULL);
+
+ /* Create the "Hello, World" label */
+ label = gtk_label_new("Select a timezone:");
+ gtk_widget_show(label);
+
+ frame = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(frame);
+
+ /* and insert it into the main window */
+ gtk_container_add(GTK_CONTAINER(window), frame);
+ gtk_container_add(GTK_CONTAINER(frame), label);
+
+ menu = make_menu2("none");
+
+ gtk_container_add(GTK_CONTAINER(frame), menu);
+ gtk_widget_show(menu);
+
+ /* make sure that everything, window and label, are visible */
+ gtk_widget_show(window);
+
+ /* start the main loop */
+ gtk_main();
+
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/localtime.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,1250 @@
+/*************************************************************************
+ * Timezone Module
+ * copied from the Olson Timezone code, licence unchanged.
+ * by Martijn van Oosterhout <kleptog@svana.org> April 2006
+ * Original Licence below (public domain).
+ *
+ * This code has been copied from the Olson Timezone code, but heavily
+ * adapted to meet my needs. In particular, you can load multiple timezones
+ * and specify which timezone to convert with.
+ *************************************************************************/
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+#define TM_GMTOFF tm_gmtoff
+#define TM_ZONE tm_zone
+
+/*
+** Leap second handling from Bradley White.
+** POSIX-style TZ environment variable handling from Guy Harris.
+*/
+
+/*LINTLIBRARY*/
+
+#include "private.h"
+#include "tzfile.h"
+#include "fcntl.h"
+#include "float.h" /* for FLT_MAX and DBL_MAX */
+
+#ifndef TZ_ABBR_MAX_LEN
+#define TZ_ABBR_MAX_LEN 16
+#endif /* !defined TZ_ABBR_MAX_LEN */
+
+#ifndef TZ_ABBR_CHAR_SET
+#define TZ_ABBR_CHAR_SET \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._"
+#endif /* !defined TZ_ABBR_CHAR_SET */
+
+#ifndef TZ_ABBR_ERR_CHAR
+#define TZ_ABBR_ERR_CHAR '_'
+#endif /* !defined TZ_ABBR_ERR_CHAR */
+
+/*
+** SunOS 4.1.1 headers lack O_BINARY.
+*/
+
+#ifdef O_BINARY
+#define OPEN_MODE (O_RDONLY | O_BINARY)
+#endif /* defined O_BINARY */
+#ifndef O_BINARY
+#define OPEN_MODE O_RDONLY
+#endif /* !defined O_BINARY */
+
+#ifndef WILDABBR
+/*
+** Someone might make incorrect use of a time zone abbreviation:
+** 1. They might reference tzname[0] before calling tzset (explicitly
+** or implicitly).
+** 2. They might reference tzname[1] before calling tzset (explicitly
+** or implicitly).
+** 3. They might reference tzname[1] after setting to a time zone
+** in which Daylight Saving Time is never observed.
+** 4. They might reference tzname[0] after setting to a time zone
+** in which Standard Time is never observed.
+** 5. They might reference tm.TM_ZONE after calling offtime.
+** What's best to do in the above cases is open to debate;
+** for now, we just set things up so that in any of the five cases
+** WILDABBR is used. Another possibility: initialize tzname[0] to the
+** string "tzname[0] used before set", and similarly for the other cases.
+** And another: initialize tzname[0] to "ERA", with an explanation in the
+** manual page of what this "time zone abbreviation" means (doing this so
+** that tzname[0] has the "normal" length of three characters).
+*/
+#define WILDABBR " "
+#endif /* !defined WILDABBR */
+
+static char wildabbr[] = WILDABBR;
+
+static const char gmt[] = "GMT";
+
+static char *tzdir;
+
+/*
+** The DST rules to use if TZ has no rules and we can't load TZDEFRULES.
+** We default to US rules as of 1999-08-17.
+** POSIX 1003.1 section 8.1.1 says that the default DST rules are
+** implementation dependent; for historical reasons, US rules are a
+** common default.
+*/
+#ifndef TZDEFRULESTRING
+#define TZDEFRULESTRING ",M4.1.0,M10.5.0"
+#endif /* !defined TZDEFDST */
+
+struct ttinfo { /* time type information */
+ long tt_gmtoff; /* UTC offset in seconds */
+ int tt_isdst; /* used to set tm_isdst */
+ int tt_abbrind; /* abbreviation list index */
+ int tt_ttisstd; /* TRUE if transition is std time */
+ int tt_ttisgmt; /* TRUE if transition is UTC */
+};
+
+struct lsinfo { /* leap second information */
+ time_t ls_trans; /* transition time */
+ long ls_corr; /* correction to apply */
+};
+
+#define BIGGEST(a, b) (((a) > (b)) ? (a) : (b))
+
+#ifdef TZNAME_MAX
+#define MY_TZNAME_MAX TZNAME_MAX
+#endif /* defined TZNAME_MAX */
+#ifndef TZNAME_MAX
+#define MY_TZNAME_MAX 255
+#endif /* !defined TZNAME_MAX */
+
+struct state {
+ int leapcnt;
+ int timecnt;
+ int typecnt;
+ int charcnt;
+ time_t ats[TZ_MAX_TIMES];
+ unsigned char types[TZ_MAX_TIMES];
+ struct ttinfo ttis[TZ_MAX_TYPES];
+ char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt),
+ (2 * (MY_TZNAME_MAX + 1)))];
+ struct lsinfo lsis[TZ_MAX_LEAPS];
+};
+
+struct rule {
+ int r_type; /* type of rule--see below */
+ int r_day; /* day number of rule */
+ int r_week; /* week number of rule */
+ int r_mon; /* month number of rule */
+ long r_time; /* transition time of rule */
+};
+
+#define JULIAN_DAY 0 /* Jn - Julian day */
+#define DAY_OF_YEAR 1 /* n - day of year */
+#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */
+
+/*
+** Prototypes for static functions.
+*/
+
+static long detzcode P((const char * codep));
+static const char * getzname P((const char * strp));
+static const char * getqzname P((const char * strp, const char delim));
+static const char * getnum P((const char * strp, int * nump, int min,
+ int max));
+static const char * getsecs P((const char * strp, long * secsp));
+static const char * getoffset P((const char * strp, long * offsetp));
+static const char * getrule P((const char * strp, struct rule * rulep));
+static void gmtload P((struct state * sp));
+struct tm * gmtsub P((const time_t * timep, long offset,
+ struct tm * tmp));
+struct tm * localsub P((const time_t * timep, long offset,
+ struct tm * tmp, struct state *sp));
+static int increment_overflow P((int * number, int delta));
+static int leaps_thru_end_of P((int y));
+static struct tm * timesub P((const time_t * timep, long offset,
+ const struct state * sp, struct tm * tmp));
+static time_t transtime P((time_t janfirst, int year,
+ const struct rule * rulep, long offset));
+static int tzload P((const char * name, struct state * sp));
+static int tzparse P((const char * name, struct state * sp,
+ int lastditch));
+
+struct state *timezone_load P((const char * name));
+
+#ifdef ALL_STATE
+static struct state * gmtptr;
+#endif /* defined ALL_STATE */
+
+#ifndef ALL_STATE
+static struct state gmtmem;
+#define gmtptr (&gmtmem)
+#endif /* State Farm */
+
+#ifndef TZ_STRLEN_MAX
+#define TZ_STRLEN_MAX 255
+#endif /* !defined TZ_STRLEN_MAX */
+
+//static char lcl_TZname[TZ_STRLEN_MAX + 1];
+//static int lcl_is_set;
+static int gmt_is_set;
+
+/*
+** Section 4.12.3 of X3.159-1989 requires that
+** Except for the strftime function, these functions [asctime,
+** ctime, gmtime, localtime] return values in one of two static
+** objects: a broken-down time structure and an array of char.
+** Thanks to Paul Eggert for noting this.
+*/
+
+//static struct tm tm;
+
+#ifdef USG_COMPAT
+time_t timezone = 0;
+int daylight = 0;
+#endif /* defined USG_COMPAT */
+
+#ifdef ALTZONE
+time_t altzone = 0;
+#endif /* defined ALTZONE */
+
+static long
+detzcode(codep)
+const char * const codep;
+{
+ register long result;
+ register int i;
+
+ result = (codep[0] & 0x80) ? ~0L : 0L;
+ for (i = 0; i < 4; ++i)
+ result = (result << 8) | (codep[i] & 0xff);
+ return result;
+}
+
+static int
+tzload(name, sp)
+register const char * name;
+register struct state * const sp;
+{
+ register const char * p;
+ register int i;
+ register int fid;
+
+ if (name == NULL && (name = TZDEFAULT) == NULL)
+ return -1;
+ {
+ register int doaccess;
+ /*
+ ** Section 4.9.1 of the C standard says that
+ ** "FILENAME_MAX expands to an integral constant expression
+ ** that is the size needed for an array of char large enough
+ ** to hold the longest file name string that the implementation
+ ** guarantees can be opened."
+ */
+ char fullname[FILENAME_MAX + 1];
+
+ if (name[0] == ':')
+ ++name;
+ doaccess = name[0] == '/';
+ if (!doaccess) {
+ if ((p = tzdir) == NULL)
+ return -1;
+ if ((strlen(p) + strlen(name) + 1) >= sizeof fullname)
+ return -1;
+ (void) strcpy(fullname, p);
+ (void) strcat(fullname, "/");
+ (void) strcat(fullname, name);
+ /*
+ ** Set doaccess if '.' (as in "../") shows up in name.
+ */
+ if (strchr(name, '.') != NULL)
+ doaccess = TRUE;
+ name = fullname;
+ }
+ if (doaccess && access(name, R_OK) != 0)
+ return -1;
+ if ((fid = open(name, OPEN_MODE)) == -1)
+ return -1;
+ }
+ {
+ struct tzhead * tzhp;
+ union {
+ struct tzhead tzhead;
+ char buf[sizeof *sp + sizeof *tzhp];
+ } u;
+ int ttisstdcnt;
+ int ttisgmtcnt;
+
+ i = read(fid, u.buf, sizeof u.buf);
+ if (close(fid) != 0)
+ return -1;
+ ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt);
+ ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt);
+ sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt);
+ sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt);
+ sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt);
+ sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt);
+ p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt;
+ if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS ||
+ sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES ||
+ sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES ||
+ sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS ||
+ (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) ||
+ (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0))
+ return -1;
+ if (i - (p - u.buf) < sp->timecnt * 4 + /* ats */
+ sp->timecnt + /* types */
+ sp->typecnt * (4 + 2) + /* ttinfos */
+ sp->charcnt + /* chars */
+ sp->leapcnt * (4 + 4) + /* lsinfos */
+ ttisstdcnt + /* ttisstds */
+ ttisgmtcnt) /* ttisgmts */
+ return -1;
+ for (i = 0; i < sp->timecnt; ++i) {
+ sp->ats[i] = detzcode(p);
+ p += 4;
+ }
+ for (i = 0; i < sp->timecnt; ++i) {
+ sp->types[i] = (unsigned char) *p++;
+ if (sp->types[i] >= sp->typecnt)
+ return -1;
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ ttisp->tt_gmtoff = detzcode(p);
+ p += 4;
+ ttisp->tt_isdst = (unsigned char) *p++;
+ if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1)
+ return -1;
+ ttisp->tt_abbrind = (unsigned char) *p++;
+ if (ttisp->tt_abbrind < 0 ||
+ ttisp->tt_abbrind > sp->charcnt)
+ return -1;
+ }
+ for (i = 0; i < sp->charcnt; ++i)
+ sp->chars[i] = *p++;
+ sp->chars[i] = '\0'; /* ensure '\0' at end */
+ for (i = 0; i < sp->leapcnt; ++i) {
+ register struct lsinfo * lsisp;
+
+ lsisp = &sp->lsis[i];
+ lsisp->ls_trans = detzcode(p);
+ p += 4;
+ lsisp->ls_corr = detzcode(p);
+ p += 4;
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ if (ttisstdcnt == 0)
+ ttisp->tt_ttisstd = FALSE;
+ else {
+ ttisp->tt_ttisstd = *p++;
+ if (ttisp->tt_ttisstd != TRUE &&
+ ttisp->tt_ttisstd != FALSE)
+ return -1;
+ }
+ }
+ for (i = 0; i < sp->typecnt; ++i) {
+ register struct ttinfo * ttisp;
+
+ ttisp = &sp->ttis[i];
+ if (ttisgmtcnt == 0)
+ ttisp->tt_ttisgmt = FALSE;
+ else {
+ ttisp->tt_ttisgmt = *p++;
+ if (ttisp->tt_ttisgmt != TRUE &&
+ ttisp->tt_ttisgmt != FALSE)
+ return -1;
+ }
+ }
+ /*
+ ** Out-of-sort ats should mean we're running on a
+ ** signed time_t system but using a data file with
+ ** unsigned values (or vice versa).
+ */
+ for (i = 0; i < sp->timecnt - 2; ++i)
+ if (sp->ats[i] > sp->ats[i + 1]) {
+ ++i;
+ if (TYPE_SIGNED(time_t)) {
+ /*
+ ** Ignore the end (easy).
+ */
+ sp->timecnt = i;
+ } else {
+ /*
+ ** Ignore the beginning (harder).
+ */
+ register int j;
+
+ for (j = 0; j + i < sp->timecnt; ++j) {
+ sp->ats[j] = sp->ats[j + i];
+ sp->types[j] = sp->types[j + i];
+ }
+ sp->timecnt = j;
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+struct state *timezone_load(name)
+const char * name;
+{
+ struct state *sp = malloc( sizeof(struct state) );
+ int res;
+
+ if( !sp )
+ return NULL;
+ res = tzload( name, sp );
+ if( res < 0 )
+ {
+ free(sp);
+ return NULL;
+ }
+ return sp;
+}
+
+
+static const int mon_lengths[2][MONSPERYEAR] = {
+ { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+ { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+};
+
+static const int year_lengths[2] = {
+ DAYSPERNYEAR, DAYSPERLYEAR
+};
+
+/*
+** Given a pointer into a time zone string, scan until a character that is not
+** a valid character in a zone name is found. Return a pointer to that
+** character.
+*/
+
+static const char *
+getzname(strp)
+register const char * strp;
+{
+ register char c;
+
+ while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' &&
+ c != '+')
+ ++strp;
+ return strp;
+}
+
+/*
+** Given a pointer into an extended time zone string, scan until the ending
+** delimiter of the zone name is located. Return a pointer to the delimiter.
+**
+** As with getzname above, the legal character set is actually quite
+** restricted, with other characters producing undefined results.
+** We choose not to care - allowing almost anything to be in the zone abbrev.
+*/
+
+static const char *
+#if __STDC__
+getqzname(register const char *strp, const char delim)
+#else /* !__STDC__ */
+getqzname(strp, delim)
+register const char * strp;
+const char delim;
+#endif /* !__STDC__ */
+{
+ register char c;
+
+ while ((c = *strp) != '\0' && c != delim)
+ ++strp;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract a number from that string.
+** Check that the number is within a specified range; if it is not, return
+** NULL.
+** Otherwise, return a pointer to the first character not part of the number.
+*/
+
+static const char *
+getnum(strp, nump, min, max)
+register const char * strp;
+int * const nump;
+const int min;
+const int max;
+{
+ register char c;
+ register int num;
+
+ if (strp == NULL || !is_digit(c = *strp))
+ return NULL;
+ num = 0;
+ do {
+ num = num * 10 + (c - '0');
+ if (num > max)
+ return NULL; /* illegal value */
+ c = *++strp;
+ } while (is_digit(c));
+ if (num < min)
+ return NULL; /* illegal value */
+ *nump = num;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract a number of seconds,
+** in hh[:mm[:ss]] form, from the string.
+** If any error occurs, return NULL.
+** Otherwise, return a pointer to the first character not part of the number
+** of seconds.
+*/
+
+static const char *
+getsecs(strp, secsp)
+register const char * strp;
+long * const secsp;
+{
+ int num;
+
+ /*
+ ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
+ ** "M10.4.6/26", which does not conform to Posix,
+ ** but which specifies the equivalent of
+ ** ``02:00 on the first Sunday on or after 23 Oct''.
+ */
+ strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
+ if (strp == NULL)
+ return NULL;
+ *secsp = num * (long) SECSPERHOUR;
+ if (*strp == ':') {
+ ++strp;
+ strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
+ if (strp == NULL)
+ return NULL;
+ *secsp += num * SECSPERMIN;
+ if (*strp == ':') {
+ ++strp;
+ /* `SECSPERMIN' allows for leap seconds. */
+ strp = getnum(strp, &num, 0, SECSPERMIN);
+ if (strp == NULL)
+ return NULL;
+ *secsp += num;
+ }
+ }
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract an offset, in
+** [+-]hh[:mm[:ss]] form, from the string.
+** If any error occurs, return NULL.
+** Otherwise, return a pointer to the first character not part of the time.
+*/
+
+static const char *
+getoffset(strp, offsetp)
+register const char * strp;
+long * const offsetp;
+{
+ register int neg = 0;
+
+ if (*strp == '-') {
+ neg = 1;
+ ++strp;
+ } else if (*strp == '+')
+ ++strp;
+ strp = getsecs(strp, offsetp);
+ if (strp == NULL)
+ return NULL; /* illegal time */
+ if (neg)
+ *offsetp = -*offsetp;
+ return strp;
+}
+
+/*
+** Given a pointer into a time zone string, extract a rule in the form
+** date[/time]. See POSIX section 8 for the format of "date" and "time".
+** If a valid rule is not found, return NULL.
+** Otherwise, return a pointer to the first character not part of the rule.
+*/
+
+static const char *
+getrule(strp, rulep)
+const char * strp;
+register struct rule * const rulep;
+{
+ if (*strp == 'J') {
+ /*
+ ** Julian day.
+ */
+ rulep->r_type = JULIAN_DAY;
+ ++strp;
+ strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);
+ } else if (*strp == 'M') {
+ /*
+ ** Month, week, day.
+ */
+ rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
+ ++strp;
+ strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
+ if (strp == NULL)
+ return NULL;
+ if (*strp++ != '.')
+ return NULL;
+ strp = getnum(strp, &rulep->r_week, 1, 5);
+ if (strp == NULL)
+ return NULL;
+ if (*strp++ != '.')
+ return NULL;
+ strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
+ } else if (is_digit(*strp)) {
+ /*
+ ** Day of year.
+ */
+ rulep->r_type = DAY_OF_YEAR;
+ strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
+ } else return NULL; /* invalid format */
+ if (strp == NULL)
+ return NULL;
+ if (*strp == '/') {
+ /*
+ ** Time specified.
+ */
+ ++strp;
+ strp = getsecs(strp, &rulep->r_time);
+ } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
+ return strp;
+}
+
+/*
+** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the
+** year, a rule, and the offset from UTC at the time that rule takes effect,
+** calculate the Epoch-relative time that rule takes effect.
+*/
+
+static time_t
+transtime(janfirst, year, rulep, offset)
+const time_t janfirst;
+const int year;
+register const struct rule * const rulep;
+const long offset;
+{
+ register int leapyear;
+ register time_t value;
+ register int i;
+ int d, m1, yy0, yy1, yy2, dow;
+
+ INITIALIZE(value);
+ leapyear = isleap(year);
+ switch (rulep->r_type) {
+
+ case JULIAN_DAY:
+ /*
+ ** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
+ ** years.
+ ** In non-leap years, or if the day number is 59 or less, just
+ ** add SECSPERDAY times the day number-1 to the time of
+ ** January 1, midnight, to get the day.
+ */
+ value = janfirst + (rulep->r_day - 1) * SECSPERDAY;
+ if (leapyear && rulep->r_day >= 60)
+ value += SECSPERDAY;
+ break;
+
+ case DAY_OF_YEAR:
+ /*
+ ** n - day of year.
+ ** Just add SECSPERDAY times the day number to the time of
+ ** January 1, midnight, to get the day.
+ */
+ value = janfirst + rulep->r_day * SECSPERDAY;
+ break;
+
+ case MONTH_NTH_DAY_OF_WEEK:
+ /*
+ ** Mm.n.d - nth "dth day" of month m.
+ */
+ value = janfirst;
+ for (i = 0; i < rulep->r_mon - 1; ++i)
+ value += mon_lengths[leapyear][i] * SECSPERDAY;
+
+ /*
+ ** Use Zeller's Congruence to get day-of-week of first day of
+ ** month.
+ */
+ m1 = (rulep->r_mon + 9) % 12 + 1;
+ yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
+ yy1 = yy0 / 100;
+ yy2 = yy0 % 100;
+ dow = ((26 * m1 - 2) / 10 +
+ 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
+ if (dow < 0)
+ dow += DAYSPERWEEK;
+
+ /*
+ ** "dow" is the day-of-week of the first day of the month. Get
+ ** the day-of-month (zero-origin) of the first "dow" day of the
+ ** month.
+ */
+ d = rulep->r_day - dow;
+ if (d < 0)
+ d += DAYSPERWEEK;
+ for (i = 1; i < rulep->r_week; ++i) {
+ if (d + DAYSPERWEEK >=
+ mon_lengths[leapyear][rulep->r_mon - 1])
+ break;
+ d += DAYSPERWEEK;
+ }
+
+ /*
+ ** "d" is the day-of-month (zero-origin) of the day we want.
+ */
+ value += d * SECSPERDAY;
+ break;
+ }
+
+ /*
+ ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in
+ ** question. To get the Epoch-relative time of the specified local
+ ** time on that day, add the transition time and the current offset
+ ** from UTC.
+ */
+ return value + rulep->r_time + offset;
+}
+
+/*
+** Given a POSIX section 8-style TZ string, fill in the rule tables as
+** appropriate.
+*/
+
+static int
+tzparse(name, sp, lastditch)
+const char * name;
+register struct state * const sp;
+const int lastditch;
+{
+ const char * stdname;
+ const char * dstname;
+ size_t stdlen;
+ size_t dstlen;
+ long stdoffset;
+ long dstoffset;
+ register time_t * atp;
+ register unsigned char * typep;
+ register char * cp;
+ register int load_result;
+
+ INITIALIZE(dstname);
+ stdname = name;
+ if (lastditch) {
+ stdlen = strlen(name); /* length of standard zone name */
+ name += stdlen;
+ if (stdlen >= sizeof sp->chars)
+ stdlen = (sizeof sp->chars) - 1;
+ stdoffset = 0;
+ } else {
+ if (*name == '<') {
+ name++;
+ stdname = name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return (-1);
+ stdlen = name - stdname;
+ name++;
+ } else {
+ name = getzname(name);
+ stdlen = name - stdname;
+ }
+ if (*name == '\0')
+ return -1;
+ name = getoffset(name, &stdoffset);
+ if (name == NULL)
+ return -1;
+ }
+ load_result = tzload(TZDEFRULES, sp);
+ if (load_result != 0)
+ sp->leapcnt = 0; /* so, we're off a little */
+ if (*name != '\0') {
+ if (*name == '<') {
+ dstname = ++name;
+ name = getqzname(name, '>');
+ if (*name != '>')
+ return -1;
+ dstlen = name - dstname;
+ name++;
+ } else {
+ dstname = name;
+ name = getzname(name);
+ dstlen = name - dstname; /* length of DST zone name */
+ }
+ if (*name != '\0' && *name != ',' && *name != ';') {
+ name = getoffset(name, &dstoffset);
+ if (name == NULL)
+ return -1;
+ } else dstoffset = stdoffset - SECSPERHOUR;
+ if (*name == '\0' && load_result != 0)
+ name = TZDEFRULESTRING;
+ if (*name == ',' || *name == ';') {
+ struct rule start;
+ struct rule end;
+ register int year;
+ register time_t janfirst;
+ time_t starttime;
+ time_t endtime;
+
+ ++name;
+ if ((name = getrule(name, &start)) == NULL)
+ return -1;
+ if (*name++ != ',')
+ return -1;
+ if ((name = getrule(name, &end)) == NULL)
+ return -1;
+ if (*name != '\0')
+ return -1;
+ sp->typecnt = 2; /* standard time and DST */
+ /*
+ ** Two transitions per year, from EPOCH_YEAR to 2037.
+ */
+ sp->timecnt = 2 * (2037 - EPOCH_YEAR + 1);
+ if (sp->timecnt > TZ_MAX_TIMES)
+ return -1;
+ sp->ttis[0].tt_gmtoff = -dstoffset;
+ sp->ttis[0].tt_isdst = 1;
+ sp->ttis[0].tt_abbrind = stdlen + 1;
+ sp->ttis[1].tt_gmtoff = -stdoffset;
+ sp->ttis[1].tt_isdst = 0;
+ sp->ttis[1].tt_abbrind = 0;
+ atp = sp->ats;
+ typep = sp->types;
+ janfirst = 0;
+ for (year = EPOCH_YEAR; year <= 2037; ++year) {
+ starttime = transtime(janfirst, year, &start,
+ stdoffset);
+ endtime = transtime(janfirst, year, &end,
+ dstoffset);
+ if (starttime > endtime) {
+ *atp++ = endtime;
+ *typep++ = 1; /* DST ends */
+ *atp++ = starttime;
+ *typep++ = 0; /* DST begins */
+ } else {
+ *atp++ = starttime;
+ *typep++ = 0; /* DST begins */
+ *atp++ = endtime;
+ *typep++ = 1; /* DST ends */
+ }
+ janfirst += year_lengths[isleap(year)] *
+ SECSPERDAY;
+ }
+ } else {
+ register long theirstdoffset;
+ register long theirdstoffset;
+ register long theiroffset;
+ register int isdst;
+ register int i;
+ register int j;
+
+ if (*name != '\0')
+ return -1;
+ /*
+ ** Initial values of theirstdoffset and theirdstoffset.
+ */
+ theirstdoffset = 0;
+ for (i = 0; i < sp->timecnt; ++i) {
+ j = sp->types[i];
+ if (!sp->ttis[j].tt_isdst) {
+ theirstdoffset =
+ -sp->ttis[j].tt_gmtoff;
+ break;
+ }
+ }
+ theirdstoffset = 0;
+ for (i = 0; i < sp->timecnt; ++i) {
+ j = sp->types[i];
+ if (sp->ttis[j].tt_isdst) {
+ theirdstoffset =
+ -sp->ttis[j].tt_gmtoff;
+ break;
+ }
+ }
+ /*
+ ** Initially we're assumed to be in standard time.
+ */
+ isdst = FALSE;
+ theiroffset = theirstdoffset;
+ /*
+ ** Now juggle transition times and types
+ ** tracking offsets as you do.
+ */
+ for (i = 0; i < sp->timecnt; ++i) {
+ j = sp->types[i];
+ sp->types[i] = sp->ttis[j].tt_isdst;
+ if (sp->ttis[j].tt_ttisgmt) {
+ /* No adjustment to transition time */
+ } else {
+ /*
+ ** If summer time is in effect, and the
+ ** transition time was not specified as
+ ** standard time, add the summer time
+ ** offset to the transition time;
+ ** otherwise, add the standard time
+ ** offset to the transition time.
+ */
+ /*
+ ** Transitions from DST to DDST
+ ** will effectively disappear since
+ ** POSIX provides for only one DST
+ ** offset.
+ */
+ if (isdst && !sp->ttis[j].tt_ttisstd) {
+ sp->ats[i] += dstoffset -
+ theirdstoffset;
+ } else {
+ sp->ats[i] += stdoffset -
+ theirstdoffset;
+ }
+ }
+ theiroffset = -sp->ttis[j].tt_gmtoff;
+ if (sp->ttis[j].tt_isdst)
+ theirdstoffset = theiroffset;
+ else theirstdoffset = theiroffset;
+ }
+ /*
+ ** Finally, fill in ttis.
+ ** ttisstd and ttisgmt need not be handled.
+ */
+ sp->ttis[0].tt_gmtoff = -stdoffset;
+ sp->ttis[0].tt_isdst = FALSE;
+ sp->ttis[0].tt_abbrind = 0;
+ sp->ttis[1].tt_gmtoff = -dstoffset;
+ sp->ttis[1].tt_isdst = TRUE;
+ sp->ttis[1].tt_abbrind = stdlen + 1;
+ sp->typecnt = 2;
+ }
+ } else {
+ dstlen = 0;
+ sp->typecnt = 1; /* only standard time */
+ sp->timecnt = 0;
+ sp->ttis[0].tt_gmtoff = -stdoffset;
+ sp->ttis[0].tt_isdst = 0;
+ sp->ttis[0].tt_abbrind = 0;
+ }
+ sp->charcnt = stdlen + 1;
+ if (dstlen != 0)
+ sp->charcnt += dstlen + 1;
+ if ((size_t) sp->charcnt > sizeof sp->chars)
+ return -1;
+ cp = sp->chars;
+ (void) strncpy(cp, stdname, stdlen);
+ cp += stdlen;
+ *cp++ = '\0';
+ if (dstlen != 0) {
+ (void) strncpy(cp, dstname, dstlen);
+ *(cp + dstlen) = '\0';
+ }
+ return 0;
+}
+
+static void
+gmtload(sp)
+struct state * const sp;
+{
+ if (tzload(gmt, sp) != 0)
+ (void) tzparse(gmt, sp, TRUE);
+}
+
+/*
+** The easy way to behave "as if no library function calls" localtime
+** is to not call it--so we drop its guts into "localsub", which can be
+** freely called. (And no, the PANS doesn't require the above behavior--
+** but it *is* desirable.)
+**
+** The unused offset argument is for the benefit of mktime variants.
+*/
+
+struct tm *
+localsub(timep, offset, tmp, sp)
+const time_t * const timep;
+const long offset;
+struct tm * const tmp;
+struct state * sp;
+{
+ register const struct ttinfo * ttisp;
+ register int i;
+ register struct tm * result;
+ const time_t t = *timep;
+
+#ifdef ALL_STATE
+ if (sp == NULL)
+ return gmtsub(timep, offset, tmp);
+#endif /* defined ALL_STATE */
+ if (sp->timecnt == 0 || t < sp->ats[0]) {
+ i = 0;
+ while (sp->ttis[i].tt_isdst)
+ if (++i >= sp->typecnt) {
+ i = 0;
+ break;
+ }
+ } else {
+ for (i = 1; i < sp->timecnt; ++i)
+ if (t < sp->ats[i])
+ break;
+ i = (int) sp->types[i - 1];
+ }
+ ttisp = &sp->ttis[i];
+ /*
+ ** To get (wrong) behavior that's compatible with System V Release 2.0
+ ** you'd replace the statement below with
+ ** t += ttisp->tt_gmtoff;
+ ** timesub(&t, 0L, sp, tmp);
+ */
+ result = timesub(&t, ttisp->tt_gmtoff, sp, tmp);
+ tmp->tm_isdst = ttisp->tt_isdst;
+ tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind];
+#ifdef TM_ZONE
+ tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind];
+#endif /* defined TM_ZONE */
+ return result;
+}
+
+/*
+** gmtsub is to gmtime as localsub is to localtime.
+*/
+
+struct tm *
+gmtsub(timep, offset, tmp)
+const time_t * const timep;
+const long offset;
+struct tm * const tmp;
+{
+ register struct tm * result;
+
+ if (!gmt_is_set) {
+ gmt_is_set = TRUE;
+#ifdef ALL_STATE
+ gmtptr = (struct state *) malloc(sizeof *gmtptr);
+ if (gmtptr != NULL)
+#endif /* defined ALL_STATE */
+ gmtload(gmtptr);
+ }
+ result = timesub(timep, offset, gmtptr, tmp);
+#ifdef TM_ZONE
+ /*
+ ** Could get fancy here and deliver something such as
+ ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero,
+ ** but this is no time for a treasure hunt.
+ */
+ if (offset != 0)
+ tmp->TM_ZONE = wildabbr;
+ else {
+#ifdef ALL_STATE
+ if (gmtptr == NULL)
+ tmp->TM_ZONE = gmt;
+ else tmp->TM_ZONE = gmtptr->chars;
+#endif /* defined ALL_STATE */
+#ifndef ALL_STATE
+ tmp->TM_ZONE = gmtptr->chars;
+#endif /* State Farm */
+ }
+#endif /* defined TM_ZONE */
+ return result;
+}
+
+/*
+** Return the number of leap years through the end of the given year
+** where, to make the math easy, the answer for year zero is defined as zero.
+*/
+
+static int
+leaps_thru_end_of(y)
+register const int y;
+{
+ return (y >= 0) ? (y / 4 - y / 100 + y / 400) :
+ -(leaps_thru_end_of(-(y + 1)) + 1);
+}
+
+static struct tm *
+timesub(timep, offset, sp, tmp)
+const time_t * const timep;
+const long offset;
+register const struct state * const sp;
+register struct tm * const tmp;
+{
+ register const struct lsinfo * lp;
+ register time_t tdays;
+ register int idays; /* unsigned would be so 2003 */
+ register long rem;
+ int y;
+ register const int * ip;
+ register long corr;
+ register int hit;
+ register int i;
+
+ corr = 0;
+ hit = 0;
+#ifdef ALL_STATE
+ i = (sp == NULL) ? 0 : sp->leapcnt;
+#endif /* defined ALL_STATE */
+#ifndef ALL_STATE
+ i = sp->leapcnt;
+#endif /* State Farm */
+ while (--i >= 0) {
+ lp = &sp->lsis[i];
+ if (*timep >= lp->ls_trans) {
+ if (*timep == lp->ls_trans) {
+ hit = ((i == 0 && lp->ls_corr > 0) ||
+ lp->ls_corr > sp->lsis[i - 1].ls_corr);
+ if (hit)
+ while (i > 0 &&
+ sp->lsis[i].ls_trans ==
+ sp->lsis[i - 1].ls_trans + 1 &&
+ sp->lsis[i].ls_corr ==
+ sp->lsis[i - 1].ls_corr + 1) {
+ ++hit;
+ --i;
+ }
+ }
+ corr = lp->ls_corr;
+ break;
+ }
+ }
+ y = EPOCH_YEAR;
+ tdays = *timep / SECSPERDAY;
+ rem = *timep - tdays * SECSPERDAY;
+ while (tdays < 0 || tdays >= year_lengths[isleap(y)]) {
+ int newy;
+ register time_t tdelta;
+ register int idelta;
+ register int leapdays;
+
+ tdelta = tdays / DAYSPERLYEAR;
+ idelta = tdelta;
+ if (tdelta - idelta >= 1 || idelta - tdelta >= 1)
+ return NULL;
+ if (idelta == 0)
+ idelta = (tdays < 0) ? -1 : 1;
+ newy = y;
+ if (increment_overflow(&newy, idelta))
+ return NULL;
+ leapdays = leaps_thru_end_of(newy - 1) -
+ leaps_thru_end_of(y - 1);
+ tdays -= ((time_t) newy - y) * DAYSPERNYEAR;
+ tdays -= leapdays;
+ y = newy;
+ }
+ {
+ register long seconds;
+
+ seconds = tdays * SECSPERDAY + 0.5;
+ tdays = seconds / SECSPERDAY;
+ rem += seconds - tdays * SECSPERDAY;
+ }
+ /*
+ ** Given the range, we can now fearlessly cast...
+ */
+ idays = tdays;
+ rem += offset - corr;
+ while (rem < 0) {
+ rem += SECSPERDAY;
+ --idays;
+ }
+ while (rem >= SECSPERDAY) {
+ rem -= SECSPERDAY;
+ ++idays;
+ }
+ while (idays < 0) {
+ if (increment_overflow(&y, -1))
+ return NULL;
+ idays += year_lengths[isleap(y)];
+ }
+ while (idays >= year_lengths[isleap(y)]) {
+ idays -= year_lengths[isleap(y)];
+ if (increment_overflow(&y, 1))
+ return NULL;
+ }
+ tmp->tm_year = y;
+ if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE))
+ return NULL;
+ tmp->tm_yday = idays;
+ /*
+ ** The "extra" mods below avoid overflow problems.
+ */
+ tmp->tm_wday = EPOCH_WDAY +
+ ((y - EPOCH_YEAR) % DAYSPERWEEK) *
+ (DAYSPERNYEAR % DAYSPERWEEK) +
+ leaps_thru_end_of(y - 1) -
+ leaps_thru_end_of(EPOCH_YEAR - 1) +
+ idays;
+ tmp->tm_wday %= DAYSPERWEEK;
+ if (tmp->tm_wday < 0)
+ tmp->tm_wday += DAYSPERWEEK;
+ tmp->tm_hour = (int) (rem / SECSPERHOUR);
+ rem %= SECSPERHOUR;
+ tmp->tm_min = (int) (rem / SECSPERMIN);
+ /*
+ ** A positive leap second requires a special
+ ** representation. This uses "... ??:59:60" et seq.
+ */
+ tmp->tm_sec = (int) (rem % SECSPERMIN) + hit;
+ ip = mon_lengths[isleap(y)];
+ for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
+ idays -= ip[tmp->tm_mon];
+ tmp->tm_mday = (int) (idays + 1);
+ tmp->tm_isdst = 0;
+#ifdef TM_GMTOFF
+ tmp->TM_GMTOFF = offset;
+#endif /* defined TM_GMTOFF */
+ return tmp;
+}
+
+
+/*
+** Adapted from code provided by Robert Elz, who writes:
+** The "best" way to do mktime I think is based on an idea of Bob
+** Kridle's (so its said...) from a long time ago.
+** It does a binary search of the time_t space. Since time_t's are
+** just 32 bits, its a max of 32 iterations (even at 64 bits it
+** would still be very reasonable).
+*/
+
+#ifndef WRONG
+#define WRONG (-1)
+#endif /* !defined WRONG */
+
+/*
+** Simplified normalize logic courtesy Paul Eggert.
+*/
+
+static int
+increment_overflow(number, delta)
+int * number;
+int delta;
+{
+ int number0;
+
+ number0 = *number;
+ *number += delta;
+ return (*number < number0) != (delta < 0);
+}
+
+int tz_init( const char *zoneinfo_dir )
+{
+ char *ptr;
+ int fd;
+
+ if( zoneinfo_dir == NULL )
+ zoneinfo_dir = TZDIR;
+
+ ptr = malloc( strlen(zoneinfo_dir) + 10 );
+ sprintf( ptr, "%s/zone.tab", zoneinfo_dir );
+ fd = open( ptr, O_RDONLY );
+ free(ptr);
+
+ if( fd < 0 )
+ return -1;
+ close(fd);
+ if( tzdir )
+ free(tzdir);
+ tzdir = strdup(zoneinfo_dir);
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/localtime.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,16 @@
+/*************************************************************************
+ * Header file for timezone module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *************************************************************************/
+
+#include <time.h>
+
+struct state;
+
+struct state *timezone_load (const char * name);
+struct tm * gmtsub (const time_t * timep, long offset,
+ struct tm * tmp);
+struct tm * localsub (const time_t * timep, long offset,
+ struct tm * tmp, struct state *sp);
+int tz_init(const char *zoneinfo_dir);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/private.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,316 @@
+/*************************************************************************
+ * Private Header file for timezone module
+ * copied from Olson timezone code, licence unchanged
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ *************************************************************************/
+
+#ifndef PRIVATE_H
+
+#define PRIVATE_H
+
+/*
+** This file is in the public domain, so clarified as of
+** 1996-06-05 by Arthur David Olson.
+*/
+
+/*
+** This header is for use ONLY with the time conversion code.
+** There is no guarantee that it will remain unchanged,
+** or that it will remain at all.
+** Do NOT copy it to any system include directory.
+** Thank you!
+*/
+
+/*
+** ID
+*/
+
+#define GRANDPARENTED "Local time zone must be set--see zic manual page"
+
+/*
+** Defaults for preprocessor symbols.
+** You can override these in your C compiler options, e.g. `-DHAVE_ADJTIME=0'.
+*/
+
+#ifndef HAVE_ADJTIME
+#define HAVE_ADJTIME 1
+#endif /* !defined HAVE_ADJTIME */
+
+#ifndef HAVE_GETTEXT
+#define HAVE_GETTEXT 0
+#endif /* !defined HAVE_GETTEXT */
+
+#ifndef HAVE_INCOMPATIBLE_CTIME_R
+#define HAVE_INCOMPATIBLE_CTIME_R 0
+#endif /* !defined INCOMPATIBLE_CTIME_R */
+
+#ifndef HAVE_SETTIMEOFDAY
+#define HAVE_SETTIMEOFDAY 3
+#endif /* !defined HAVE_SETTIMEOFDAY */
+
+#ifndef HAVE_STRERROR
+#define HAVE_STRERROR 1
+#endif /* !defined HAVE_STRERROR */
+
+#ifndef HAVE_SYMLINK
+#define HAVE_SYMLINK 1
+#endif /* !defined HAVE_SYMLINK */
+
+#ifndef HAVE_SYS_STAT_H
+#define HAVE_SYS_STAT_H 1
+#endif /* !defined HAVE_SYS_STAT_H */
+
+#ifndef HAVE_SYS_WAIT_H
+#define HAVE_SYS_WAIT_H 1
+#endif /* !defined HAVE_SYS_WAIT_H */
+
+#ifndef HAVE_UNISTD_H
+#define HAVE_UNISTD_H 1
+#endif /* !defined HAVE_UNISTD_H */
+
+#ifndef HAVE_UTMPX_H
+#define HAVE_UTMPX_H 0
+#endif /* !defined HAVE_UTMPX_H */
+
+#ifndef LOCALE_HOME
+#define LOCALE_HOME "/usr/lib/locale"
+#endif /* !defined LOCALE_HOME */
+
+#if HAVE_INCOMPATIBLE_CTIME_R
+#define asctime_r _incompatible_asctime_r
+#define ctime_r _incompatible_ctime_r
+#endif /* HAVE_INCOMPATIBLE_CTIME_R */
+
+/*
+** Nested includes
+*/
+
+#include "sys/types.h" /* for time_t */
+#include "stdio.h"
+#include "errno.h"
+#include "string.h"
+#include "limits.h" /* for CHAR_BIT */
+#include "time.h"
+#include "stdlib.h"
+
+#if HAVE_GETTEXT
+#include "libintl.h"
+#endif /* HAVE_GETTEXT */
+
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h> /* for WIFEXITED and WEXITSTATUS */
+#endif /* HAVE_SYS_WAIT_H */
+
+#ifndef WIFEXITED
+#define WIFEXITED(status) (((status) & 0xff) == 0)
+#endif /* !defined WIFEXITED */
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(status) (((status) >> 8) & 0xff)
+#endif /* !defined WEXITSTATUS */
+
+#if HAVE_UNISTD_H
+#include "unistd.h" /* for F_OK and R_OK */
+#endif /* HAVE_UNISTD_H */
+
+#if !HAVE_UNISTD_H
+#ifndef F_OK
+#define F_OK 0
+#endif /* !defined F_OK */
+#ifndef R_OK
+#define R_OK 4
+#endif /* !defined R_OK */
+#endif /* !HAVE_UNISTD_H */
+
+/* Unlike <ctype.h>'s isdigit, this also works if c < 0 | c > UCHAR_MAX. */
+#define is_digit(c) ((unsigned)(c) - '0' <= 9)
+
+/*
+** Workarounds for compilers/systems.
+*/
+
+/*
+** SunOS 4.1.1 cc lacks prototypes.
+*/
+
+#ifndef P
+#if __STDC__
+#define P(x) x
+#else /* !__STDC__ */
+#define P(x) ()
+#endif /* !__STDC__ */
+#endif /* !defined P */
+
+/*
+** SunOS 4.1.1 headers lack EXIT_SUCCESS.
+*/
+
+#ifndef EXIT_SUCCESS
+#define EXIT_SUCCESS 0
+#endif /* !defined EXIT_SUCCESS */
+
+/*
+** SunOS 4.1.1 headers lack EXIT_FAILURE.
+*/
+
+#ifndef EXIT_FAILURE
+#define EXIT_FAILURE 1
+#endif /* !defined EXIT_FAILURE */
+
+/*
+** SunOS 4.1.1 headers lack FILENAME_MAX.
+*/
+
+#ifndef FILENAME_MAX
+
+#ifndef MAXPATHLEN
+#ifdef unix
+#include "sys/param.h"
+#endif /* defined unix */
+#endif /* !defined MAXPATHLEN */
+
+#ifdef MAXPATHLEN
+#define FILENAME_MAX MAXPATHLEN
+#endif /* defined MAXPATHLEN */
+#ifndef MAXPATHLEN
+#define FILENAME_MAX 1024 /* Pure guesswork */
+#endif /* !defined MAXPATHLEN */
+
+#endif /* !defined FILENAME_MAX */
+
+/*
+** SunOS 4.1.1 libraries lack remove.
+*/
+
+#ifndef remove
+extern int unlink P((const char * filename));
+#define remove unlink
+#endif /* !defined remove */
+
+/*
+** Some ancient errno.h implementations don't declare errno.
+** But some newer errno.h implementations define it as a macro.
+** Fix the former without affecting the latter.
+*/
+
+#ifndef errno
+extern int errno;
+#endif /* !defined errno */
+
+/*
+** Some time.h implementations don't declare asctime_r.
+** Others might define it as a macro.
+** Fix the former without affecting the latter.
+*/
+
+#ifndef asctime_r
+extern char * asctime_r();
+#endif
+
+/*
+** Private function declarations.
+*/
+
+char * icalloc P((int nelem, int elsize));
+char * icatalloc P((char * old, const char * new));
+char * icpyalloc P((const char * string));
+char * imalloc P((int n));
+void * irealloc P((void * pointer, int size));
+void icfree P((char * pointer));
+void ifree P((char * pointer));
+const char *scheck P((const char *string, const char *format));
+
+/*
+** Finally, some convenience items.
+*/
+
+#ifndef TRUE
+#define TRUE 1
+#endif /* !defined TRUE */
+
+#ifndef FALSE
+#define FALSE 0
+#endif /* !defined FALSE */
+
+#ifndef TYPE_BIT
+#define TYPE_BIT(type) (sizeof (type) * CHAR_BIT)
+#endif /* !defined TYPE_BIT */
+
+#ifndef TYPE_SIGNED
+#define TYPE_SIGNED(type) (((type) -1) < 0)
+#endif /* !defined TYPE_SIGNED */
+
+/*
+** Since the definition of TYPE_INTEGRAL contains floating point numbers,
+** it cannot be used in preprocessor directives.
+*/
+
+#ifndef TYPE_INTEGRAL
+#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5)
+#endif /* !defined TYPE_INTEGRAL */
+
+#ifndef INT_STRLEN_MAXIMUM
+/*
+** 302 / 1000 is log10(2.0) rounded up.
+** Subtract one for the sign bit if the type is signed;
+** add one for integer division truncation;
+** add one more for a minus sign if the type is signed.
+*/
+#define INT_STRLEN_MAXIMUM(type) \
+ ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
+ 1 + TYPE_SIGNED(type))
+#endif /* !defined INT_STRLEN_MAXIMUM */
+
+/*
+** INITIALIZE(x)
+*/
+
+#ifndef GNUC_or_lint
+#ifdef lint
+#define GNUC_or_lint
+#endif /* defined lint */
+#ifndef lint
+#ifdef __GNUC__
+#define GNUC_or_lint
+#endif /* defined __GNUC__ */
+#endif /* !defined lint */
+#endif /* !defined GNUC_or_lint */
+
+#ifndef INITIALIZE
+#ifdef GNUC_or_lint
+#define INITIALIZE(x) ((x) = 0)
+#endif /* defined GNUC_or_lint */
+#ifndef GNUC_or_lint
+#define INITIALIZE(x)
+#endif /* !defined GNUC_or_lint */
+#endif /* !defined INITIALIZE */
+
+/*
+** For the benefit of GNU folk...
+** `_(MSGID)' uses the current locale's message library string for MSGID.
+** The default is to use gettext if available, and use MSGID otherwise.
+*/
+
+#ifndef _
+#if HAVE_GETTEXT
+#define _(msgid) gettext(msgid)
+#else /* !HAVE_GETTEXT */
+#define _(msgid) msgid
+#endif /* !HAVE_GETTEXT */
+#endif /* !defined _ */
+
+#ifndef TZ_DOMAIN
+#define TZ_DOMAIN "tz"
+#endif /* !defined TZ_DOMAIN */
+
+#if HAVE_INCOMPATIBLE_CTIME_R
+#undef asctime_r
+#undef ctime_r
+char *asctime_r P((struct tm const *, char *));
+char *ctime_r P((time_t const *, char *));
+#endif /* HAVE_INCOMPATIBLE_CTIME_R */
+
+/*
+** UNIX was a registered trademark of The Open Group in 2003.
+*/
+
+#endif /* !defined PRIVATE_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recurse.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,141 @@
+/*************************************************************************
+ * Recursion module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * Provides a function to recurse a directory and call a callback for each
+ * file found.
+ *************************************************************************/
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "recurse.h"
+
+#if 1
+/* GLibc specific version. In this version, the entries are sorted */
+/* We assume dirname ends in a /, prefix also unless empty */
+static int
+recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data)
+{
+ struct dirent **namelist;
+ int ents;
+ struct dirent *ent;
+ int i;
+ int ret = 0;
+
+ if((ents = scandir(dirname, &namelist, 0, alphasort)) < 0)
+ return -1;
+
+ for (i = 0; i < ents; i++)
+ {
+ char *ptr;
+ struct stat s;
+
+ ent = namelist[i];
+ asprintf(&ptr, "%s%s", dirname, ent->d_name);
+ if(stat(ptr, &s) < 0)
+ {
+ free(ptr);
+ continue;
+ }
+
+ if(S_ISREG(s.st_mode))
+ {
+ free(ptr);
+ asprintf(&ptr, "%s%s", prefix, ent->d_name);
+ ret = func(ptr, data);
+ }
+ else if(S_ISDIR(s.st_mode))
+ {
+ char *newdirname, *newprefix;
+
+ if(ent->d_name[0] != '.')
+ {
+ asprintf(&newdirname, "%s%s/", dirname, ent->d_name);
+ asprintf(&newprefix, "%s%s/", prefix, ent->d_name);
+ ret = recurse_directory_int(newdirname, newprefix, func, data);
+ free(newdirname);
+ free(newprefix);
+ }
+ }
+ free(ptr);
+ if(ret < 0)
+ break;
+ }
+ free(namelist);
+ return 0;
+}
+#else
+/* generic version, here they are unsorted */
+/* We assume dirname ends in a /, prefix also unless empty */
+static int
+recurse_directory_int(char *dirname, char *prefix, DirRecurseMatch func, void *data)
+{
+ DIR *dir;
+ struct dirent *ent;
+ int ret = 0;
+
+ dir = opendir(dirname);
+ if(!dir)
+ return -1;
+ while ((ent = readdir(dir)) != NULL)
+ {
+ char *ptr;
+ struct stat s;
+
+ asprintf(&ptr, "%s%s", dirname, ent->d_name);
+ if(stat(ptr, &s) < 0)
+ {
+ free(ptr);
+ continue;
+ }
+
+ if(S_ISREG(s.st_mode))
+ {
+ free(ptr);
+ asprintf(&ptr, "%s%s", prefix, ent->d_name);
+ ret = func(ptr, data);
+ }
+ else if(S_ISDIR(s.st_mode))
+ {
+ char *newdirname, *newprefix;
+
+ if(ent->d_name[0] != '.')
+ {
+ asprintf(&newdirname, "%s%s/", dirname, ent->d_name);
+ asprintf(&newprefix, "%s%s/", prefix, ent->d_name);
+ ret = recurse_directory_int(newdirname, newprefix, func, data);
+ free(newdirname);
+ free(newprefix);
+ }
+ }
+ free(ptr);
+ if(ret < 0)
+ break;
+ }
+ closedir(dir);
+ return ret;
+}
+#endif
+
+int
+recurse_directory(char *dirname, DirRecurseMatch func, void *data)
+{
+ char *newdirname = NULL;
+ int ret;
+
+ if(dirname[strlen(dirname) - 1] != '/')
+ asprintf(&newdirname, "%s/", dirname);
+
+ ret = recurse_directory_int(newdirname ? newdirname : dirname, "", func, data);
+
+ if(newdirname)
+ free(newdirname);
+ return ret;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recurse.h Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,9 @@
+/*************************************************************************
+ * Header file for recursion module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *************************************************************************/
+
+typedef int (*DirRecurseMatch)(char *filename, void *data);
+int recurse_directory( char *dirname, DirRecurseMatch func, void *data );
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/recursetest.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,25 @@
+/*************************************************************************
+ * Recursion test module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * Code to test the recursion module.
+ *************************************************************************/
+
+#include <stdio.h>
+
+#include "recurse.h"
+
+int
+process_entry(char *str, void *ptr)
+{
+ printf("%s\n", str);
+ return 0;
+}
+
+int
+main()
+{
+ recurse_directory("/usr/share/zoneinfo", process_entry, main);
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/timetest.c Mon Oct 22 05:00:47 2007 -0400
@@ -0,0 +1,28 @@
+/*************************************************************************
+ * Timezone test module
+ * by Martijn van Oosterhout <kleptog@svana.org> (C) April 2006
+ * Licenced under the GNU General Public Licence version 2.
+ *
+ * Code to test the timezone module.
+ *************************************************************************/
+
+//#include "private.h"
+//#include "tzfile.h"
+#include "localtime.h"
+
+int
+main()
+{
+ struct state *state;
+ time_t now = time(NULL);
+ struct tm tm;
+
+ state = timezone_load("Australia/Sydney");
+
+ if(!state)
+ return 0;
+
+ localsub(&now, 0, &tm, state);
+ gmtsub(&now, 0, &tm);
+ return 0;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/buddytime/tzfile.h Mon Oct 22 05:00:47 2007 -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 */