*.ico
aweather
main.ui
+fips.txt
configdir = $(datadir)/aweather/
dist_config_DATA = defaults.ini
+fipsdir = $(datadir)/aweather/
+dist_fips_DATA = fips.txt
+
logodir = $(datadir)/aweather/
dist_logo_DATA = logo.svg
noinst_DATA = icons/48x48/aweather.ico
+fips.txt:
+ wget http://lug.rose-hulman.edu/proj/aweather/files/fips/fips.txt
+
install-data-hook:
if test -z "$(DESTDIR)"; then \
gtk-update-icon-cache -f -t $(datadir)/icons/hicolor; \
env=true
elev=false
radar=true
+alert=true
pluginsdir = $(pkglibdir)
-plugins_LTLIBRARIES =
+plugins_LTLIBRARIES = alert.la
+
+alert_la_SOURCES = \
+ alert.c alert.h \
+ alert-info.c alert-info.h
+alert_la_CPPFLAGS = \
+ -DPKGDATADIR="\"$(dots)$(pkgdatadir)\"" \
+ -I$(top_srcdir)/lib
+alert_la_LIBADD = $(GRITS_LIBS) \
+ $(addprefix $(top_srcdir)/lib/,$(gl_LTLIBOBJS))
if HAVE_RSL
plugins_LTLIBRARIES += radar.la
--- /dev/null
+/*
+ * Copyright (C) 2010-2011 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/>.
+ */
+
+/* Taken from: http://www.weather.gov/wwamap-prd/faq.php */
+
+#include "alert-info.h"
+
+AlertInfo alert_info[] = {
+ /* Warnings */
+ // hide poly
+ // title category abbr prio | cur | color
+ {"Ashfall Warning" , "warning", "Ashfall", 72 , 0, 0, 0, {169, 169, 169}}, // darkgray
+ {"Avalanche Warning" , "warning", "Avalanche", 21 , 0, 0, 0, {30 , 144, 255}}, // dodgerblue
+ {"Blizzard Warning" , "warning", "Blizzard", 12 , 0, 0, 0, {255, 69 , 0 }}, // orangered
+ {"Civil Danger Warning" , "warning", "Cvl Danger", 7 , 0, 0, 0, {255, 182, 193}}, // lightpink
+ {"Coastal Flood Warning" , "warning", "C Flood", 24 , 0, 0, 0, {34 , 139, 34 }}, // forestgreen
+ {"Dust Storm Warning" , "warning", "Dust Storm", 32 , 0, 0, 0, {255, 228, 196}}, // bisque
+ {"Earthquake Warning" , "warning", "Earthquake", 22 , 0, 0, 0, {139, 69 , 19 }}, // saddlebrown
+ {"Excessive Heat Warning" , "warning", "Heat", 31 , 0, 0, 0, {199, 21 , 133}}, // mediumvioletred
+ {"Extreme Cold Warning" , "warning", "Cold", 44 , 0, 0, 0, {0 , 0 , 255}}, // blue
+ {"Extreme Wind Warning" , "warning", "Ext Wind", 3 , 0, 0, 1, {255, 20 , 147}}, // deeppink
+ {"Fire Warning" , "warning", "Fire", 7 , 0, 0, 0, {160, 82 , 45 }}, // sienna
+ {"Flash Flood Warning" , "warning", "F Flood", 5 , 0, 0, 1, {139, 0 , 0 }}, // darkred
+ {"Flood Warning" , "warning", "Flood", 5 , 0, 0, 1, {0 , 255, 0 }}, // lime
+ {"Freeze Warning" , "warning", "Freeze", 45 , 0, 0, 0, {0 , 255, 255}}, // cyan
+ {"Gale Warning" , "warning", "Gale", 40 , 0, 0, 0, {221, 160, 221}}, // plum
+ {"Hard Freeze Warning" , "warning", "H Freeze", 45 , 0, 0, 0, {0 , 0 , 255}}, // blue
+ {"Hazardous Materials Warning" , "warning", "Haz Mat", 7 , 0, 0, 0, {75 , 0 , 130}}, // indigo
+ {"Hazardous Seas Warning" , "warning", "Haz Seas", 67 , 0, 0, 0, {221, 160, 221}}, // plum
+ {"Heavy Freezing Spray Warning" , "warning", "Fz Spray", 63 , 0, 0, 0, {0 , 191, 255}}, // deepskyblue
+ {"Heavy Snow Warning" , "warning", "Heavy Snow", 15 , 0, 0, 0, {138, 43 , 226}}, // blueviolet
+ {"High Surf Warning" , "warning", "High Surf", 28 , 0, 0, 0, {34 , 139, 34 }}, // forestgreen
+ {"High Wind Warning" , "warning", "High Wind", 17 , 0, 0, 0, {218, 165, 32 }}, // goldenrod
+ {"Hurricane Force Wind Warning" , "warning", "Hur F Wind", 9 , 0, 0, 0, {205, 92 , 92 }}, // westernred
+ {"Hurricane Warning" , "warning", "Hurricane", 9 , 0, 0, 0, {220, 20 , 60 }}, // crimson
+ {"Hurricane Wind Warning" , "warning", "Hur Wind", 9 , 0, 0, 0, {205, 92 , 92 }}, // westernred
+ {"Ice Storm Warning" , "warning", "Ice Storm", 13 , 0, 0, 0, {139, 0 , 139}}, // darkmagenta
+ {"Inland Hurricane Warning" , "warning", "I Hurr", 9 , 0, 0, 0, {220, 20 , 60 }}, // crimson
+ {"Inland Tropical Storm Warning" , "warning", "I Trop Stm", 14 , 0, 0, 0, {178, 34 , 34 }}, // firebrick
+ {"Lake Effect Snow Warning" , "warning", "Lake Snow", 30 , 0, 0, 0, {0 , 139, 139}}, // darkcyan
+ {"Lakeshore Flood Warning" , "warning", "L Flood", 25 , 0, 0, 0, {34 , 139, 34 }}, // forestgreen
+ {"Law Enforcement Warning" , "warning", "Law Enf.", 8 , 0, 0, 0, {192, 192, 192}}, // silver
+ {"Nuclear Power Plant Warning" , "warning", "Nuke Pwr", 7 , 0, 0, 0, {75 , 0 , 130}}, // indigo
+ {"Radiological Hazard Warning" , "warning", "Radiology", 7 , 0, 0, 0, {75 , 0 , 130}}, // indigo
+ {"Red Flag Warning" , "warning", "Red Flag", 47 , 0, 0, 0, {255, 20 , 147}}, // deeppink
+ {"Severe Thunderstorm Warning" , "warning", "Svr Storm", 4 , 0, 0, 1, {255, 165, 0 }}, // orange
+ {"Shelter In Place Warning" , "warning", "Shelter", 6 , 0, 0, 0, {250, 128, 114}}, // salmon
+ {"Sleet Warning" , "warning", "Sleet", 29 , 0, 0, 0, {135, 206, 235}}, // skyblue
+ {"Special Marine Warning" , "warning", "Sp Marine", 11 , 0, 0, 1, {255, 165, 0 }}, // orange
+ {"Storm Warning" , "warning", "Storm", 13 , 0, 0, 0, {148, 0 , 211}}, // darkviolet
+ {"Tornado Warning" , "warning", "Tornado", 2 , 0, 0, 1, {255, 0 , 0 }}, // red
+ {"Tropical Storm Warning" , "warning", "Trop Storm", 14 , 0, 0, 0, {178, 34 , 34 }}, // firebrick
+ {"Tropical Storm Wind Warning" , "warning", "Trop Wind", 9 , 0, 0, 0, {178, 34 , 34 }}, // firebrick
+ {"Tsunami Warning" , "warning", "Tsunami", 1 , 0, 0, 0, {253, 99 , 71 }}, // tomato
+ {"Typhoon Warning" , "warning", "Typhoon", 10 , 0, 0, 0, {220, 20 , 60 }}, // crimson
+ {"Volcano Warning" , "warning", "Volcano", 23 , 0, 0, 0, {47 , 79 , 79 }}, // darkslategray
+ {"Wind Chill Warning" , "warning", "Wind Chill", 43 , 0, 0, 0, {176, 196, 222}}, // lightsteelblue
+ {"Winter Storm Warning" , "warning", "Wntr Storm", 16 , 0, 0, 0, {255, 105, 180}}, // hotpink
+
+ /* Advisorys */
+ {"Air Stagnation Advisory" , "advisory", "Air Stag", 76 , 0, 0, 0, {128, 128, 128}}, // gray
+ {"Ashfall Advisory" , "advisory", "Ashfall", 73 , 0, 0, 0, {105, 105, 105}}, // dimgray
+ {"Blowing Dust Advisory" , "advisory", "Blow Dust", 71 , 0, 0, 0, {189, 183, 107}}, // darkkhaki
+ {"Blowing Snow Advisory" , "advisory", "Blow Snow", 50 , 0, 0, 0, {173, 216, 230}}, // lightblue
+ {"Brisk Wind Advisory" , "advisory", "Brsk Wind", 66 , 0, 0, 0, {216, 191, 216}}, // thistle
+ {"Coastal Flood Advisory" , "advisory", "C Flood", 58 , 0, 0, 0, {124, 252, 0 }}, // lawngreen
+ {"Dense Fog Advisory" , "advisory", "Fog", 68 , 0, 0, 0, {112, 128, 144}}, // slategray
+ {"Dense Smoke Advisory" , "advisory", "Smoke", 64 , 0, 0, 0, {240, 230, 140}}, // khaki
+ {"Flood Advisory" , "advisory", "Flood", 57 , 0, 0, 1, {0 , 255, 127}}, // springgreen
+ {"Freezing Drizzle Advisory" , "advisory", "Fz Drzl", 51 , 0, 0, 0, {218, 112, 214}}, // orchid
+ {"Freezing Fog Advisory" , "advisory", "Fz Fog", 74 , 0, 0, 0, {0 , 128, 128}}, // teal
+ {"Freezing Rain Advisory" , "advisory", "Fz Rain", 51 , 0, 0, 0, {218, 112, 214}}, // orchid
+ {"Freezing Spray Advisory" , "advisory", "Fz Spray", 75 , 0, 0, 0, {0 , 191, 255}}, // deepskyblue
+ {"Frost Advisory" , "advisory", "Frost", 72 , 0, 0, 0, {100, 149, 237}}, // cornflowerblue
+ {"Heat Advisory" , "advisory", "Heat", 56 , 0, 0, 0, {255, 127, 80 }}, // coral
+ {"High Surf Advisory" , "advisory", "Surf", 60 , 0, 0, 0, {186, 85 , 211}}, // mediumorchid
+ {"Hydrologic Advisory" , "advisory", "Hydro", 57 , 0, 0, 0, {0 , 255, 127}}, // springgreen
+ {"Lake Effect Snow Advisory" , "advisory", "Lake Snow", 54 , 0, 0, 0, {72 , 209, 204}}, // mediumturquoise
+ {"Lake Wind Advisory" , "advisory", "Lake Wind", 69 , 0, 0, 0, {210, 180, 140}}, // tan
+ {"Lakeshore Flood Advisory" , "advisory", "L Flood", 58 , 0, 0, 0, {124, 252, 0 }}, // lawngreen
+ {"Low Water Advisory" , "advisory", "Low Water", 77 , 0, 0, 0, {165, 42 , 42 }}, // brown
+ {"Sleet Advisory" , "advisory", "Sleet", 52 , 0, 0, 0, {123, 104, 238}}, // mediumslateblue
+ {"Small Craft Advisory" , "advisory", "S Craft", 65 , 0, 0, 0, {216, 191, 216}}, // thistle
+ {"Small Stream Flood Advisory" , "advisory", "Flood", 57 , 0, 0, 1, {0 , 255, 127}}, // springgreen
+ {"Snow and Blowing Snow Advisory" , "advisory", "S/B Snow", 50 , 0, 0, 0, {176, 224, 230}}, // powderblue
+ {"Tsunami Advisory" , "advisory", "Tsunami", 42 , 0, 0, 0, {210, 105, 30 }}, // chocolate
+ {"Wind Advisory" , "advisory", "Wind", 66 , 0, 0, 0, {210, 180, 140}}, // tan
+ {"Wind Chill Advisory" , "advisory", "Wind Chill", 55 , 0, 0, 0, {175, 238, 238}}, // paleturquoise
+ {"Winter Weather Advisory" , "advisory", "Winter Wx", 53 , 0, 0, 0, {123, 104, 238}}, // mediumslateblue
+ {"Lake Effect Snow and Blowing Snow Advisory"
+ , "advisory", "L/S Snow", 54 , 0, 0, 0, {72 , 209, 204}}, // mediumturquoise
+ {"Urban and Small Stream Flood Advisory"
+ , "advisory", "Flood", 57 , 0, 0, 1, {0 , 255, 127}}, // springgreen
+ {"Arroyo and Small Stream Advisory"
+ , "advisory", "Flood", 57 , 0, 0, 1, {0 , 255, 127}}, // springgreen
+
+
+ /* Watches */
+ {"Tornado Watch" , "watch", "Tornado", 33 , 0, 0, 0, {255, 255, 0 }}, // yellow
+ {"Severe Thunderstorm Watch" , "watch", "Svr Storm", 35 , 0, 0, 0, {219, 112, 147}}, // palevioletred
+ {"Avalanche Watch" , "watch", "Avalanche", 79 , 0, 0, 0, {244, 164, 96 }}, // sandybrown
+ {"Blizzard Watch" , "watch", "Blizzard", 80 , 0, 0, 0, {173, 255, 47 }}, // greenyellow
+ {"Coastal Flood Watch" , "watch", "C Flood", 84 , 0, 0, 0, {102, 205, 170}}, // mediumaquamarine
+ {"Excessive Heat Watch" , "watch", "Heat", 87 , 0, 0, 0, {128, 0 , 0 }}, // maroon
+ {"Extreme Cold Watch" , "watch", "Ext Cold", 88 , 0, 0, 0, {0 , 0 , 255}}, // blue
+ {"Fire Weather Watch" , "watch", "Fire Wx", 93 , 0, 0, 0, {255, 222, 173}}, // navajowhite
+ {"Flash Flood Watch" , "watch", "F Flood", 37 , 0, 0, 0, {50 , 205, 50 }}, // limegreen
+ {"Flood Watch" , "watch", "Flood", 37 , 0, 0, 0, {46 , 139, 87 }}, // seagreen
+ {"Freeze Watch" , "watch", "Freeze", 91 , 0, 0, 0, {65 , 105, 225}}, // royalblue
+ {"Gale Watch" , "watch", "Gale", 1000, 0, 0, 0, {255, 192, 203}}, // pink
+ {"Hard Freeze Watch" , "watch", "H Freeze", 91 , 0, 0, 0, {65 , 105, 225}}, // royalblue
+ {"Hazardous Seas Watch" , "watch", "Haz Seas", 1000, 0, 0, 0, {72 , 61 , 139}}, // darkslateblue
+ {"Heavy Freezing Spray Watch" , "watch", "Fz Spray", 1000, 0, 0, 0, {188, 143, 143}}, // rosybrown
+ {"High Wind Watch" , "watch", "Wind", 86 , 0, 0, 0, {184, 134, 11 }}, // darkgoldenrod
+ {"Hurricane Force Wind Watch" , "watch", "Hur Wind", 1000, 0, 0, 0, {153, 50 , 204}}, // darkorchid
+ {"Hurricane Watch" , "watch", "Hurricane", 48 , 0, 0, 0, {255, 0 , 255}}, // magenta
+ {"Hurricane Wind Watch" , "watch", "Hur F Wind", 1000, 0, 0, 0, {255, 160, 122}}, // lightsalmon
+ {"Lake Effect Snow Watch" , "watch", "Lake Snow", 1000, 0, 0, 0, {135, 206, 250}}, // lightskyblue
+ {"Lakeshore Flood Watch" , "watch", "L Flood", 84 , 0, 0, 0, {102, 205, 170}}, // mediumaquamarine
+ {"Storm Watch" , "watch", "Storm", 81 , 0, 0, 0, {238, 130, 238}}, // violet
+ {"Tropical Storm Watch" , "watch", "Trop Storm", 81 , 0, 0, 0, {240, 128, 128}}, // lightcoral
+ {"Tropical Storm Wind Watch" , "watch", "Trop Wind", 1000, 0, 0, 0, {240, 128, 128}}, // lightcoral
+ {"Typhoon Watch" , "watch", "Typhoon", 48 , 0, 0, 0, {255, 0 , 255}}, // magenta
+ {"Wind Chill Watch" , "watch", "Wind Chill", 89 , 0, 0, 0, {95 , 158, 160}}, // cadetblue
+ {"Winter Storm Watch" , "watch", "Wntr Storm", 83 , 0, 0, 0, {70 , 130, 180}}, // steelblue
+
+ /* Statements */
+ {"Coastal Flood Statement" , "other", "C Flood", 97 , 0, 0, 0, {107, 142, 35 }}, // olivedrab
+ {"Flash Flood Statement" , "other", "F Flood", 39 , 0, 0, 1, {154, 205, 50 }}, // yellowgreen
+ {"Flood Statement" , "other", "Flood", 39 , 0, 0, 1, {0 , 255, 0 }}, // lime
+ {"Hurricane Statement" , "other", "Hurricane", 49 , 0, 0, 0, {147, 112, 219}}, // mediumpurple
+ {"Lakeshore Flood Statement" , "other", "L Flood", 98 , 0, 0, 0, {107, 142, 35 }}, // olivedrab
+ {"Marine Weather Statement" , "other", "Marine Wx", 11 , 0, 0, 1, {255, 239, 213}}, // peachpuff
+ {"Rip Current Statement" , "other", "Rip Curnt", 1000, 0, 0, 0, {64 , 224, 208}}, // turquoise
+ {"Severe Weather Statement" , "other", "Severe Wx", 38 , 0, 0, 1, {0 , 255, 255}}, // aqua
+ {"Special Weather Statement" , "other", "Special Wx", 99 , 0, 0, 0, {255, 228, 181}}, // moccasin
+ {"Typhoon Statement" , "other", "Typhoon", 49 , 0, 0, 0, {147, 112, 219}}, // mediumpurple
+
+ /* Misc */
+ {"Air Quality Alert" , "other", "Air Qual", 1000, 0, 0, 0, {128, 128, 128}}, // gray
+
+ {"Extreme Fire Danger" , "other", "Fire Dngr", 94 , 0, 0, 0, {233, 150, 122}}, // darksalmon
+
+ {"Child Abduction Emergency" , "other", "Child Abd", 95 , 0, 0, 0, {255, 215, 0 }}, // gold
+ {"Local Area Emergency" , "other", "Local", 78 , 0, 0, 0, {192, 192, 192}}, // silver
+
+ {"Short Term Forecast" , "other", "S Term Fc", 102 , 0, 0, 0, {152, 251, 152}}, // palegreen
+
+ {"Evacuation Immediate" , "other", "Evacuate", 6 , 0, 0, 0, {127, 255, 0 }}, // chartreuse
+
+ {"Administrative Message" , "other", "Admin", 103 , 0, 0, 0, {255, 255, 255}}, // white
+ {"Civil Emergency Message" , "other", "Civil Em", 7 , 0, 0, 0, {255, 182, 193}}, // lightpink
+
+ {"911 Telephone Outage" , "other", "911 Out", 96 , 0, 0, 0, {192, 192, 192}}, // silver
+
+ {"Hazardous Weather Outlook" , "other", "Haz Wx Ol", 101 , 0, 0, 0, {238, 232, 170}}, // palegoldenrod
+ {"Hydrologic Outlook" , "other", "Hydro Ol", 1000, 0, 0, 0, {144, 238, 144}}, // lightgreen
+
+ {"Test" , "other", "Test", 104 , 0, 0, 0, {240, 255, 255}}, // azure
+
+ /* End of list */
+ {NULL, NULL, NULL, 0, 0, 0, 0, {0, 0, 0}},
+};
+
+AlertInfo *alert_info_find(gchar *title)
+{
+ for (int i = 0; alert_info[i].title; i++)
+ if (g_str_has_prefix(title, alert_info[i].title))
+ return &alert_info[i];
+ return NULL;
+}
--- /dev/null
+/*
+ * Copyright (C) 2010-2011 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/>.
+ */
+
+#ifndef __ALERT_INFO_H__
+#define __ALERT_INFO_H__
+
+#include <glib.h>
+#include <rsl.h>
+
+typedef struct {
+ char *title; // Title, "Tornado Warning"
+ char *category; // Category, warning/watch/etc
+ char *abbr; // Abbreviation, for button label
+ int prior; // Priority, for county color picking
+ gboolean hidden; // Show this alert type?
+ gboolean current; // Are the currently alerts for this type?
+ gboolean ispoly; // May contain a polygon warning area
+ guint8 color[3]; // Color to use for drawing alert
+} AlertInfo;
+
+extern AlertInfo alert_info[];
+
+AlertInfo *alert_info_find(gchar *title);
+
+#endif
--- /dev/null
+/*
+ * Copyright (C) 2010-2011 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 <sys/time.h>
+#include <grits.h>
+#include <GL/gl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "alert.h"
+#include "alert-info.h"
+
+#define MSG_INDEX "http://alerts.weather.gov/cap/us.php?x=0"
+
+/* Format for single cap alert:
+ * "http://alerts.weather.gov/cap/wwacapget.php?x="
+ * "AK20111012000500CoastalFloodWarning20111012151500"
+ * "AKAFGCFWAFG.6258a84eb1e8c34cd888057248224d10" */
+
+/************
+ * AlertMsg *
+ ************/
+/* AlertMsg 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;
+} AlertVtec;
+
+typedef struct {
+ /* Basic info (from alert index) */
+ 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
+ AlertVtec *vtec; // /X.CON.PAFG.WW.Y.0064.000000T0000Z-101220T0300Z/
+ } cap;
+
+ /* Advanced info (from CAP file) */
+ char *description;
+ char *instruction;
+ char *polygon;
+
+ /* Internal state */
+ AlertInfo *info; // Link to info structure for this alert
+ GritsPoly *county_based; // Polygon for county-based warning
+ GritsPoly *storm_based; // Polygon for storm-based warning
+} AlertMsg;
+
+/* AlertMsg parsing */
+typedef struct {
+ time_t updated;
+ AlertMsg *msg;
+ GList *msgs;
+ gchar *text;
+ gchar *value_name;
+} ParseData;
+time_t msg_parse_time(gchar *iso8601)
+{
+ GTimeVal tv = {};
+ g_time_val_from_iso8601(iso8601, &tv);
+ return tv.tv_sec;
+}
+AlertVtec *msg_parse_vtec(char *buf)
+{
+ AlertVtec *vtec = g_new0(AlertVtec, 1);
+ strncpy((char*)vtec, buf, sizeof(AlertVtec));
+ vtec->sep0 = vtec->sep1 = vtec->sep2 = '\0';
+ vtec->sep3 = vtec->sep4 = vtec->sep5 = '\0';
+ vtec->sep6 = vtec->sep7 = vtec->sep8 = '\0';
+ return vtec;
+}
+void msg_parse_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 = g_strndup(text, len);
+}
+void msg_parse_index_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->msg = g_new0(AlertMsg, 1);
+}
+void msg_parse_index_end(GMarkupParseContext *context, const gchar *name,
+ gpointer user_data, GError **error)
+{
+ //g_debug("end %s", name);
+ ParseData *data = user_data;
+ AlertMsg *msg = data->msg;
+ char *text = data->text;
+
+ if (g_str_equal(name, "updated") && text && !data->updated)
+ data->updated = msg_parse_time(text);
+
+ if (g_str_equal(name, "entry"))
+ data->msgs = g_list_prepend(data->msgs, data->msg);
+
+ if (!text || !msg) return;
+ else if (g_str_equal(name, "title")) msg->title = g_strdup(text);
+ else if (g_str_equal(name, "id")) msg->link = g_strdup(text); // hack
+ else if (g_str_equal(name, "summary")) msg->summary = g_strdup(text);
+ else if (g_str_equal(name, "cap:effective")) msg->cap.effective = msg_parse_time(text);
+ else if (g_str_equal(name, "cap:expires")) msg->cap.expires = msg_parse_time(text);
+ else if (g_str_equal(name, "cap:status")) msg->cap.status = g_strdup(text);
+ else if (g_str_equal(name, "cap:urgency")) msg->cap.urgency = g_strdup(text);
+ else if (g_str_equal(name, "cap:severity")) msg->cap.severity = g_strdup(text);
+ else if (g_str_equal(name, "cap:certainty")) msg->cap.certainty = g_strdup(text);
+ else if (g_str_equal(name, "cap:areaDesc")) msg->cap.area_desc = g_strdup(text);
+
+ if (g_str_equal(name, "title"))
+ msg->info = alert_info_find(msg->title);
+
+ 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")) msg->cap.fips6 = g_strdup(text);
+ if (g_str_equal(data->value_name, "VTEC")) msg->cap.vtec = msg_parse_vtec(text);
+ }
+}
+void msg_parse_cap_end(GMarkupParseContext *context, const gchar *name,
+ gpointer user_data, GError **error)
+{
+ ParseData *data = user_data;
+ AlertMsg *msg = data->msg;
+ char *text = data->text;
+ if (!text || !msg) return;
+ if (g_str_equal(name, "description")) msg->description = g_strdup(text);
+ else if (g_str_equal(name, "instruction")) msg->instruction = g_strdup(text);
+ else if (g_str_equal(name, "polygon")) msg->polygon = g_strdup(text);
+}
+
+/* AlertMsg methods */
+GList *msg_parse_index(gchar *text, gsize len, time_t *updated)
+{
+ g_debug("GritsPluginAlert: msg_parse");
+ GMarkupParser parser = {
+ .start_element = msg_parse_index_start,
+ .end_element = msg_parse_index_end,
+ .text = msg_parse_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);
+ *updated = data.updated;
+ return data.msgs;
+}
+
+void msg_parse_cap(AlertMsg *msg, gchar *text, gsize len)
+{
+ g_debug("GritsPluginAlert: msg_parse_cap");
+ GMarkupParser parser = {
+ .end_element = msg_parse_cap_end,
+ .text = msg_parse_text,
+ };
+ ParseData data = { .msg = msg };
+ 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);
+
+ /* Add spaces to description... */
+ static GRegex *regex = NULL;
+ if (regex == NULL)
+ regex = g_regex_new("\\.\\n", 0, G_REGEX_MATCH_NEWLINE_ANY, NULL);
+ if (msg->description && regex) {
+ char *old = msg->description;
+ msg->description = g_regex_replace_literal(
+ regex, old, -1, 0, ".\n\n", 0, NULL);
+ g_free(old);
+ }
+}
+
+void msg_free(AlertMsg *msg)
+{
+ g_free(msg->title);
+ g_free(msg->link);
+ g_free(msg->summary);
+ g_free(msg->cap.status);
+ g_free(msg->cap.urgency);
+ g_free(msg->cap.severity);
+ g_free(msg->cap.certainty);
+ g_free(msg->cap.area_desc);
+ g_free(msg->cap.fips6);
+ g_free(msg->cap.vtec);
+ g_free(msg->description);
+ g_free(msg->instruction);
+ g_free(msg->polygon);
+ g_free(msg);
+}
+
+void msg_print(GList *msgs)
+{
+ g_message("msg_print");
+ for (GList *cur = msgs; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ g_message("alert:");
+ g_message(" title = %s", msg->title );
+ g_message(" link = %s", msg->link );
+ g_message(" summary = %s", msg->summary );
+ g_message(" cat.effective = %lu", msg->cap.effective);
+ g_message(" cat.expires = %lu", msg->cap.expires );
+ g_message(" cat.status = %s", msg->cap.status );
+ g_message(" cat.urgency = %s", msg->cap.urgency );
+ g_message(" cat.severity = %s", msg->cap.severity );
+ g_message(" cat.certainty = %s", msg->cap.certainty);
+ g_message(" cat.area_desc = %s", msg->cap.area_desc);
+ g_message(" cat.fips6 = %s", msg->cap.fips6 );
+ g_message(" cat.vtec = %p", msg->cap.vtec );
+ }
+}
+
+gchar *msg_find_nearest(GritsHttp *http, time_t when, gboolean offline)
+{
+ GList *files = grits_http_available(http,
+ "^[0-9]*.xml$", "index", NULL, NULL);
+
+ time_t this_time = 0;
+ time_t nearest_time = offline ? 0 : time(NULL);
+ char *nearest_file = NULL;
+
+ for (GList *cur = files; cur; cur = cur->next) {
+ gchar *file = cur->data;
+ sscanf(file, "%ld", &this_time);
+ if (ABS(when - this_time) <
+ ABS(when - nearest_time)) {
+ nearest_file = file;
+ nearest_time = this_time;
+ }
+ }
+
+ if (nearest_file)
+ return g_strconcat("index/", nearest_file, NULL);
+ else if (!offline)
+ return g_strdup_printf("index/%ld.xml", time(NULL));
+ else
+ return NULL;
+}
+
+GList *msg_load_index(GritsHttp *http, time_t when, time_t *updated, gboolean offline)
+{
+ /* Fetch current alerts */
+ gchar *tmp = msg_find_nearest(http, when, offline);
+ if (!tmp)
+ return NULL;
+ gchar *file = grits_http_fetch(http, MSG_INDEX, tmp, GRITS_ONCE, NULL, NULL);
+ g_free(tmp);
+ if (!file)
+ return NULL;
+
+ /* Load file */
+ gchar *text; gsize len;
+ g_file_get_contents(file, &text, &len, NULL);
+ GList *msgs = msg_parse_index(text, len, updated);
+ //msg_print(msgs);
+ g_free(file);
+ g_free(text);
+
+ /* Delete unrecognized messages */
+ GList *dead = NULL;
+ for (GList *cur = msgs; cur; cur = cur->next)
+ if (!((AlertMsg*)cur->data)->info)
+ dead = g_list_prepend(dead, cur->data);
+ for (GList *cur = dead; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ g_warning("GritsPluginAlert: unknown msg type - %s", msg->title);
+ msgs = g_list_remove(msgs, msg);
+ msg_free(msg);
+ }
+ g_list_free(dead);
+
+ return msgs;
+}
+
+void msg_load_cap(GritsHttp *http, AlertMsg *msg)
+{
+ if (msg->description || msg->instruction || msg->polygon)
+ return;
+ g_debug("GritsPlguinAlert: update_cap");
+
+ /* Download */
+ gchar *id = strrchr(msg->link, '=');
+ if (!id) return; id++;
+ gchar *dir = g_strdelimit(g_strdup(msg->info->abbr), " ", '_');
+ gchar *tmp = g_strdup_printf("%s/%s.xml", dir, id);
+ gchar *file = grits_http_fetch(http, msg->link, tmp, GRITS_ONCE, NULL, NULL);
+ g_free(tmp);
+ g_free(dir);
+ if (!file)
+ return;
+
+ /* Parse */
+ gchar *text; gsize len;
+ g_file_get_contents(file, &text, &len, NULL);
+ msg_parse_cap(msg, text, len);
+ g_free(file);
+ g_free(text);
+}
+
+
+/********
+ * FIPS *
+ ********/
+int fips_compare(int a, int b)
+{
+ return (a < b) ? -1 :
+ (a == b) ? 0 : 1;
+}
+
+GritsPoly *fips_combine(GList *polys)
+{
+ /* Create points list */
+ GPtrArray *array = g_ptr_array_new();
+ for (GList *cur = polys; cur; cur = cur->next) {
+ GritsPoly *poly = cur->data;
+ gdouble (**points)[3] = poly->points;
+ for (int i = 0; points[i]; i++)
+ g_ptr_array_add(array, points[i]);
+ }
+ g_ptr_array_add(array, NULL);
+ gdouble (**points)[3] = (gpointer)g_ptr_array_free(array, FALSE);
+
+ /* Calculate center */
+ GritsBounds bounds = {-90, 90, -180, 180};
+ for (GList *cur = polys; cur; cur = cur->next) {
+ GritsObject *poly = cur->data;
+ gdouble lat = poly->center.lat;
+ gdouble lon = poly->center.lon;
+ if (lat > bounds.n) bounds.n = lat;
+ if (lat < bounds.s) bounds.s = lat;
+ if (lon > bounds.e) bounds.e = lon;
+ if (lon < bounds.w) bounds.w = lon;
+ }
+ GritsPoint center = {
+ .lat = (bounds.n + bounds.s)/2,
+ .lon = lon_avg(bounds.e, bounds.w),
+ };
+
+ /* Create polygon */
+ GritsPoly *poly = grits_poly_new(points);
+ GRITS_OBJECT(poly)->skip |= GRITS_SKIP_CENTER;
+ GRITS_OBJECT(poly)->skip |= GRITS_SKIP_STATE;
+ GRITS_OBJECT(poly)->center = center;
+ g_object_weak_ref(G_OBJECT(poly), (GWeakNotify)g_free, points);
+ return poly;
+}
+
+gboolean fips_group_state(gpointer key, gpointer value, gpointer data)
+{
+ GList *counties = value;
+ GList **states = data;
+ GritsPoly *poly = fips_combine(counties);
+ GRITS_OBJECT(poly)->lod = EARTH_R/10;
+ *states = g_list_prepend(*states, poly);
+ g_list_free(counties);
+ return FALSE;
+}
+
+void fips_parse(gchar *text, GTree **_counties, GList **_states)
+{
+ g_debug("GritsPluginAlert: fips_parse");
+ GTree *counties = g_tree_new((GCompareFunc)fips_compare);
+ GTree *states = g_tree_new_full((GCompareDataFunc)g_strcmp0,
+ NULL, g_free, NULL);
+ gchar **lines = g_strsplit(text, "\n", -1);
+ for (gint li = 0; lines[li]; li++) {
+ /* Split line */
+ gchar **sparts = g_strsplit(lines[li], "\t", 4);
+ int nparts = g_strv_length(sparts);
+ if (nparts < 4) {
+ g_strfreev(sparts);
+ continue;
+ }
+
+ /* Create GritsPoly */
+ GritsPoly *poly = grits_poly_parse(sparts[3], "\t", " ", ",");
+
+ /* Insert polys into the tree */
+ gint id = g_ascii_strtoll(sparts[0], NULL, 10);
+ g_tree_insert(counties, (gpointer)id, poly);
+
+ /* Insert into states list */
+ GList *list = g_tree_lookup(states, sparts[2]);
+ list = g_list_prepend(list, poly);
+ g_tree_replace(states, g_strdup(sparts[2]), list);
+
+ g_strfreev(sparts);
+ }
+ g_strfreev(lines);
+
+ /* Group state counties */
+ *_counties = counties;
+ *_states = NULL;
+ g_tree_foreach(states, (GTraverseFunc)fips_group_state, _states);
+ g_tree_destroy(states);
+}
+
+/********************
+ * GritsPluginAlert *
+ ********************/
+static GtkWidget *_make_msg_details(AlertMsg *msg)
+{
+
+ GtkWidget *title = gtk_label_new("");
+ gchar *title_str = g_markup_printf_escaped(
+ "<big><b>%s</b></big>", msg->title);
+ gtk_label_set_use_markup(GTK_LABEL(title), TRUE);
+ gtk_label_set_markup(GTK_LABEL(title), title_str);
+ gtk_label_set_line_wrap(GTK_LABEL(title), TRUE);
+ gtk_misc_set_alignment(GTK_MISC(title), 0, 0);
+ gtk_widget_set_size_request(GTK_WIDGET(title), 500, -1);
+ g_free(title_str);
+
+ GtkWidget *alert = gtk_scrolled_window_new(NULL, NULL);
+ GtkWidget *alert_view = gtk_text_view_new();
+ GtkTextBuffer *alert_buf = gtk_text_buffer_new(NULL);
+ gchar *alert_str = g_markup_printf_escaped(
+ "%s\n\n%s", msg->description, msg->instruction);
+ PangoFontDescription *alert_font = pango_font_description_from_string(
+ "monospace");
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(alert),
+ GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(alert),
+ GTK_SHADOW_IN);
+ gtk_text_buffer_set_text(alert_buf, alert_str, -1);
+ gtk_text_view_set_buffer(GTK_TEXT_VIEW(alert_view), alert_buf);
+ gtk_widget_modify_font(GTK_WIDGET(alert_view), alert_font);
+ gtk_container_add(GTK_CONTAINER(alert), alert_view);
+ g_free(alert_str);
+
+ GtkWidget *align = gtk_alignment_new(0, 0, 1, 1);
+ GtkWidget *box = gtk_vbox_new(FALSE, 10);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(align), 10, 10, 10, 10);
+ gtk_container_add(GTK_CONTAINER(align), box);
+ gtk_box_pack_start(GTK_BOX(box), title, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(box), alert, TRUE, TRUE, 0);
+
+ return align;
+}
+
+static GtkWidget *_find_details(GtkNotebook *notebook, AlertMsg *msg)
+{
+ int pages = gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook));
+ for (int i = 0; i < pages; i++) {
+ GtkWidget *page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), i);
+ if (msg == g_object_get_data(G_OBJECT(page), "msg"))
+ return page;
+ }
+ return NULL;
+}
+
+static void _show_details(GritsPoly *county, GritsPluginAlert *alert)
+{
+ /* Add details for this messages */
+ AlertMsg *msg = g_object_get_data(G_OBJECT(county), "msg");
+ msg_load_cap(alert->http, msg);
+
+ GtkWidget *dialog = alert->details;
+ GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ GList *list = gtk_container_get_children(GTK_CONTAINER(content));
+ GtkWidget *notebook = list->data;
+ GtkWidget *details = _find_details(GTK_NOTEBOOK(notebook), msg);
+ g_list_free(list);
+
+ if (details == NULL) {
+ details = _make_msg_details(msg);
+ GtkWidget *label = gtk_label_new(msg->info->abbr);
+ g_object_set_data(G_OBJECT(details), "msg", msg);
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), details, label);
+ }
+
+ gtk_widget_show_all(dialog);
+ gint num = gtk_notebook_page_num(GTK_NOTEBOOK(notebook), details);
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), num);
+}
+
+/* Update counties */
+static void _alert_leave(GritsPoly *county, GritsPluginAlert *alert)
+{
+ g_debug("_alert_leave");
+ if (county->width == 3) {
+ county->color[3] = 0;
+ } else {
+ county->border[3] = 0.25;
+ county->width = 1;
+ }
+ grits_object_queue_draw(GRITS_OBJECT(county));
+}
+
+static void _alert_enter(GritsPoly *county, GritsPluginAlert *alert)
+{
+ g_debug("_alert_enter");
+ if (county->width == 3) {
+ county->color[3] = 0.25;
+ } else {
+ county->border[3] = 1.0;
+ county->width = 2;
+ }
+ grits_object_queue_draw(GRITS_OBJECT(county));
+}
+
+/* Update polygons */
+static void _load_common(GritsPluginAlert *alert, AlertMsg *msg, GritsPoly *poly,
+ float color, float border, int width)
+{
+ g_object_set_data(G_OBJECT(poly), "msg", msg);
+ poly->color[0] = poly->border[0] = (float)msg->info->color[0] / 256;
+ poly->color[1] = poly->border[1] = (float)msg->info->color[1] / 256;
+ poly->color[2] = poly->border[2] = (float)msg->info->color[2] / 256;
+ poly->color[3] = color;
+ poly->border[3] = border;
+ poly->width = width;
+ GRITS_OBJECT(poly)->lod = 0;
+ GRITS_OBJECT(poly)->hidden = msg->info->hidden;
+ g_signal_connect(poly, "enter", G_CALLBACK(_alert_enter), alert);
+ g_signal_connect(poly, "leave", G_CALLBACK(_alert_leave), alert);
+ g_signal_connect(poly, "clicked", G_CALLBACK(_show_details), alert);
+}
+
+static GritsPoly *_load_storm_based(GritsPluginAlert *alert, AlertMsg *msg)
+{
+ if (!msg->info->ispoly)
+ return NULL;
+
+ msg_load_cap(alert->http, msg);
+
+ GritsPoly *poly = grits_poly_parse(msg->polygon, "\t", " ", ",");
+ _load_common(alert, msg, poly, 0, 1, 3);
+ grits_viewer_add(alert->viewer, GRITS_OBJECT(poly), GRITS_LEVEL_WORLD+2, TRUE);
+
+ return poly;
+}
+
+static GritsPoly *_load_county_based(GritsPluginAlert *alert, AlertMsg *msg)
+{
+ /* Locate counties in the path of the storm */
+ gchar **fipses = g_strsplit(msg->cap.fips6, " ", -1);
+ GList *counties = NULL;
+ 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;
+ counties = g_list_prepend(counties, county);
+ }
+ g_strfreev(fipses);
+
+ /* No county based warning.. */
+ if (!counties)
+ return NULL;
+
+ /* Create new county based warning */
+ GritsPoly *poly = fips_combine(counties);
+ _load_common(alert, msg, poly, 0.25, 0.25, 0);
+ grits_viewer_add(alert->viewer, GRITS_OBJECT(poly), GRITS_LEVEL_WORLD+1, FALSE);
+
+ g_list_free(counties);
+ return poly;
+}
+
+/* Update buttons */
+static void _button_click(GtkToggleButton *button, gpointer _alert)
+{
+ g_debug("GritsPluginAlert: _button_click");
+ GritsPluginAlert *alert = _alert;
+ AlertInfo *info = g_object_get_data(G_OBJECT(button), "info");
+ info->hidden = !gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
+
+ /* Show/hide each polygons */
+ for (GList *cur = alert->msgs; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ if (msg->info != info)
+ continue;
+ if (msg->county_based) GRITS_OBJECT(msg->county_based)->hidden = info->hidden;
+ if (msg->storm_based) GRITS_OBJECT(msg->storm_based)->hidden = info->hidden;
+ }
+ gtk_widget_queue_draw(GTK_WIDGET(alert->viewer));
+}
+
+static GtkWidget *_button_new(AlertInfo *info)
+{
+ g_debug("GritsPluginAlert: _button_new - %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[64];
+ g_snprintf(text, sizeof(text), "<b>%.10s</b>", info->abbr);
+
+ 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->hidden);
+ 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 gboolean _update_buttons(GritsPluginAlert *alert)
+{
+ g_debug("GritsPluginAlert: _update_buttons");
+ GtkWidget *alerts = g_object_get_data(G_OBJECT(alert->config), "alerts");
+ GtkWidget *updated = g_object_get_data(G_OBJECT(alert->config), "updated");
+
+ /* Determine which buttons to show */
+ for (int i = 0; alert_info[i].title; i++)
+ alert_info[i].current = FALSE;
+ for (GList *cur = alert->msgs; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ msg->info->current = TRUE;
+ }
+
+ /* Delete old buttons */
+ GList *frames = gtk_container_get_children(GTK_CONTAINER(alerts));
+ 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);
+ g_list_free(btns);
+ }
+ g_list_free(frames);
+
+ /* 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(alerts),
+ alert_info[i].category);
+ GList *kids = gtk_container_get_children(GTK_CONTAINER(table));
+ gint nkids = g_list_length(kids);
+ guint rows, cols;
+ gtk_table_get_size(GTK_TABLE(table), &rows, &cols);
+ gint x = nkids % cols;
+ gint y = nkids / cols;
+ g_list_free(kids);
+
+ GtkWidget *button = _button_new(&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(_button_click), alert);
+ }
+
+ /* Set time widget */
+ struct tm tm;
+ gmtime_r(&alert->updated, &tm);
+ gchar *date_str = g_strdup_printf(" <b><i>%04d-%02d-%02d %02d:%02d</i></b>",
+ tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min);
+ gtk_label_set_markup(GTK_LABEL(updated), date_str);
+ g_free(date_str);
+
+ gtk_widget_show_all(GTK_WIDGET(alert->config));
+ return FALSE;
+}
+
+static gint _sort_warnings(gconstpointer _a, gconstpointer _b)
+{
+ const AlertMsg *a=_a, *b=_b;
+ return (a->info->prior < b->info->prior) ? -1 :
+ (a->info->prior == b->info->prior) ? 0 : 1;
+}
+
+static void _update_warnings(GritsPluginAlert *alert, GList *old)
+{
+ g_debug("GritsPluginAlert: _update_warnings");
+
+ /* Sort by priority:
+ * reversed here since they're added
+ * to the viewer in reverse order */
+ alert->msgs = g_list_sort(alert->msgs, _sort_warnings);
+
+ /* Remove old messages */
+ for (GList *cur = old; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ if (msg->county_based) grits_viewer_remove(alert->viewer,
+ GRITS_OBJECT(msg->county_based));
+ if (msg->storm_based) grits_viewer_remove(alert->viewer,
+ GRITS_OBJECT(msg->storm_based));
+ }
+
+ /* Add new messages */
+ for (GList *cur = alert->msgs; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ msg->county_based = _load_county_based(alert, msg);
+ msg->storm_based = _load_storm_based(alert, msg);
+ }
+
+ g_debug("GritsPluginAlert: _load_warnings - end");
+}
+
+/* Callbacks */
+static void _update(gpointer _, gpointer _alert)
+{
+ GritsPluginAlert *alert = _alert;
+ GList *old = alert->msgs;
+ g_debug("GritsPluginAlert: _update");
+
+ time_t when = grits_viewer_get_time(alert->viewer);
+ gboolean offline = grits_viewer_get_offline(alert->viewer);
+ if (!(alert->msgs = msg_load_index(alert->http, when, &alert->updated, offline)))
+ return;
+
+ g_idle_add((GSourceFunc)_update_buttons, alert);
+ _update_warnings(alert, old);
+
+ g_list_foreach(old, (GFunc)msg_free, NULL);
+ g_list_free(old);
+
+ gtk_widget_queue_draw(GTK_WIDGET(alert->viewer));
+ g_debug("GritsPluginAlert: _update - end");
+}
+
+static void _on_update(GritsPluginAlert *alert)
+{
+ g_thread_pool_push(alert->threads, NULL+1, NULL);
+}
+
+/* Init helpers */
+static GtkWidget *_make_config(void)
+{
+ GtkWidget *config = gtk_vbox_new(FALSE, 0);
+
+ /* Setup tools area */
+ GtkWidget *tools = gtk_hbox_new(FALSE, 10);
+ GtkWidget *updated = gtk_label_new(" Loading...");
+ gtk_label_set_use_markup(GTK_LABEL(updated), TRUE);
+ gtk_box_pack_start(GTK_BOX(tools), updated, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(config), tools, FALSE, FALSE, 0);
+ g_object_set_data(G_OBJECT(config), "updated", updated);
+
+ /* Setup alerts */
+ gchar *labels[] = {"<b>Warnings</b>", "<b>Watches</b>",
+ "<b>Advisories</b>", "<b>Other</b>"};
+ gchar *keys[] = {"warning", "watch", "advisory", "other"};
+ gint cols[] = {2, 2, 3, 2};
+ GtkWidget *alerts = 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);
+ GtkWidget *label = gtk_frame_get_label_widget(GTK_FRAME(frame));
+ gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
+ gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
+ gtk_container_add(GTK_CONTAINER(frame), table);
+ gtk_box_pack_start(GTK_BOX(alerts), frame, TRUE, TRUE, 0);
+ g_object_set_data(G_OBJECT(alerts), keys[i], table);
+ }
+ gtk_box_pack_start(GTK_BOX(config), alerts, TRUE, TRUE, 0);
+ g_object_set_data(G_OBJECT(config), "alerts", alerts);
+
+ return config;
+}
+
+static gboolean _clear_details(GtkWidget *dialog)
+{
+ GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ GList *list = gtk_container_get_children(GTK_CONTAINER(content));
+ GtkWidget *notebook = list->data;
+ g_list_free(list);
+ gtk_widget_hide(dialog);
+ while (gtk_notebook_get_n_pages(GTK_NOTEBOOK(notebook)))
+ gtk_notebook_remove_page(GTK_NOTEBOOK(notebook), 0);
+ return TRUE;
+}
+
+static gboolean _set_details_uri(GtkWidget *notebook, GtkNotebookPage *_,
+ guint num, GtkWidget *button)
+{
+ g_debug("_set_details_uri");
+ GtkWidget *page = gtk_notebook_get_nth_page(GTK_NOTEBOOK(notebook), num);
+ AlertMsg *msg = g_object_get_data(G_OBJECT(page), "msg");
+ gtk_link_button_set_uri(GTK_LINK_BUTTON(button), msg->link);
+ return FALSE;
+}
+
+static GtkWidget *_make_details(GritsViewer *viewer)
+{
+ GtkWidget *dialog = gtk_dialog_new();
+ GtkWidget *action = gtk_dialog_get_action_area (GTK_DIALOG(dialog));
+ GtkWidget *content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
+ GtkWidget *notebook = gtk_notebook_new();
+ GtkWidget *win = gtk_widget_get_toplevel(GTK_WIDGET(viewer));
+ GtkWidget *link = gtk_link_button_new_with_label("", "Full Text");
+
+ gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(win));
+ gtk_window_set_title(GTK_WINDOW(dialog), "Alert Details - AWeather");
+ gtk_window_set_default_size(GTK_WINDOW(dialog), 625, 500);
+ gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
+ gtk_container_add(GTK_CONTAINER(content), notebook);
+ gtk_box_pack_end(GTK_BOX(action), link, 0, 0, 0);
+ gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE);
+
+ g_signal_connect(dialog, "response", G_CALLBACK(_clear_details), NULL);
+ g_signal_connect(dialog, "delete-event", G_CALLBACK(_clear_details), NULL);
+ g_signal_connect(notebook, "switch-page", G_CALLBACK(_set_details_uri), link);
+
+ return dialog;
+}
+
+/* Methods */
+GritsPluginAlert *grits_plugin_alert_new(GritsViewer *viewer, GritsPrefs *prefs)
+{
+ g_debug("GritsPluginAlert: new");
+ GritsPluginAlert *alert = g_object_new(GRITS_TYPE_PLUGIN_ALERT, NULL);
+ alert->details = _make_details(viewer);
+ alert->viewer = g_object_ref(viewer);
+ alert->prefs = g_object_ref(prefs);
+
+ alert->refresh_id = g_signal_connect_swapped(alert->viewer, "refresh",
+ G_CALLBACK(_on_update), alert);
+ alert->time_changed_id = g_signal_connect_swapped(alert->viewer, "time_changed",
+ G_CALLBACK(_on_update), alert);
+
+ for (GList *cur = alert->states; cur; cur = cur->next)
+ grits_viewer_add(viewer, cur->data, GRITS_LEVEL_WORLD+1, FALSE);
+
+ _on_update(alert);
+ 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: init");
+ /* Set defaults */
+ alert->threads = g_thread_pool_new(_update, alert, 1, TRUE, NULL);
+ alert->config = _make_config();
+ alert->http = grits_http_new(G_DIR_SEPARATOR_S
+ "alerts" G_DIR_SEPARATOR_S
+ "cap" G_DIR_SEPARATOR_S);
+
+ /* Load counties */
+ gchar *text; gsize len;
+ const gchar *file = PKGDATADIR G_DIR_SEPARATOR_S "fips.txt";
+ if (!g_file_get_contents(file, &text, &len, NULL))
+ g_error("GritsPluginAlert: init - error loading fips polygons");
+ fips_parse(text, &alert->counties, &alert->states);
+ g_free(text);
+}
+static void grits_plugin_alert_dispose(GObject *gobject)
+{
+ g_debug("GritsPluginAlert: dispose");
+ GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject);
+ /* Drop references */
+ if (alert->viewer) {
+ GritsViewer *viewer = alert->viewer;
+ alert->viewer = NULL;
+ g_signal_handler_disconnect(viewer, alert->refresh_id);
+ g_signal_handler_disconnect(viewer, alert->time_changed_id);
+ soup_session_abort(alert->http->soup);
+ g_thread_pool_free(alert->threads, TRUE, TRUE);
+ gtk_widget_destroy(alert->details);
+ while (gtk_events_pending())
+ gtk_main_iteration();
+ for (GList *cur = alert->msgs; cur; cur = cur->next) {
+ AlertMsg *msg = cur->data;
+ if (msg->county_based) grits_viewer_remove(viewer,
+ GRITS_OBJECT(msg->county_based));
+ if (msg->storm_based) grits_viewer_remove(viewer,
+ GRITS_OBJECT(msg->storm_based));
+ }
+ for (GList *cur = alert->states; cur; cur = cur->next)
+ grits_viewer_remove(viewer, cur->data);
+ g_object_unref(alert->prefs);
+ g_object_unref(viewer);
+ }
+ G_OBJECT_CLASS(grits_plugin_alert_parent_class)->dispose(gobject);
+}
+static gboolean _unref_county(gpointer key, gpointer val, gpointer data)
+{
+ g_object_unref(val);
+ return FALSE;
+}
+static void grits_plugin_alert_finalize(GObject *gobject)
+{
+ g_debug("GritsPluginAlert: finalize");
+ GritsPluginAlert *alert = GRITS_PLUGIN_ALERT(gobject);
+ g_list_foreach(alert->msgs, (GFunc)msg_free, NULL);
+ g_list_free(alert->msgs);
+ g_list_free(alert->states);
+ g_tree_foreach(alert->counties, (GTraverseFunc)_unref_county, NULL);
+ g_tree_destroy(alert->counties);
+ grits_http_free(alert->http);
+ 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;
+}
--- /dev/null
+/*
+ * Copyright (C) 2010-2011 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/>.
+ */
+
+#ifndef __ALERT_H__
+#define __ALERT_H__
+
+#include <glib-object.h>
+#include <grits.h>
+
+#define GRITS_TYPE_PLUGIN_ALERT (grits_plugin_alert_get_type ())
+#define GRITS_PLUGIN_ALERT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), GRITS_TYPE_PLUGIN_ALERT, GritsPluginAlert))
+#define GRITS_IS_PLUGIN_ALERT(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), GRITS_TYPE_PLUGIN_ALERT))
+#define GRITS_PLUGIN_ALERT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GRITS_TYPE_PLUGIN_ALERT, GritsPluginAlertClass))
+#define GRITS_IS_PLUGIN_ALERT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GRITS_TYPE_PLUGIN_ALERT))
+#define GRITS_PLUGIN_ALERT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GRITS_TYPE_PLUGIN_ALERT, GritsPluginAlertClass))
+
+typedef struct _GritsPluginAlert GritsPluginAlert;
+typedef struct _GritsPluginAlertClass GritsPluginAlertClass;
+
+struct _GritsPluginAlert {
+ GObject parent_instance;
+
+ /* instance members */
+ GritsViewer *viewer;
+ GritsPrefs *prefs;
+ GtkWidget *config;
+ GtkWidget *details;
+
+ GritsHttp *http;
+ guint refresh_id;
+ guint time_changed_id;
+ GThreadPool *threads;
+
+ GList *msgs;
+ time_t updated;
+ GTree *counties;
+ GList *states;
+};
+
+struct _GritsPluginAlertClass {
+ GObjectClass parent_class;
+};
+
+GType grits_plugin_alert_get_type();
+
+/* Methods */
+GritsPluginAlert *grits_plugin_alert_new(GritsViewer *viewer, GritsPrefs *prefs);
+
+#endif
+
/* Draw wsr88d */
Sweep *sweep = level2->sweep;
- glDisable(GL_ALPHA_TEST);
+ //glDisable(GL_ALPHA_TEST);
glDisable(GL_CULL_FACE);
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
vol->disp = GRITS_VOLUME_SURFACE;
GRITS_OBJECT(vol)->center = GRITS_OBJECT(level2)->center;
grits_viewer_add(GRITS_OBJECT(level2)->viewer,
- GRITS_OBJECT(vol), GRITS_LEVEL_WORLD, TRUE);
+ GRITS_OBJECT(vol), GRITS_LEVEL_WORLD+1, FALSE);
level2->volume = vol;
}
if (ISO_MIN < level && level < ISO_MAX) {
}
grits_object_hide(GRITS_OBJECT(site->level2), site->hidden);
grits_viewer_add(site->viewer, GRITS_OBJECT(site->level2),
- GRITS_LEVEL_WORLD, TRUE);
+ GRITS_LEVEL_WORLD+1, TRUE);
out:
g_idle_add(_site_update_end, site);