org.guifications.plugins: 7c6d795b543b0dfbbe31afd178fb6d8c49741e85

nosnilmot at guifications.org nosnilmot at guifications.org
Sun Oct 21 14:25:04 CDT 2007


-----------------------------------------------------------------
Revision: 7c6d795b543b0dfbbe31afd178fb6d8c49741e85
Ancestor: 1c399939bcf52fd83fe458d3bd21e6960fc70ff9
Author: nosnilmot at guifications.org
Date: 2007-10-21T19:20:31
Branch: org.guifications.plugins

Added files:
        timelog/.build timelog/.pidgin-plugin timelog/Makefile.am
        timelog/Makefile.mingw timelog/log-widget.c
        timelog/log-widget.h timelog/range-widget.c
        timelog/range-widget.h timelog/timelog.c timelog/timelog.h
Added directories:
        timelog
Modified files:
        configure.ac

ChangeLog: 

Add the timelog plugin, which is Jon Oberheide's gaim-timelog plugin ported
to Pidgin

-----------------------------------------------------------------
This revision's diffstat output:
 configure.ac           |    1 
 timelog/Makefile.am    |   31 ++++
 timelog/Makefile.mingw |   21 ++
 timelog/log-widget.c   |  379 +++++++++++++++++++++++++++++++++++++++++++++++++
 timelog/log-widget.h   |   29 +++
 timelog/range-widget.c |  359 ++++++++++++++++++++++++++++++++++++++++++++++
 timelog/range-widget.h |   31 ++++
 timelog/timelog.c      |  191 ++++++++++++++++++++++++
 timelog/timelog.h      |   35 ++++
 9 files changed, 1077 insertions(+)
