]> Pileus Git - aweather/blobdiff - src/plugins/alert.c
Add warning/watch/alert plugin
[aweather] / src / plugins / alert.c
diff --git a/src/plugins/alert.c b/src/plugins/alert.c
new file mode 100644 (file)
index 0000000..1641abd
--- /dev/null
@@ -0,0 +1,617 @@
+/*
+ * Copyright (C) 2010 Andy Spencer <andy753421@gmail.com>
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <grits.h>
+#include <GL/gl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "alert.h"
+#include "alert-info.h"
+
+/**********
+ * Alerts *
+ **********/
+/* Data types */
+typedef struct {
+       char sep0;
+       char class [1 ]; char sep1;
+       char action[3 ]; char sep2;
+       char office[4 ]; char sep3;
+       char phenom[2 ]; char sep4;
+       char signif[1 ]; char sep5;
+       char event [4 ]; char sep6;
+       char begin [12]; char sep7;
+       char end   [12]; char sep8;
+} AWeatherVtec;
+
+typedef struct {
+       char *title;   // Winter Weather Advisory issued December 19 at 8:51PM
+       char *link;    // http://www.weather.gov/alerts-beta/wwacapget.php?x=AK20101219205100AFGWinterWeatherAdvisoryAFG20101220030000AK
+       char *summary; // ...WINTER WEATHER ADVISORY REMAINS IN EFFECT UNTIL 6
+       struct {
+               time_t  effective;  // 2010-12-19T20:51:00-09:00
+               time_t  expires;    // 2010-12-20T03:00:00-09:00
+               char   *status;     // Actual
+               char   *urgency;    // Expected
+               char   *severity;   // Minor
+               char   *certainty;  // Likely
+               char   *area_desc;  // Northeastern Brooks Range; Northwestern Brooks Range
+               char   *fips6;      // 006015 006023 006045 006105
+               AWeatherVtec *vtec; // /X.CON.PAFG.WW.Y.0064.000000T0000Z-101220T0300Z/
+       } cap;
+} AWeatherAlert;
+
+/* Alert parsing */
+typedef struct {
+       AWeatherAlert *alert;
+       GList         *alerts;
+       gchar         *text;
+       gchar         *value_name;
+} ParseData;
+time_t parse_time(gchar *iso8601)
+{
+       GTimeVal tv = {};
+       g_time_val_from_iso8601(iso8601, &tv);
+       return tv.tv_sec;
+}
+AWeatherVtec *parse_vtec(char *buf)
+{
+       AWeatherVtec *vtec = g_new0(AWeatherVtec, 1);
+       strncpy((char*)vtec, buf, sizeof(AWeatherVtec));
+       vtec->sep0 = vtec->sep1 = vtec->sep2 = '\0';
+       vtec->sep3 = vtec->sep4 = vtec->sep5 = '\0';
+       vtec->sep6 = vtec->sep7 = vtec->sep8 = '\0';
+       return vtec;
+}
+void alert_start(GMarkupParseContext *context, const gchar *name,
+               const gchar **keys, const gchar **vals,
+               gpointer user_data, GError **error)
+{
+       //g_debug("start %s", name);
+       ParseData *data = user_data;
+       if (g_str_equal(name, "entry"))
+               data->alert  = g_new0(AWeatherAlert, 1);
+}
+void alert_end(GMarkupParseContext *context, const gchar *name,
+               gpointer user_data, GError **error)
+{
+       //g_debug("end %s", name);
+       ParseData     *data  = user_data;
+       AWeatherAlert *alert = data->alert;
+       char          *text  = data->text;
+
+       if (g_str_equal(name, "entry"))
+               data->alerts = g_list_prepend(data->alerts, data->alert);
+
+       if (!text || !alert) return;
+       if      (g_str_equal(name, "title"))         alert->title         = g_strdup(text);
+       else if (g_str_equal(name, "id"))            alert->link          = g_strdup(text); // hack
+       else if (g_str_equal(name, "summary"))       alert->summary       = g_strdup(text);
+       else if (g_str_equal(name, "cap:effective")) alert->cap.effective = parse_time(text);
+       else if (g_str_equal(name, "cap:expires"))   alert->cap.expires   = parse_time(text);
+       else if (g_str_equal(name, "cap:status"))    alert->cap.status    = g_strdup(text);
+       else if (g_str_equal(name, "cap:urgency"))   alert->cap.urgency   = g_strdup(text);
+       else if (g_str_equal(name, "cap:severity"))  alert->cap.severity  = g_strdup(text);
+       else if (g_str_equal(name, "cap:certainty")) alert->cap.certainty = g_strdup(text);
+       else if (g_str_equal(name, "cap:areaDesc"))  alert->cap.area_desc = g_strdup(text);
+
+       if (g_str_equal(name, "valueName")) {
+               if (data->value_name)
+                       g_free(data->value_name);
+               data->value_name = g_strdup(text);
+       }
+
+       if (g_str_equal(name, "value") && data->value_name) {
+               if (g_str_equal(data->value_name, "FIPS6")) alert->cap.fips6 = g_strdup(text);
+               if (g_str_equal(data->value_name, "VTEC"))  alert->cap.vtec  = parse_vtec(text);
+       }
+}
+void alert_text(GMarkupParseContext *context, const gchar *text,
+               gsize len, gpointer user_data, GError **error)
+{
+       //g_debug("text %s", text);
+       ParseData *data = user_data;
+       if (data->text)
+               g_free(data->text);
+       data->text = strndup(text, len);
+}
+
+/* Alert methods */
+GList *alert_parse(gchar *text, gsize len)
+{
+       g_debug("GritsPluginAlert: alert_parse");
+       GMarkupParser parser = {
+               .start_element = alert_start,
+               .end_element   = alert_end,
+               .text          = alert_text,
+       };
+       ParseData data = {};
+       GMarkupParseContext *context =
+               g_markup_parse_context_new(&parser, 0, &data, NULL);
+       g_markup_parse_context_parse(context, text, len, NULL);
+       g_markup_parse_context_free(context);
+       if (data.text)
+               g_free(data.text);
+       if (data.value_name)
+               g_free(data.value_name);
+       return data.alerts;
+}
+
+void alert_free(AWeatherAlert *alert)
+{
+       g_free(alert->title);
+       g_free(alert->link);
+       g_free(alert->summary);
+       g_free(alert->cap.status);
+       g_free(alert->cap.urgency);
+       g_free(alert->cap.severity);
+       g_free(alert->cap.certainty);
+       g_free(alert->cap.area_desc);
+       g_free(alert->cap.fips6);
+       g_free(alert->cap.vtec);
+       g_free(alert);
+}
+
+void alert_test(char *file)
+{
+       gchar *text; gsize len;
+       g_file_get_contents(file, &text, &len, NULL);
+       GList *alerts = alert_parse(text, len);
+       g_free(text);
+       for (GList *cur = alerts; cur; cur = cur->next) {
+               AWeatherAlert *alert = cur->data;
+               g_message("alert:");
+               g_message("     title         = %s",  alert->title        );
+               g_message("     link          = %s",  alert->link         );
+               g_message("     summary       = %s",  alert->summary      );
+               g_message("     cat.effective = %lu", alert->cap.effective);
+               g_message("     cat.expires   = %lu", alert->cap.expires  );
+               g_message("     cat.status    = %s",  alert->cap.status   );
+               g_message("     cat.urgency   = %s",  alert->cap.urgency  );
+               g_message("     cat.severity  = %s",  alert->cap.severity );
+               g_message("     cat.certainty = %s",  alert->cap.certainty);
+               g_message("     cat.area_desc = %s",  alert->cap.area_desc);
+               g_message("     cat.fips6     = %s",  alert->cap.fips6    );
+               g_message("     cat.vtec      = %p",  alert->cap.vtec     );
+       }
+       g_list_foreach(alerts, (GFunc)alert_free, NULL);
+       g_list_free(alerts);
+}
+
+
+/********
+ * FIPS *
+ ********/
+int int_compare(int a, int b)
+{
+       return (a <  b) ? -1 :
+              (a == b) ?  0 : 1;
+}
+
+gdouble timed(void)
+{
+       GTimeVal tv;
+       g_get_current_time(&tv);
+       return (gdouble)tv.tv_sec + (gdouble)tv.tv_usec/G_USEC_PER_SEC;
+}
+
+gdouble fips_area(gdouble *a, gdouble *b, gdouble *c)
+{
+       gdouble cross[3];
+       crossd3(a, b, c, cross);
+       return  lengthd(cross)/2;
+}
+
+gdouble fips_remove_one(GList **poly, gdouble thresh)
+{
+       gdouble min_area = thresh;
+       GList  *min_list = NULL;
+       if (g_list_length(*poly) <= 4)
+               return 0;
+       for (GList *prev = *poly; prev->next->next; prev = prev->next) {
+               GList *cur  = prev->next;
+               GList *next = prev->next->next;
+               gdouble area = fips_area(prev->data, cur->data, next->data);
+               if (area < min_area) {
+                       min_area = area;
+                       min_list = cur;
+               }
+       }
+       if (min_list) {
+               *poly = g_list_delete_link(*poly, min_list);
+               return min_area;
+       } else {
+               return 0;
+       }
+}
+
+gpointer fips_simplify(gdouble (*coords)[3], gdouble thresh, gint key)
+{
+       GList *poly = NULL;
+
+       /* Add points to poly list */
+       for (int i = 0; coords[i][0]; i++)
+               poly = g_list_prepend(poly, coords[i]);
+       int before = g_list_length(poly);
+
+       /* Simplify poly list */
+       //fprintf(stderr, "GritsPluginAlert: remove_one ");
+       gdouble area;
+       while ((area = fips_remove_one(&poly, thresh)) > 0) {
+               //fprintf(stderr, " %f", area);
+       }
+       //fprintf(stderr, "\n");
+       int after = g_list_length(poly);
+
+       /* Copy points back */
+       gdouble (*simp)[3] = (gpointer)g_new0(gdouble, 3*(after+1));
+       GList *cur = poly;
+       for (int i = 0; i < after; i++) {
+               gdouble *coord = cur->data;
+               simp[i][0] = coord[0];
+               simp[i][1] = coord[1];
+               simp[i][2] = coord[2];
+               cur = cur->next;
+       }
+
+       (void)before;
+       (void)after;
+       g_debug("GritsPluginAlert: fips_simplify %d: %d -> %d",
+                       key, before, after);
+       g_list_free(poly);
+       g_free(coords);
+       return simp;
+}
+
+void fips_print(FILE *fd, gchar *fips, gchar *name, gchar *state,
+               gdouble (**points)[3])
+{
+       fprintf(fd, "%s\t%s\t%s", fips, name, state);
+       for (int i = 0; points[i]; i++) {
+               fprintf(fd, "\t");
+               for (int j = 0; points[i][j][0]; j++) {
+                       //fwrite(points[i][j], sizeof(double), 3, fd);
+                       gdouble lat, lon;
+                       xyz2ll(points[i][j][0], points[i][j][1],
+                                       points[i][j][2], &lat, &lon);
+                       fprintf(fd, "%.7lf,%.7lf ", lat, lon);
+               }
+       }
+       fprintf(fd, "\n");
+}
+
+GTree *fips_parse(gchar *text)
+{
+       g_debug("GritsPluginAlert: fips_parse");
+       //FILE *fd = fopen("/tmp/fips_polys_simp.txt", "w+");
+
+       GTree *tree = g_tree_new((GCompareFunc)int_compare);
+       gchar **lines = g_strsplit(text, "\n", -1);
+       for (gint li = 0; lines[li]; li++) {
+               /* Split and count parts */
+               gchar **sparts = g_strsplit(lines[li], "\t", -1);
+               int     nparts = g_strv_length(sparts);
+               if (nparts < 4) {
+                       g_strfreev(sparts);
+                       continue;
+               }
+
+               GritsPoint center = {0,0,0};
+               gdouble (**polys)[3] = (gpointer)g_new0(double*, nparts-3+1);
+               for (int pi = 3; pi < nparts; pi++) {
+                       /* Split and count coordinates */
+                       gchar **scoords = g_strsplit(sparts[pi], " ", -1);
+                       int     ncoords = g_strv_length(scoords);
+
+                       /* Create binary coords */
+                       gdouble (*coords)[3] = (gpointer)g_new0(gdouble, 3*(ncoords+1));
+                       for (int ci = 0; ci < ncoords; ci++) {
+                               gdouble lat, lon;
+                               //sscanf(scoords[ci], "%lf,%lf", &lon, &lat);
+                               sscanf(scoords[ci], "%lf,%lf", &lat, &lon);
+                               if (ci == 0) {
+                                       center.lat  = lat;
+                                       center.lon  = lon;
+                                       center.elev = 0;
+                               }
+                               lle2xyz(lat, lon, 0,
+                                       &coords[ci][0],
+                                       &coords[ci][1],
+                                       &coords[ci][2]);
+                       }
+
+                       /* Simplify coords/contour */
+                       //coords = fips_simplify(coords, 1000*1000*4, li);
+
+                       /* Insert coords into poly array */
+                       polys[pi-3] = coords;
+                       g_strfreev(scoords);
+               }
+
+               /* print simplified polys */
+               //fips_print(fd, sparts[0], sparts[1], sparts[2], polys);
+
+               /* Create GritsPoly */
+               GritsPoly *poly = grits_poly_new(polys);
+               GRITS_OBJECT(poly)->center  = center;
+               GRITS_OBJECT(poly)->skip   |= GRITS_SKIP_CENTER;
+               GRITS_OBJECT(poly)->skip   |= GRITS_SKIP_STATE;
+
+               /* Insert polys into the tree */
+               gint id = g_ascii_strtoll(sparts[0], NULL, 10);
+               g_tree_insert(tree, (gpointer)id, poly);
+               g_strfreev(sparts);
+       }
+       g_strfreev(lines);
+
+       //fclose(fd);
+       return tree;
+}
+
+/********************
+ * GritsPluginAlert *
+ ********************/
+/* Setup helpers
+ * init:
+ *   parse counties
+ *   init county polys
+ *   init config area
+ * refresh:
+ *   parse alert
+ *   update buttons
+ *   update counties
+ * clicked:
+ *   update counties
+ */
+
+/* Update counties */
+static gboolean _hide_county(gchar *_, GritsObject *county)
+{
+       g_object_set_data(G_OBJECT(county), "info", NULL);
+       //county->hidden = TRUE;
+       return FALSE;
+}
+static void _update_counties(GritsPluginAlert *alert)
+{
+       /* Setup counties based on alerts:
+        * foreach alert in alerts:
+        *      info   = infotable.get(alert.type)
+        *      foreach fips  in alert.fipses:
+        *              county = counties.get(fips)
+        *              if info is higher priority
+        *                      county.color = info.color
+        */
+       g_debug("GritsPluginAlert: _update_counties");
+       g_tree_foreach(alert->counties, (GTraverseFunc)_hide_county, NULL);
+       for (GList *cur = alert->alerts; cur; cur = cur->next) {
+               AWeatherAlert *msg = cur->data;
+
+               /* Find alert info */
+               AlertInfo *info = alert_info_find(msg->title);
+               if (!info) {
+                       g_warning("GritsPluginAlert: unknown warning - %s", msg->title);
+                       continue;
+               }
+               if (!info->enabled)
+                       continue;
+
+               /* Set color for each county in alert */
+               gchar **fipses = g_strsplit(msg->cap.fips6, " ", -1);
+               for (int i = 0; fipses[i]; i++) {
+                       gint fips = g_ascii_strtoll(fipses[i], NULL, 10);
+                       GritsPoly *county = g_tree_lookup(alert->counties, (gpointer)fips);
+                       if (!county)
+                               continue;
+                       AlertInfo *old = g_object_get_data(G_OBJECT(county), "info");
+                       if (old != NULL && old < info)
+                               continue;
+                       g_object_set_data(G_OBJECT(county), "info", info);
+                       county->color[0] = (float)info->color[0] / 256;
+                       county->color[1] = (float)info->color[1] / 256;
+                       county->color[2] = (float)info->color[2] / 256;
+                       county->color[3] = 0.25;
+                       GRITS_OBJECT(county)->hidden = FALSE;
+               }
+               g_strfreev(fipses);
+       }
+       gtk_widget_queue_draw(GTK_WIDGET(alert->viewer));
+}
+
+/* Update buttons */
+static void _alert_click(GtkRadioButton *button, gpointer alert)
+{
+       g_debug("GritsPluginAlert: _alert_click");
+       AlertInfo *info = g_object_get_data(G_OBJECT(button), "info");
+       info->enabled = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
+       _update_counties(alert);
+}
+
+static GtkWidget *_make_button(AlertInfo *info)
+{
+       g_debug("GritsPluginAlert: _make_button - %s", info->title);
+       GdkColor black = {0, 0, 0, 0};
+       GdkColor color = {0, info->color[0]<<8, info->color[1]<<8, info->color[2]<<8};
+
+       gchar text[6+7];
+       g_snprintf(text, sizeof(text), "<b>%.5s</b>", info->title);
+
+       GtkWidget *button = gtk_toggle_button_new();
+       GtkWidget *align  = gtk_alignment_new(0.5, 0.5, 1, 1);
+       GtkWidget *cbox   = gtk_event_box_new();
+       GtkWidget *label  = gtk_label_new(text);
+       for (int state = 0; state < GTK_STATE_INSENSITIVE; state++) {
+               gtk_widget_modify_fg(label, state, &black);
+               gtk_widget_modify_bg(cbox,  state, &color);
+       } /* Yuck.. */
+       g_object_set_data(G_OBJECT(button), "info", info);
+       gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
+       gtk_alignment_set_padding(GTK_ALIGNMENT(align), 2, 2, 4, 4);
+       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), info->enabled);
+       gtk_widget_set_tooltip_text(GTK_WIDGET(button), info->title);
+       gtk_container_add(GTK_CONTAINER(cbox), label);
+       gtk_container_add(GTK_CONTAINER(align), cbox);
+       gtk_container_add(GTK_CONTAINER(button), align);
+       return button;
+}
+
+static void _update_buttons(GritsPluginAlert *alert)
+{
+       g_debug("GritsPluginAlert: _update_buttons");
+       /* Delete old buttons */
+       GList *frames = gtk_container_get_children(GTK_CONTAINER(alert->config));
+       for (GList *frame = frames; frame; frame = frame->next) {
+               GtkWidget *table = gtk_bin_get_child(GTK_BIN(frame->data));
+               GList *btns = gtk_container_get_children(GTK_CONTAINER(table));
+               g_list_foreach(btns, (GFunc)gtk_widget_destroy, NULL);
+       }
+
+       /* Add new buttons */
+       for (int i = 0; alert_info[i].title; i++) {
+               if (!alert_info[i].current)
+                       continue;
+
+               GtkWidget *table = g_object_get_data(G_OBJECT(alert->config),
+                               alert_info[i].category);
+               GList *kids = gtk_container_get_children(GTK_CONTAINER(table));
+               int nkids = g_list_length(kids);
+               int x = nkids % 3;
+               int y = nkids / 3;
+               g_list_free(kids);
+
+               GtkWidget *button = _make_button(&alert_info[i]);
+               gtk_table_attach(GTK_TABLE(table), button, x, x+1, y, y+1,
+                               GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 0);
+               g_signal_connect(button, "clicked",
+                               G_CALLBACK(_alert_click), alert);
+       }
+}
+
+/* Init helpers */
+static gboolean _add_county(gint key, GritsObject *poly, GritsViewer *viewer)
+{
+       grits_viewer_add(viewer, poly, GRITS_LEVEL_WORLD+1, TRUE);
+       return FALSE;
+}
+
+static GtkWidget *_make_config(void)
+{
+       gchar *labels[] = {"Warnings", "Watches", "Advisories", "Other"};
+       gchar *keys[]   = {"warning",  "watch",   "advisory",   "other"};
+       gint   cols[]   = {3, 2, 2, 2};
+       GtkWidget *config = gtk_hbox_new(FALSE, 10);
+       for (int i = 0; i < G_N_ELEMENTS(labels); i++) {
+               GtkWidget *frame = gtk_frame_new(labels[i]);
+               GtkWidget *table = gtk_table_new(1, cols[i], TRUE);
+               gtk_container_add(GTK_CONTAINER(frame), table);
+               gtk_box_pack_start(GTK_BOX(config), frame, TRUE, TRUE, 0);
+               g_object_set_data(G_OBJECT(config), keys[i], table);
+       }
+       return config;
+}
+
+/* Methods */
+GritsPluginAlert *grits_plugin_alert_new(GritsViewer *viewer, GritsPrefs *prefs)
+{
+       g_debug("GritsPluginAlert: new");
+       GritsPluginAlert *alert = g_object_new(GRITS_TYPE_PLUGIN_ALERT, NULL);
+       g_tree_foreach(alert->counties, (GTraverseFunc)_add_county, viewer);
+       alert->viewer = viewer;
+       alert->prefs  = prefs;
+       return alert;
+}
+
+static GtkWidget *grits_plugin_alert_get_config(GritsPlugin *_alert)
+{
+       GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(_alert);
+       return alert->config;
+}
+
+
+/* GObject code */
+static void grits_plugin_alert_plugin_init(GritsPluginInterface *iface);
+G_DEFINE_TYPE_WITH_CODE(GritsPluginAlert, grits_plugin_alert, G_TYPE_OBJECT,
+               G_IMPLEMENT_INTERFACE(GRITS_TYPE_PLUGIN,
+                       grits_plugin_alert_plugin_init));
+static void grits_plugin_alert_plugin_init(GritsPluginInterface *iface)
+{
+       g_debug("GritsPluginAlert: plugin_init");
+       /* Add methods to the interface */
+       iface->get_config = grits_plugin_alert_get_config;
+}
+static void grits_plugin_alert_init(GritsPluginAlert *alert)
+{
+       g_debug("GritsPluginAlert: class_init");
+       /* Set defaults */
+       alert->config   = _make_config();
+
+       /* Load counties */
+       gchar *text; gsize len;
+       const gchar *fips_file = "/scratch/aweather/data/fips_polys_simp.txt";
+       g_file_get_contents(fips_file, &text, &len, NULL);
+       alert->counties = fips_parse(text);
+       g_free(text);
+
+       /* Load alerts (once for testing) */
+       const gchar *alert_file = "/scratch/aweather/src/plugins/alert4.xml";
+       g_file_get_contents(alert_file, &text, &len, NULL);
+       alert->alerts = alert_parse(text, len);
+       g_free(text);
+
+       /* For now, set alert "current" at init */
+       for (GList *cur = alert->alerts; cur; cur = cur->next) {
+               AWeatherAlert *msg  = cur->data;
+               AlertInfo     *info = alert_info_find(msg->title);
+               if (info) info->current = TRUE;
+       }
+
+       /* For now, run updates */
+       _update_buttons(alert);
+       _update_counties(alert);
+}
+static void grits_plugin_alert_dispose(GObject *gobject)
+{
+       g_debug("GritsPluginAlert: dispose");
+       GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject);
+       (void)alert; // TODO
+       /* Drop references */
+       G_OBJECT_CLASS(grits_plugin_alert_parent_class)->dispose(gobject);
+}
+static void grits_plugin_alert_finalize(GObject *gobject)
+{
+       g_debug("GritsPluginAlert: finalize");
+       GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject);
+       /* Free data */
+       gtk_widget_destroy(alert->config);
+
+       /* Free countes */
+       g_tree_destroy(alert->counties);
+
+       /* Free alerts (once for testing) */
+       g_list_foreach(alert->alerts, (GFunc)alert_free, NULL);
+       g_list_free(alert->alerts);
+
+       G_OBJECT_CLASS(grits_plugin_alert_parent_class)->finalize(gobject);
+}
+static void grits_plugin_alert_class_init(GritsPluginAlertClass *klass)
+{
+       g_debug("GritsPluginAlert: class_init");
+       GObjectClass *gobject_class = (GObjectClass*)klass;
+       gobject_class->dispose  = grits_plugin_alert_dispose;
+       gobject_class->finalize = grits_plugin_alert_finalize;
+}