-------------- next part --------------
============================================================
--- timelog/.build	da39a3ee5e6b4b0d3255bfef95601890afd80709
+++ timelog/.build	da39a3ee5e6b4b0d3255bfef95601890afd80709
============================================================
--- timelog/.pidgin-plugin	da39a3ee5e6b4b0d3255bfef95601890afd80709
+++ timelog/.pidgin-plugin	da39a3ee5e6b4b0d3255bfef95601890afd80709
============================================================
--- timelog/Makefile.am	23aa61342bb9e8fccbca553e606aff076ab3382c
+++ timelog/Makefile.am	23aa61342bb9e8fccbca553e606aff076ab3382c
@@ -0,0 +1,31 @@
+EXTRA_DIST = .pidgin-plugin .build
+
+timelogdir = $(PIDGIN_LIBDIR)
+
+timelog_la_LDFLAGS = -module -avoid-version
+
+if HAVE_PIDGIN
+
+timelog_LTLIBRARIES = timelog.la
+
+timelog_la_SOURCES = \
+	log-widget.c \
+	log-widget.h \
+	range-widget.c \
+	range-widget.h \
+	timelog.c \
+	timelog.h
+
+timelog_la_LIBADD = \
+	$(PIDGIN_LIBS) \
+	$(GTK_LIBS)
+
+endif
+
+AM_CPPFLAGS = \
+	-DLIBDIR=\"$(PIDGIN_LIBDIR)\" \
+	-DDATADIR=\"$(PIDGIN_DATADIR)\" \
+	-DPIXMAPSDIR=\"$(PIDGIN_PIXMAPSDIR)\" \
+	$(GTK_CFLAGS) \
+	$(DEBUG_CFLAGS) \
+	$(PIDGIN_CFLAGS)
============================================================
--- timelog/Makefile.mingw	89af0f7b6e0ea53ac6417d716a28c0a60671830d
+++ timelog/Makefile.mingw	89af0f7b6e0ea53ac6417d716a28c0a60671830d
@@ -0,0 +1,21 @@
+#
+# Makefile.mingw
+#
+# Description: Makefile for timelog plugin.
+#
+
+PP_TOP :=		..
+
+PP = timelog
+
+
+PP_SRC := \
+	log-widget.c \
+	log-widget.h \
+	range-widget.c \
+	range-widget.h \
+	timelog.c \
+	timelog.h
+
+include $(PP_TOP)/win_pp.mak
+
============================================================
--- timelog/log-widget.c	a519042b5b2efe9b136e5f9c5330e9cd33155f10
+++ timelog/log-widget.c	a519042b5b2efe9b136e5f9c5330e9cd33155f10
@@ -0,0 +1,379 @@
+/*
+ * TimeLog plugin.
+ *
+ * Copyright (c) 2006 Jon Oberheide.
+ *
+ * Based on code from Gaim's gtklog.c
+ *
+ * $Id: log-widget.c,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <pidgin.h>
+#include <account.h>
+#include <gtkblist.h>
+#include <gtkutils.h>
+#include <gtkimhtml.h>
+#include <gtklog.h>
+#include <log.h>
+#include <notify.h>
+#include <util.h>
+
+#include "timelog.h"
+#include "log-widget.h"
+
+static PidginLogViewer *syslog_viewer = NULL;
+
+static gint
+log_compare(gconstpointer y, gconstpointer z)
+{
+	const PurpleLog *a = y;
+	const PurpleLog *b = z;
+
+	return strcmp(a->name, b->name);
+}
+
+static void
+populate_log_tree(PidginLogViewer *lv)
+{
+	char name[64];
+	char title[64];
+	char prev_top_name[64] = "";
+	char *utf8_tmp; /* temporary variable for utf8 conversion */
+	GtkTreeIter toplevel, child;
+	GList *logs = lv->logs;
+
+	while (logs != NULL) {
+		PurpleLog *log = logs->data;
+
+		strncpy(name, log->name, sizeof(name));
+		strftime(title, sizeof(title), "%c", localtime(&log->time));
+
+		/* do utf8 conversions */
+		utf8_tmp = purple_utf8_try_convert(name);
+		strncpy(name, utf8_tmp, sizeof(name));
+		g_free(utf8_tmp);
+		utf8_tmp = purple_utf8_try_convert(title);
+		strncpy(title, utf8_tmp, sizeof(title));
+		g_free(utf8_tmp);
+
+		if (strncmp(name, prev_top_name, sizeof(name)) != 0) {
+			/* top level */
+			gtk_tree_store_append(lv->treestore, &toplevel, NULL);
+			gtk_tree_store_set(lv->treestore, &toplevel, 0, name, 1, NULL, -1);
+
+			strncpy(prev_top_name, name, sizeof(prev_top_name));
+		}
+
+		/* sub */
+		gtk_tree_store_append(lv->treestore, &child, &toplevel);
+		gtk_tree_store_set(lv->treestore, &child, 0, title, 1, log, -1);
+
+		logs = logs->next;
+	}
+}
+
+static void
+search_cb(GtkWidget *button, PidginLogViewer *lv)
+{
+	const char *search_term = gtk_entry_get_text(GTK_ENTRY(lv->entry));
+	GList *logs;
+	GdkCursor *cursor;
+
+	if (lv->search != NULL)
+		g_free(lv->search);
+
+	gtk_tree_store_clear(lv->treestore);
+	if (!(*search_term)) {
+		/* reset the tree */
+		populate_log_tree(lv);
+		lv->search = NULL;
+		gtk_imhtml_search_clear(GTK_IMHTML(lv->imhtml));
+		return;
+	}
+
+	lv->search = g_strdup(search_term);
+
+	cursor = gdk_cursor_new(GDK_WATCH);
+	gdk_window_set_cursor(lv->window->window, cursor);
+	gdk_cursor_unref(cursor);
+	while (gtk_events_pending())
+		gtk_main_iteration();
+
+	for (logs = lv->logs; logs != NULL; logs = logs->next) {
+		char *read = purple_log_read((PurpleLog*)logs->data, NULL);
+		if (read && *read && purple_strcasestr(read, search_term)) {
+			GtkTreeIter iter;
+			PurpleLog *log = logs->data;
+			char title[64];
+			char *title_utf8; /* temporary variable for utf8 conversion */
+
+			strftime(title, sizeof(title), "%c", localtime(&log->time));
+			title_utf8 = purple_utf8_try_convert(title);
+			strncpy(title, title_utf8, sizeof(title));
+			g_free(title_utf8);
+
+			gtk_tree_store_append (lv->treestore, &iter, NULL);
+			gtk_tree_store_set(lv->treestore, &iter,
+					   0, title,
+					   1, log, -1);
+		}
+		g_free(read);
+	}
+
+	gdk_window_set_cursor(lv->window->window, NULL);
+}
+
+static gboolean
+destroy_cb(GtkWidget *w, gint resp, gpointer data)
+{
+	PidginLogViewer *lv = syslog_viewer;
+	syslog_viewer = NULL;
+
+	while (lv->logs != NULL) {
+		GList *logs2;
+
+		purple_log_free((PurpleLog *)lv->logs->data);
+
+		logs2 = lv->logs->next;
+		g_list_free_1(lv->logs);
+		lv->logs = logs2;
+	}
+
+	if (lv->search != NULL)
+		g_free(lv->search);
+
+	g_free(lv);
+	gtk_widget_destroy(w);
+
+	return TRUE;
+}
+
+static void
+log_row_activated_cb(GtkTreeView *tv, GtkTreePath *path, GtkTreeViewColumn *col, PidginLogViewer *viewer)
+{
+	if (gtk_tree_view_row_expanded(tv, path)) {
+		gtk_tree_view_collapse_row(tv, path);
+	} else {
+		gtk_tree_view_expand_row(tv, path, FALSE);
+	}
+}
+
+static void
+log_select_cb(GtkTreeSelection *sel, PidginLogViewer *viewer)
+{
+	GtkTreeIter iter;
+	GValue val;
+	GtkTreeModel *model = GTK_TREE_MODEL(viewer->treestore);
+	PurpleLog *log = NULL;
+	GdkCursor *cursor;
+	PurpleLogReadFlags flags;
+	char *read = NULL;
+	char time[64];
+
+	if (!gtk_tree_selection_get_selected(sel, &model, &iter)) {
+		return;
+	}
+
+	val.g_type = 0;
+	gtk_tree_model_get_value (model, &iter, 1, &val);
+	log = g_value_get_pointer(&val);
+	g_value_unset(&val);
+
+	if (log == NULL) {
+		return;
+	}
+
+	/* When we set the initial log, this gets called and the window is still NULL. */
+	if (viewer->window->window != NULL) {
+		cursor = gdk_cursor_new(GDK_WATCH);
+		gdk_window_set_cursor(viewer->window->window, cursor);
+		gdk_cursor_unref(cursor);
+		while (gtk_events_pending())
+			gtk_main_iteration();
+	}
+
+	if (log->type != PURPLE_LOG_SYSTEM) {
+		char *title;
+		char *title_utf8; /* temporary variable for utf8 conversion */
+
+		strftime(time, sizeof(time), "%c", localtime(&log->time));
+
+		if (log->type == PURPLE_LOG_CHAT)
+			title = g_strdup_printf("Conversation in %s on %s", log->name, time);
+		else
+			title = g_strdup_printf("Conversation with %s on %s", log->name, time);
+
+		title_utf8 = purple_utf8_try_convert(title);
+		g_free(title);
+
+		title = g_strdup_printf("<span size='larger' weight='bold'>%s</span>", title_utf8);
+		g_free(title_utf8);
+
+		gtk_label_set_markup(GTK_LABEL(viewer->label), title);
+		g_free(title);
+	}
+
+	read = purple_log_read(log, &flags);
+	viewer->flags = flags;
+
+	gtk_imhtml_clear(GTK_IMHTML(viewer->imhtml));
+	gtk_imhtml_set_protocol_name(GTK_IMHTML(viewer->imhtml),
+				    purple_account_get_protocol_name(log->account));
+	gtk_imhtml_append_text(GTK_IMHTML(viewer->imhtml), read,
+			       GTK_IMHTML_NO_COMMENTS | GTK_IMHTML_NO_TITLE | GTK_IMHTML_NO_SCROLL |
+			       ((flags & PURPLE_LOG_READ_NO_NEWLINE) ? GTK_IMHTML_NO_NEWLINE : 0));
+	g_free(read);
+
+	if (viewer->search != NULL) {
+		gtk_imhtml_search_clear(GTK_IMHTML(viewer->imhtml));
+		gtk_imhtml_search_find(GTK_IMHTML(viewer->imhtml), viewer->search);
+	}
+
+	/* When we set the initial log, this gets called and the window is still NULL. */
+	if (viewer->window->window != NULL)
+		gdk_window_set_cursor(viewer->window->window, NULL);
+}
+
+void
+log_widget_display_logs(GList *logs)
+{
+	PidginLogViewer *lv;
+	GtkWidget *title_box;
+	char *text;
+	GtkWidget *pane;
+	GtkWidget *sw;
+	GtkCellRenderer *rend;
+	GtkTreeViewColumn *col;
+	GtkTreeSelection *sel;
+#if GTK_CHECK_VERSION(2,2,0)
+	GtkTreePath *path_to_first_log;
+#endif
+	GtkWidget *vbox;
+	GtkWidget *frame;
+	GtkWidget *hbox;
+	GtkWidget *button;
+
+	if (syslog_viewer != NULL) {
+		gtk_window_present(GTK_WINDOW(syslog_viewer->window));
+		return;
+	}
+
+	lv = g_new0(PidginLogViewer, 1);
+	lv->logs = logs;
+
+	if (logs == NULL) {
+		/* No logs were found. */
+		purple_notify_info(NULL, TIMELOG_TITLE, "No logs were found", NULL);
+		return;
+	}
+
+	lv->logs = g_list_sort(lv->logs, log_compare);
+
+	/* Window ***********/
+	lv->window = gtk_dialog_new_with_buttons(TIMELOG_TITLE, NULL, 0,
+					     GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, NULL);
+	gtk_container_set_border_width (GTK_CONTAINER(lv->window), PIDGIN_HIG_BOX_SPACE);
+	gtk_dialog_set_has_separator(GTK_DIALOG(lv->window), FALSE);
+	gtk_box_set_spacing(GTK_BOX(GTK_DIALOG(lv->window)->vbox), 0);
+	g_signal_connect(G_OBJECT(lv->window), "response", G_CALLBACK(destroy_cb), NULL);
+	gtk_window_set_role(GTK_WINDOW(lv->window), "log_viewer");
+
+	title_box = GTK_DIALOG(lv->window)->vbox;
+
+	/* Label ************/
+	lv->label = gtk_label_new(NULL);
+
+	text = g_strdup_printf("<span size='larger' weight='bold'>%s</span>", TIMELOG_TITLE);
+
+	gtk_label_set_markup(GTK_LABEL(lv->label), text);
+	gtk_misc_set_alignment(GTK_MISC(lv->label), 0, 0);
+	gtk_box_pack_start(GTK_BOX(title_box), lv->label, FALSE, FALSE, 0);
+	g_free(text);
+
+	/* Pane *************/
+	pane = gtk_hpaned_new();
+	gtk_container_set_border_width(GTK_CONTAINER(pane), PIDGIN_HIG_BOX_SPACE);
+	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(lv->window)->vbox), pane, TRUE, TRUE, 0);
+
+	/* List *************/
+	sw = gtk_scrolled_window_new (NULL, NULL);
+	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN);
+	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
+	gtk_paned_add1(GTK_PANED(pane), sw);
+	lv->treestore = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_POINTER);
+	lv->treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (lv->treestore));
+	rend = gtk_cell_renderer_text_new();
+	col = gtk_tree_view_column_new_with_attributes ("time", rend, "markup", 0, NULL);
+	gtk_tree_view_append_column (GTK_TREE_VIEW(lv->treeview), col);
+	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (lv->treeview), FALSE);
+	gtk_container_add (GTK_CONTAINER (sw), lv->treeview);
+
+	populate_log_tree(lv);
+
+	sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (lv->treeview));
+	g_signal_connect (G_OBJECT (sel), "changed",
+			G_CALLBACK (log_select_cb),
+			lv);
+	g_signal_connect (G_OBJECT(lv->treeview), "row-activated",
+			G_CALLBACK(log_row_activated_cb),
+			lv);
+	pidgin_set_accessible_label(lv->treeview, lv->label);
+
+	/* A fancy little box ************/
+	vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_paned_add2(GTK_PANED(pane), vbox);
+
+	/* Viewer ************/
+	frame = pidgin_create_imhtml(FALSE, &lv->imhtml, NULL, NULL);
+	gtk_widget_set_name(lv->imhtml, "pidginlog_imhtml");
+	gtk_widget_set_size_request(lv->imhtml, 320, 200);
+	gtk_box_pack_start(GTK_BOX(vbox), frame, TRUE, TRUE, 0);
+	gtk_widget_show(frame);
+
+	/* Search box **********/
+	hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BOX_SPACE);
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
+	lv->entry = gtk_entry_new();
+	gtk_box_pack_start(GTK_BOX(hbox), lv->entry, TRUE, TRUE, 0);
+	button = gtk_button_new_from_stock(GTK_STOCK_FIND);
+	gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0);
+	g_signal_connect(GTK_ENTRY(lv->entry), "activate", G_CALLBACK(search_cb), lv);
+	g_signal_connect(GTK_BUTTON(button), "activate", G_CALLBACK(search_cb), lv);
+	g_signal_connect(GTK_BUTTON(button), "clicked", G_CALLBACK(search_cb), lv);
+
+#if GTK_CHECK_VERSION(2,2,0)
+	/* Show most recent log **********/
+	path_to_first_log = gtk_tree_path_new_from_string("0:0");
+	if (path_to_first_log)
+	{
+		gtk_tree_view_expand_to_path(GTK_TREE_VIEW(lv->treeview), path_to_first_log);
+		gtk_tree_selection_select_path(sel, path_to_first_log);
+		gtk_tree_path_free(path_to_first_log);
+	}
+#endif
+
+	gtk_widget_show_all(lv->window);
+	syslog_viewer = lv;
+}
============================================================
--- timelog/log-widget.h	1014eec43edac20715ab186d650175064dd9f03c
+++ timelog/log-widget.h	1014eec43edac20715ab186d650175064dd9f03c
@@ -0,0 +1,29 @@
+/*
+ * Gaim-TimeLog plugin.
+ *
+ * Copyright (c) 2006 Jon Oberheide.
+ *
+ * $Id: log-widget.h,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * 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 _LOG_WIDGET_H_
+#define _LOG_WIDGET_H_
+
+void log_widget_display_logs(GList *logs);
+
+#endif
============================================================
--- timelog/range-widget.c	14cdc45510ffa6adb3f728ab909bb218e006b5eb
+++ timelog/range-widget.c	14cdc45510ffa6adb3f728ab909bb218e006b5eb
@@ -0,0 +1,359 @@
+/*
+ * TimeLog plugin.
+ *
+ * Copyright (c) 2006 Jon Oberheide.
+ *
+ * $Id: range-widget.c,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "timelog.h"
+#include "range-widget.h"
+
+static GtkWidget *start_calendar;
+static GtkWidget *end_calendar;
+static GtkWidget *start_hours;
+static GtkWidget *start_minutes;
+static GtkWidget *start_seconds;
+static GtkWidget *end_hours;
+static GtkWidget *end_minutes;
+static GtkWidget *end_seconds;
+
+static void
+calendar_update(GtkWidget *calendar, gint add)
+{
+	static const gint month_length[2][13] =
+	{
+		{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
+		{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
+	};
+
+	guint day, month, year;
+	gint days_in_month;
+	gboolean leap_year;
+
+	gtk_calendar_get_date(GTK_CALENDAR(calendar), &year, &month, &day);
+
+	leap_year = g_date_is_leap_year(year);
+	days_in_month = month_length[leap_year][month+1];
+
+	if (add != 0) {
+		day += add;
+		if (day < 1) {
+			day = (month_length[leap_year][month]) + day;
+			month--;
+		} else if (day > days_in_month) {
+			day -= days_in_month;
+			month++;
+		}
+
+		if (month < 0) {
+			year--;
+			leap_year = g_date_is_leap_year(year);
+			month = 11;
+			day = month_length[leap_year][month+1];
+		} else if (month > 11) {
+			year++;
+			leap_year = g_date_is_leap_year(year);
+			month = 0;
+			day = 1;
+		}
+	}
+
+	gtk_calendar_select_month(GTK_CALENDAR(calendar), month, year);
+	gtk_calendar_select_day(GTK_CALENDAR(calendar), day);
+}
+
+static void
+cb_time_value_changed(GtkSpinButton *widget, gpointer data)
+{
+	gint value = gtk_spin_button_get_value(widget);
+
+	if (widget == GTK_SPIN_BUTTON(start_seconds)) {
+		if (value > 59) {
+			gtk_spin_button_set_value(widget, value - 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(start_minutes), GTK_SPIN_STEP_FORWARD, 1);
+		} else if (value < 0) {
+			gtk_spin_button_set_value(widget, value + 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(start_minutes), GTK_SPIN_STEP_BACKWARD, 1);
+		}
+	} else if (widget == GTK_SPIN_BUTTON(start_minutes)) {
+		if (value > 59) {
+			gtk_spin_button_set_value(widget, value - 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(start_hours), GTK_SPIN_STEP_FORWARD, 1);
+		} else if (value < 0) {
+			gtk_spin_button_set_value(widget, value + 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(start_hours), GTK_SPIN_STEP_BACKWARD, 1);
+		}
+	} else if (widget == GTK_SPIN_BUTTON(start_hours)) {
+		if (value > 23) {
+			gtk_spin_button_set_value(widget, value - 24);
+			calendar_update(start_calendar, 1);
+		} else if (value < 0) {
+			calendar_update(start_calendar, -1);
+			gtk_spin_button_set_value(widget, value + 24);
+		}
+	} else if (widget == GTK_SPIN_BUTTON(end_seconds)) {
+		if (value > 59) {
+			gtk_spin_button_set_value(widget, value - 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(end_minutes), GTK_SPIN_STEP_FORWARD, 1);
+		} else if (value < 0) {
+			gtk_spin_button_set_value(widget, value + 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(end_minutes), GTK_SPIN_STEP_BACKWARD, 1);
+		}
+	} else if (widget == GTK_SPIN_BUTTON(end_minutes)) {
+		if (value > 59) {
+			gtk_spin_button_set_value(widget, value - 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(end_hours), GTK_SPIN_STEP_FORWARD, 1);
+		} else if (value < 0) {
+			gtk_spin_button_set_value(widget, value + 60);
+			gtk_spin_button_spin(GTK_SPIN_BUTTON(end_hours), GTK_SPIN_STEP_BACKWARD, 1);
+		}
+	} else if (widget == GTK_SPIN_BUTTON(end_hours)) {
+		if (value > 23) {
+			gtk_spin_button_set_value(widget, value - 24);
+			calendar_update(end_calendar, 1);
+		} else if (value < 0) {
+			calendar_update(end_calendar, -1);
+			gtk_spin_button_set_value(widget, value + 24);
+		}
+	}
+
+	gchar *val = g_strdup_printf("%02d", value);
+	gtk_entry_set_text(GTK_ENTRY(widget), val);
+	g_free(val);
+}
+
+void
+range_widget_get_bounds(GtkWidget *dialog, time_t *start, time_t *end)
+{
+	guint year, month, day;
+	struct tm start_tm, end_tm;
+
+	memset(&start_tm, 0, sizeof(struct tm));
+	memset(&end_tm, 0, sizeof(struct tm));
+
+	gtk_calendar_get_date(GTK_CALENDAR(start_calendar), &year, &month, &day);
+	start_tm.tm_year = year - 1900;
+	start_tm.tm_mon = month;
+	start_tm.tm_mday = day;
+	start_tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_hours));
+	start_tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_minutes));
+	start_tm.tm_sec = gtk_spin_button_get_value(GTK_SPIN_BUTTON(start_seconds));
+
+	gtk_calendar_get_date(GTK_CALENDAR(end_calendar), &year, &month, &day);
+	end_tm.tm_year = year - 1900;
+	end_tm.tm_mon = month;
+	end_tm.tm_mday = day;
+	end_tm.tm_hour = gtk_spin_button_get_value(GTK_SPIN_BUTTON(end_hours));
+	end_tm.tm_min = gtk_spin_button_get_value(GTK_SPIN_BUTTON(end_minutes));
+	end_tm.tm_sec = gtk_spin_button_get_value(GTK_SPIN_BUTTON(end_seconds));
+
+	*start = mktime(&start_tm);
+	*end = mktime(&end_tm);
+}
+
+void
+range_widget_destroy(GtkWidget *dialog)
+{
+	gtk_widget_destroy(dialog);
+}
+
+GtkWidget *
+range_widget_create()
+{
+	GtkWidget *range_dialog;
+	GtkWidget *dialog_vbox4;
+	GtkWidget *vbox7;
+	GtkWidget *hbox46;
+	GtkWidget *label43;
+	GtkWidget *label44;
+	GtkWidget *hbox42;
+	GtkWidget *hbox43;
+	GtkWidget *hbox44;
+	GtkObject *start_hours_adj;
+	GtkObject *start_minutes_adj;
+	GtkObject *start_seconds_adj;
+	GtkWidget *hbox45;
+	GtkObject *end_hours_adj;
+	GtkObject *end_minutes_adj;
+	GtkObject *end_seconds_adj;
+	GtkWidget *dialog_action_area4;
+	GtkWidget *cancel_button;
+	GtkWidget *ok_button;
+	GtkWidget *alignment55;
+	GtkWidget *hbox341;
+	GtkWidget *image49;
+	GtkWidget *label97;
+
+	range_dialog = gtk_dialog_new();
+	gtk_window_set_title(GTK_WINDOW(range_dialog), TIMELOG_TITLE);
+	gtk_window_set_modal(GTK_WINDOW(range_dialog), FALSE);
+	gtk_window_set_type_hint(GTK_WINDOW(range_dialog), GDK_WINDOW_TYPE_HINT_DIALOG);
+
+	dialog_vbox4 = GTK_DIALOG(range_dialog)->vbox;
+	gtk_widget_show(dialog_vbox4);
+
+	vbox7 = gtk_vbox_new(FALSE, 0);
+	gtk_widget_show(vbox7);
+	gtk_box_pack_start(GTK_BOX(dialog_vbox4), vbox7, TRUE, TRUE, 0);
+
+	hbox46 = gtk_hbox_new(FALSE, 20);
+	gtk_widget_show(hbox46);
+	gtk_box_pack_start(GTK_BOX(vbox7), hbox46, FALSE, FALSE, 5);
+
+	label43 = gtk_label_new("Start Time");
+	gtk_widget_show(label43);
+	gtk_box_pack_start(GTK_BOX(hbox46), label43, TRUE, TRUE, 0);
+
+	label44 = gtk_label_new("End Time");
+	gtk_widget_show(label44);
+	gtk_box_pack_start(GTK_BOX(hbox46), label44, TRUE, TRUE, 0);
+
+	hbox42 = gtk_hbox_new(FALSE, 20);
+	gtk_widget_show(hbox42);
+	gtk_box_pack_start(GTK_BOX(vbox7), hbox42, TRUE, TRUE, 0);
+
+	start_calendar = gtk_calendar_new();
+	gtk_widget_show(start_calendar);
+	gtk_box_pack_start(GTK_BOX(hbox42), start_calendar, TRUE, TRUE, 0);
+	gtk_calendar_display_options(GTK_CALENDAR(start_calendar),
+			GTK_CALENDAR_SHOW_HEADING | GTK_CALENDAR_SHOW_DAY_NAMES);
+
+	end_calendar = gtk_calendar_new();
+	gtk_widget_show(end_calendar);
+	gtk_box_pack_start(GTK_BOX(hbox42), end_calendar, TRUE, TRUE, 0);
+	gtk_calendar_display_options(GTK_CALENDAR(end_calendar),
+			GTK_CALENDAR_SHOW_HEADING | GTK_CALENDAR_SHOW_DAY_NAMES);
+
+	hbox43 = gtk_hbox_new(FALSE, 20);
+	gtk_widget_show(hbox43);
+	gtk_box_pack_start(GTK_BOX(vbox7), hbox43, FALSE, TRUE, 10);
+
+	hbox44 = gtk_hbox_new(FALSE, 0);
+	gtk_widget_show(hbox44);
+	gtk_box_pack_start(GTK_BOX(hbox43), hbox44, TRUE, TRUE, 0);
+
+	start_hours_adj = gtk_adjustment_new(0, -100, 100, 1, 10, 10);
+	start_hours = gtk_spin_button_new(GTK_ADJUSTMENT(start_hours_adj), 1, 0);
+	gtk_widget_show(start_hours);
+	gtk_box_pack_start(GTK_BOX(hbox44), start_hours, TRUE, TRUE, 0);
+	gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(start_hours), TRUE);
+	gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(start_hours), TRUE);
+
+	start_minutes_adj = gtk_adjustment_new(0, -100, 100, 1, 10, 10);
+	start_minutes = gtk_spin_button_new(GTK_ADJUSTMENT(start_minutes_adj), 1, 0);
+	gtk_widget_show(start_minutes);
+	gtk_box_pack_start(GTK_BOX(hbox44), start_minutes, TRUE, TRUE, 0);
+	gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(start_minutes), TRUE);
+	gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(start_minutes), TRUE);
+
+	start_seconds_adj = gtk_adjustment_new(0, -100, 100, 1, 10, 10);
+	start_seconds = gtk_spin_button_new(GTK_ADJUSTMENT(start_seconds_adj), 1, 0);
+	gtk_widget_show(start_seconds);
+	gtk_box_pack_start(GTK_BOX(hbox44), start_seconds, TRUE, TRUE, 0);
+	gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(start_seconds), TRUE);
+	gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(start_seconds), TRUE);
+
+	hbox45 = gtk_hbox_new(FALSE, 0);
+	gtk_widget_show(hbox45);
+	gtk_box_pack_start(GTK_BOX(hbox43), hbox45, TRUE, TRUE, 0);
+
+	end_hours_adj = gtk_adjustment_new(0, -100, 100, 1, 10, 10);
+	end_hours = gtk_spin_button_new(GTK_ADJUSTMENT(end_hours_adj), 1, 0);
+	gtk_widget_show(end_hours);
+	gtk_box_pack_start(GTK_BOX(hbox45), end_hours, TRUE, TRUE, 0);
+	gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(end_hours), TRUE);
+	gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(end_hours), TRUE);
+
+	end_minutes_adj = gtk_adjustment_new(0, -100, 100, 1, 10, 10);
+	end_minutes = gtk_spin_button_new(GTK_ADJUSTMENT(end_minutes_adj), 1, 0);
+	gtk_widget_show(end_minutes);
+	gtk_box_pack_start(GTK_BOX(hbox45), end_minutes, TRUE, TRUE, 0);
+	gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(end_minutes), TRUE);
+	gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(end_minutes), TRUE);
+
+	end_seconds_adj = gtk_adjustment_new(0, -100, 100, 1, 10, 10);
+	end_seconds = gtk_spin_button_new(GTK_ADJUSTMENT(end_seconds_adj), 1, 0);
+	gtk_widget_show(end_seconds);
+	gtk_box_pack_start(GTK_BOX(hbox45), end_seconds, TRUE, TRUE, 0);
+	gtk_spin_button_set_numeric(GTK_SPIN_BUTTON(end_seconds), TRUE);
+	gtk_spin_button_set_snap_to_ticks(GTK_SPIN_BUTTON(end_seconds), TRUE);
+
+	dialog_action_area4 = GTK_DIALOG(range_dialog)->action_area;
+	gtk_widget_show(dialog_action_area4);
+	gtk_button_box_set_layout(GTK_BUTTON_BOX(dialog_action_area4), GTK_BUTTONBOX_END);
+
+	cancel_button = gtk_button_new_from_stock("gtk-cancel");
+	gtk_widget_show(cancel_button);
+	gtk_dialog_add_action_widget(GTK_DIALOG(range_dialog), cancel_button, GTK_RESPONSE_CANCEL);
+	GTK_WIDGET_SET_FLAGS(cancel_button, GTK_CAN_DEFAULT);
+
+	ok_button = gtk_button_new();
+	gtk_widget_show(ok_button);
+	gtk_dialog_add_action_widget(GTK_DIALOG(range_dialog), ok_button, GTK_RESPONSE_OK);
+	GTK_WIDGET_SET_FLAGS(ok_button, GTK_CAN_DEFAULT);
+
+	alignment55 = gtk_alignment_new(0.5, 0.5, 0, 0);
+	gtk_widget_show(alignment55);
+	gtk_container_add(GTK_CONTAINER(ok_button), alignment55);
+
+	hbox341 = gtk_hbox_new(FALSE, 2);
+	gtk_widget_show(hbox341);
+	gtk_container_add(GTK_CONTAINER(alignment55), hbox341);
+
+	image49 = gtk_image_new_from_stock("gtk-ok", GTK_ICON_SIZE_BUTTON);
+	gtk_widget_show(image49);
+	gtk_box_pack_start(GTK_BOX(hbox341), image49, FALSE, FALSE, 0);
+
+	label97 = gtk_label_new_with_mnemonic("Select Time Range");
+	gtk_widget_show(label97);
+	gtk_box_pack_start(GTK_BOX(hbox341), label97, FALSE, FALSE, 0);
+
+	g_signal_connect((gpointer) start_hours, "value_changed", 
+			G_CALLBACK(cb_time_value_changed), NULL);
+	g_signal_connect((gpointer) start_minutes, "value_changed", 
+			G_CALLBACK(cb_time_value_changed), NULL);
+	g_signal_connect((gpointer) start_seconds, "value_changed",
+			G_CALLBACK(cb_time_value_changed), NULL);
+	g_signal_connect((gpointer) end_hours, "value_changed",
+			G_CALLBACK(cb_time_value_changed), NULL);
+	g_signal_connect((gpointer) end_minutes, "value_changed",
+			G_CALLBACK(cb_time_value_changed), NULL);
+	g_signal_connect((gpointer) end_seconds, "value_changed",
+			G_CALLBACK(cb_time_value_changed), NULL);
+
+	gchar *val = g_strdup_printf("%02d", 0);
+	gtk_entry_set_text(GTK_ENTRY(start_hours), val);
+	gtk_entry_set_text(GTK_ENTRY(start_minutes), val);
+	gtk_entry_set_text(GTK_ENTRY(start_seconds), val);
+	gtk_entry_set_text(GTK_ENTRY(end_hours), val);
+	gtk_entry_set_text(GTK_ENTRY(end_minutes), val);
+	gtk_entry_set_text(GTK_ENTRY(end_seconds), val);
+	g_free(val);
+
+	gtk_widget_grab_default(ok_button);
+
+	return range_dialog;
+}
============================================================
--- timelog/range-widget.h	3e8c41b90fe646f4086c573f79e9b5501d03802b
+++ timelog/range-widget.h	3e8c41b90fe646f4086c573f79e9b5501d03802b
@@ -0,0 +1,31 @@
+/*
+ * Gaim-TimeLog plugin.
+ *
+ * Copyright (c) 2006 Jon Oberheide.
+ *
+ * $Id: range-widget.h,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * 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 _RANGE_WIDGET_H_
+#define _RANGE_WIDGET_H_
+
+GtkWidget *range_widget_create();
+void range_widget_destroy(GtkWidget *dialog);
+void range_widget_get_bounds(GtkWidget *dialog, time_t *start, time_t *end);
+
+#endif
============================================================
--- timelog/timelog.c	3c28059681f56f5e4a070fefb30dab3f904ffdc5
+++ timelog/timelog.c	3c28059681f56f5e4a070fefb30dab3f904ffdc5
@@ -0,0 +1,191 @@
+/*
+ * TimeLog plugin.
+ *
+ * Copyright (c) 2006 Jon Oberheide.
+ *
+ * $Id: timelog.c,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "../common/pp_internal.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include <plugin.h>
+#include <request.h>
+#include <log.h>
+#include <util.h>
+#include <debug.h>
+#include <version.h>
+
+#include <gtkplugin.h>
+
+#include "timelog.h"
+#include "log-widget.h"
+#include "range-widget.h"
+
+typedef struct {
+	PurpleAccount *account;
+	GList *logs;
+	time_t start;
+	time_t end;
+} log_query_t;
+
+static void
+foreach_log(gpointer value, gpointer data)
+{
+	PurpleLog *log = (PurpleLog *) value;
+	log_query_t *query = (log_query_t *) data;
+
+	if (log->time >= query->start && log->time <= query->end) {
+		query->logs = g_list_append(query->logs, log);
+	}
+}
+
+static void
+foreach_log_set(gpointer key, gpointer value, gpointer data)
+{
+	GList *logs;
+	PurpleLogSet *set = (PurpleLogSet *) value;
+	log_query_t *query = (log_query_t *) data;
+
+	if (query->account != set->account) {
+		return;
+	}
+
+	logs = purple_log_get_logs(set->type, set->name, set->account);
+
+	g_list_foreach(logs, foreach_log, query);
+}
+
+static void
+cb_select_time(gpointer data, PurpleRequestFields *fields)
+{
+	GHashTable *log_sets;
+	GtkWidget *range_dialog;
+	log_query_t *query;
+
+	query = g_new0(log_query_t, 1);
+	query->account = purple_request_fields_get_account(fields, "acct");
+
+	range_dialog = range_widget_create();
+
+	if (gtk_dialog_run(GTK_DIALOG(range_dialog)) == GTK_RESPONSE_OK) {
+		range_widget_get_bounds(range_dialog, &query->start, &query->end);
+
+		log_sets = purple_log_get_log_sets();
+		g_hash_table_foreach(log_sets, foreach_log_set, query);
+
+		tl_debug("found %u logs for %s between %u and %u\n", 
+				g_list_length(query->logs),
+				query->account->username,
+				query->start, query->end);
+
+		log_widget_display_logs(query->logs);
+
+		g_hash_table_destroy(log_sets);
+	}
+
+	range_widget_destroy(range_dialog);
+
+	g_free(query);
+}
+
+static void
+cb_select_account(PurplePluginAction *action)
+{
+	PurpleRequestFields *request;
+	PurpleRequestFieldGroup *group;
+	PurpleRequestField *field;
+
+	group = purple_request_field_group_new(NULL);
+
+	field = purple_request_field_account_new("acct", "Account", NULL);
+	purple_request_field_account_set_show_all(field, TRUE);
+	purple_request_field_group_add_field(group, field);
+
+	request = purple_request_fields_new();
+	purple_request_fields_add_group(request, group);
+
+	purple_request_fields(action->plugin, TIMELOG_TITLE,
+			"Select account to view logs for:", NULL, request,
+			"Select Account", G_CALLBACK(cb_select_time),
+			"Cancel", NULL, NULL, NULL, NULL, NULL);
+}
+
+static GList *
+actions(PurplePlugin *plugin, gpointer context)
+{
+	GList *l = NULL;
+	PurplePluginAction *act = NULL;
+
+	act = purple_plugin_action_new("Select Account/Time", cb_select_account);
+	l = g_list_append(l, act);
+
+	return l;
+}
+
+static gboolean
+load_plugin(PurplePlugin *plugin)
+{
+	return TRUE;
+}
+
+static gboolean
+unload_plugin(PurplePlugin *plugin)
+{
+	return TRUE;
+}
+
+static PurplePluginInfo info =
+{
+	PURPLE_PLUGIN_MAGIC,
+	PURPLE_MAJOR_VERSION,
+	PURPLE_MINOR_VERSION,
+	PURPLE_PLUGIN_STANDARD,				/**< type	*/
+	PIDGIN_PLUGIN_TYPE,				/**< ui_req	*/
+	0,						/**< flags	*/
+	NULL,						/**< deps	*/
+	PURPLE_PRIORITY_DEFAULT,				/**< priority	*/
+	TIMELOG_PLUGIN_ID,				/**< id		*/
+	TIMELOG_TITLE,					/**< name	*/
+	PP_VERSION,					/**< version	*/
+							/**  summary	*/
+	"Allows the viewing of Pidgin logs within a specific time range",
+							/**  desc	*/
+	"Allows the viewing of Pidgin logs within a specific time range",
+	"Jon Oberheide <jon at oberheide.org>",		/**< author	*/
+	"http://jon.oberheide.org/projects/gaim-timelog/",
+							/**< homepage	*/
+	load_plugin,					/**< load	*/
+	unload_plugin,					/**< unload	*/
+	NULL,						/**< destroy	*/
+	NULL,						/**< ui_info	*/
+	NULL,						/**< extra_info	*/
+	NULL,						/**< pref info	*/
+	actions
+};
+
+static void
+init_plugin(PurplePlugin *plugin)
+{
+}
+
+PURPLE_INIT_PLUGIN(timelog, init_plugin, info)
============================================================
--- timelog/timelog.h	beff3f9c7142e797c5d98010eca7d4db6d23203c
+++ timelog/timelog.h	beff3f9c7142e797c5d98010eca7d4db6d23203c
@@ -0,0 +1,35 @@
+/*
+ * TimeLog plugin.
+ *
+ * Copyright (c) 2006 Jon Oberheide.
+ *
+ * $Id: gaim-timelog.h,v 1.30 2005/12/24 05:14:55 binaryjono Exp $
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * 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 _TIMELOG_H_
+#define _TIMELOG_H
+
+#define TIMELOG_PLUGIN_ID	"gtk-binaryjono-timelog"
+#define TIMELOG_TITLE		"TimeLog"
+
+#define tl_debug(fmt, ...)	purple_debug(PURPLE_DEBUG_INFO, TIMELOG_TITLE, \
+							fmt, ## __VA_ARGS__);
+#define tl_error(fmt, ...)	purple_debug(PURPLE_DEBUG_ERROR, TIMELOG_TITLE, \
+							fmt, ## __VA_ARGS__);
+
+#endif
============================================================
--- configure.ac	8487a8587c930442d8dbebe05a9876be1be1a915
+++ configure.ac	fdbb066f7b4bacc8cdcb86b02cf9ae4c297f25f8
@@ -342,6 +342,7 @@ AC_OUTPUT([
 	stocker/Makefile
 	switchspell/Makefile
 	talkfilters/Makefile
+	timelog/Makefile
 	xchat-chats/Makefile
 	xmmsremote/Makefile
 	xmmsremote/pixmaps/Makefile


More information about the Plugins-commits mailing list