/* * Authors: David Trowbridge * * Copyright (C) 2005 Novell, Inc. (www.novell.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ #include #include #include #include #include #include #include #include #include #include #include #include GtkWidget *e_calendar_weather_location (EPlugin *epl, EConfigHookItemFactoryData *data); GtkWidget *e_calendar_weather_refresh (EPlugin *epl, EConfigHookItemFactoryData *data); GtkWidget *e_calendar_weather_units (EPlugin *epl, EConfigHookItemFactoryData *data); gboolean e_calendar_weather_check (EPlugin *epl, EConfigHookPageCheckData *data); void e_calendar_weather_migrate (EPlugin *epl, ECalEventTargetComponent *data); int e_plugin_lib_enable (EPluginLib *epl, int enable); static GtkTreeStore *store = NULL; #define WEATHER_BASE_URI "weather://" int e_plugin_lib_enable (EPluginLib *epl, int enable) { GList *l; gboolean found = FALSE; /* Add the categories icons if we don't have them. */ for (l = e_categories_get_list (); l; l = g_list_next (l)) { if (!strcmp (l->data, _("Weather: Cloudy"))) { found = TRUE; break; } } if (!found) { e_categories_add (_("Weather: Cloudy"), NULL, WEATHER_DATADIR "/category_weather_cloudy_16.png", FALSE); e_categories_add (_("Weather: Fog"), NULL, WEATHER_DATADIR "/category_weather_fog_16.png", FALSE); e_categories_add (_("Weather: Partly Cloudy"), NULL, WEATHER_DATADIR "/category_weather_partly_cloudy_16.png", FALSE); e_categories_add (_("Weather: Rain"), NULL, WEATHER_DATADIR "/category_weather_rain_16.png", FALSE); e_categories_add (_("Weather: Snow"), NULL, WEATHER_DATADIR "/category_weather_snow_16.png", FALSE); e_categories_add (_("Weather: Sunny"), NULL, WEATHER_DATADIR "/category_weather_sun_16.png", FALSE); e_categories_add (_("Weather: Thunderstorms"), NULL, WEATHER_DATADIR "/category_weather_tstorm_16.png", FALSE); } return 0; } void e_calendar_weather_migrate (EPlugin *epl, ECalEventTargetComponent *data) { /* Perform a migration step here. This allows us to keep the weather calendar completely * separate from evolution. If the plugin isn't built, the weather source group won't * show up in the user's evolution. If it is, this will create it if it doesn't exist */ CalendarComponent *component; ESourceList *source_list; ESourceGroup *group; GSList *groups; ESourceGroup *weather = NULL; component = data->component; source_list = calendar_component_peek_source_list (component); groups = e_source_list_peek_groups (source_list); if (groups) { /* groups are already there, we need to search */ GSList *g; for (g = groups; g; g = g_slist_next (g)) { group = E_SOURCE_GROUP (g->data); if (!weather && !strcmp (WEATHER_BASE_URI, e_source_group_peek_base_uri (group))) weather = g_object_ref (group); } } if (!weather) { group = e_source_group_new (_("Weather"), WEATHER_BASE_URI); e_source_list_add_group (source_list, group, -1); weather = group; } if (weather) g_object_unref (weather); e_source_list_sync (source_list, NULL); return 0; } static void parse_subtree (GtkTreeIter *parent, xmlNode *node) { GtkTreeIter iter; xmlNode *child; if (node->type == XML_ELEMENT_NODE) { gtk_tree_store_append (store, &iter, parent); if (strcmp (node->name, "location") == 0) { xmlAttr *attr; child = node->children; g_assert (child->type == XML_TEXT_NODE); gtk_tree_store_set (store, &iter, 0, child->content, -1); for (attr = node->properties; attr; attr = attr->next) { if (strcmp (attr->name, "code") == 0) gtk_tree_store_set (store, &iter, 1, attr->children->content, -1); else if (strcmp (attr->name, "url") == 0) gtk_tree_store_set (store, &iter, 2, attr->children->content, -1); else if (strcmp (attr->name, "type") == 0) gtk_tree_store_set (store, &iter, 3, attr->children->content, -1); } } else { xmlAttr *attr; for (child = node->children; child; child = child->next) parse_subtree (&iter, child); for (attr = node->properties; attr; attr = attr->next) if (strcmp (attr->name, "name") == 0) gtk_tree_store_set (store, &iter, 0, attr->children->content, -1); } } } static void load_locations () { xmlDoc *doc; xmlNode *root, *child; LIBXML_TEST_VERSION doc = xmlParseFile (WEATHER_EDS_DATADIR "/Locations.xml"); if (doc == NULL) { g_warning ("failed to read locations file"); return; } if (store == NULL) store = gtk_tree_store_new (4, G_TYPE_STRING, /* name */ G_TYPE_STRING, /* code */ G_TYPE_STRING, /* URL */ G_TYPE_STRING); /* type */ root = xmlDocGetRootElement (doc); for (child = root->children; child; child = child->next) parse_subtree (NULL, child); xmlFreeDoc (doc); } static void selection_changed (GtkTreeSelection *selection, GtkDialog *dialog) { GtkTreeModel *model; GtkTreeIter iter; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gchar *code = NULL; gtk_tree_model_get (model, &iter, 1, &code, -1); if (code != NULL) { gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, TRUE); } else { GtkTreeView *view = gtk_tree_selection_get_tree_view (selection); GtkTreePath *path; path = gtk_tree_model_get_path (model, &iter); gtk_tree_view_expand_row (view, path, FALSE); gtk_tree_path_free (path); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); } } else { gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); } } static struct { gchar **ids; GtkTreeIter *result; } find_data; static gboolean find_location_func (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *node, gpointer data) { gchar *type, *code, *name; gtk_tree_model_get (model, node, 0, &name, 1, &code, 3, &type, -1); if (name == NULL || code == NULL || type == NULL) return FALSE; if ((!strcmp (type, find_data.ids[0])) && (!strcmp (code, find_data.ids[1])) && (!strcmp (name, find_data.ids[2]))) { find_data.result = gtk_tree_iter_copy (node); return TRUE; } return FALSE; } static GtkTreeIter * find_location (gchar *relative_url) { /* type/code/name */ find_data.ids = g_strsplit (relative_url, "/", -1); find_data.result = NULL; gtk_tree_model_foreach (GTK_TREE_MODEL (store), (GtkTreeModelForeachFunc) find_location_func, NULL); g_strfreev (find_data.ids); return find_data.result; } static gboolean treeview_clicked (GtkTreeView *treeview, GdkEventButton *event, GtkDialog *dialog) { if (event->type == GDK_2BUTTON_PRESS) { GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); GtkTreeModel *model; GtkTreeIter iter; if (gtk_tree_selection_get_selected (selection, &model, &iter)) { gchar *code = NULL; gtk_tree_model_get (model, &iter, 1, &code, -1); if (code != NULL) { gtk_dialog_response (dialog, GTK_RESPONSE_OK); return TRUE; } } } return FALSE; } static GtkDialog * create_source_selector (ESource *source) { GtkWidget *dialog, *treeview, *scrolledwindow; GtkCellRenderer *text; GtkTreeSelection *selection; gchar *uri_text; EUri *uri; /* FIXME - should show an error here if it fails*/ if (store == NULL) return NULL; dialog = gtk_dialog_new_with_buttons ( _("Select a location"), NULL, GTK_DIALOG_MODAL, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); gtk_container_set_border_width (GTK_CONTAINER (dialog), 6); scrolledwindow = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_widget_show (scrolledwindow); treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store)); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); gtk_widget_show (treeview); gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolledwindow), treeview); gtk_widget_add_events (treeview, GDK_BUTTON_PRESS); g_signal_connect (G_OBJECT (treeview), "button-press-event", G_CALLBACK (treeview_clicked), dialog); selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); uri_text = e_source_get_uri (source); uri = e_uri_new (uri_text); if (uri->path && strlen (uri->path)) { GtkTreeIter *iter = find_location (uri_text + 10); GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); gtk_tree_view_expand_to_path (GTK_TREE_VIEW (treeview), path); gtk_tree_selection_select_path (selection, path); gtk_tree_path_free (path); } g_free (uri_text); e_uri_free (uri); g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (selection_changed), dialog); g_object_set_data (G_OBJECT (dialog), "treeview", treeview); text = gtk_cell_renderer_text_new (); gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), -1, "location", text, "text", 0, NULL); gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), scrolledwindow); gtk_container_set_border_width (GTK_CONTAINER (scrolledwindow), 6); gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6); gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), GTK_RESPONSE_OK, FALSE); gtk_window_set_default_size (GTK_WINDOW (dialog), 420, 340); return GTK_DIALOG (dialog); } static gchar * build_location_path (GtkTreeIter *iter) { GtkTreeIter parent; gchar *path, *temp1, *temp2; gtk_tree_model_get (GTK_TREE_MODEL (store), iter, 0, &temp1, -1); path = g_strdup (temp1); while (gtk_tree_model_iter_parent (GTK_TREE_MODEL (store), &parent, iter)) { gtk_tree_model_get (GTK_TREE_MODEL (store), &parent, 0, &temp1, -1); temp2 = g_strdup_printf ("%s : %s", temp1, path); g_free (path); path = temp2; iter = gtk_tree_iter_copy (&parent); } return path; } static void location_clicked (GtkButton *button, ESource *source) { GtkDialog *dialog = create_source_selector (source); gint response; if (dialog == NULL) return; response = gtk_dialog_run (dialog); if (response == GTK_RESPONSE_OK) { GtkTreeView *view = GTK_TREE_VIEW (g_object_get_data (G_OBJECT (dialog), "treeview")); GtkTreeSelection *selection = gtk_tree_view_get_selection (view); GtkTreeModel *model; GtkTreeIter iter; GtkWidget *label; gchar *type, *code, *name; gchar *path, *uri; gtk_tree_selection_get_selected (selection, &model, &iter); gtk_tree_model_get (model, &iter, 0, &name, 1, &code, 3, &type, -1); path = build_location_path (&iter); label = gtk_bin_get_child (GTK_BIN (button)); gtk_label_set_text (GTK_LABEL (label), path); uri = g_strdup_printf ("%s/%s/%s", type, code, name); /* FIXME - url_encode (&uri); */ e_source_set_relative_uri (source, uri); g_free (uri); } else { GtkWidget *label; const gchar *text; label = GTK_WIDGET (gtk_bin_get_child (GTK_BIN (button))); text = gtk_label_get_text (GTK_LABEL (label)); if (strcmp (text, _("None")) == 0) e_source_set_relative_uri (source, ""); } gtk_widget_destroy (GTK_WIDGET (dialog)); } GtkWidget * e_calendar_weather_location (EPlugin *epl, EConfigHookItemFactoryData *data) { static GtkWidget *label; GtkWidget *button, *parent, *text; int row; ECalConfigTargetSource *t = (ECalConfigTargetSource *) data->target; ESource *source = t->source; EUri *uri; char *uri_text; static GtkWidget *hidden; if (store == NULL) load_locations (); if (!hidden) hidden = gtk_label_new (""); if (data->old) gtk_widget_destroy (label); uri_text = e_source_get_uri (t->source); uri = e_uri_new (uri_text); if (strcmp (uri->protocol, "weather")) { e_uri_free (uri); return hidden; } parent = data->parent; row = ((GtkTable*)parent)->nrows; label = gtk_label_new_with_mnemonic (_("_Location:")); gtk_widget_show (label); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (parent), label, 0, 1, row, row+1, GTK_FILL, 0, 0, 0); button = gtk_button_new (); g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (location_clicked), source); gtk_widget_show (button); if (uri->path && strlen (uri->path)) { GtkTreeIter *iter = find_location (uri_text + 10); gchar *location = build_location_path (iter); text = gtk_label_new (location); g_free (location); } else text = gtk_label_new (_("None")); gtk_widget_show (text); #if (GTK_CHECK_VERSION(2, 6, 0)) gtk_label_set_ellipsize (GTK_LABEL (text), PANGO_ELLIPSIZE_START); #endif gtk_container_add (GTK_CONTAINER (button), text); e_uri_free (uri); g_free (uri_text); gtk_table_attach (GTK_TABLE (parent), button, 1, 2, row, row+1, GTK_EXPAND | GTK_FILL, 0, 0, 0); return button; } static void set_refresh_time (ESource *source, GtkWidget *spin, GtkWidget *option) { int time; int item_num = 0; const char *refresh_str = e_source_get_property (source, "refresh"); time = refresh_str ? atoi (refresh_str) : 30; if (time && !(time % 10080)) { /* weeks */ item_num = 3; time /= 10080; } else if (time && !(time % 1440)) { /* days */ item_num = 2; time /= 1440; } else if (time && !(time % 60)) { /* hours */ item_num = 1; time /= 60; } gtk_option_menu_set_history (GTK_OPTION_MENU (option), item_num); gtk_spin_button_set_value (GTK_SPIN_BUTTON (spin), time); } static char * get_refresh_minutes (GtkWidget *spin, GtkWidget *option) { int setting = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin)); switch (gtk_option_menu_get_history (GTK_OPTION_MENU (option))) { case 0: /* minutes */ break; case 1: /* hours */ setting *= 60; break; case 2: /* days */ setting *= 1440; break; case 3: /* weeks - is this *really* necessary? */ setting *= 10080; break; default: g_warning ("Time unit out of range"); break; } return g_strdup_printf ("%d", setting); } static void spin_changed (GtkSpinButton *spin, ECalConfigTargetSource *t) { char *refresh_str; GtkWidget *option; option = g_object_get_data (G_OBJECT (spin), "option"); refresh_str = get_refresh_minutes ((GtkWidget *) spin, option); e_source_set_property (t->source, "refresh", refresh_str); g_free (refresh_str); } static void option_changed (GtkOptionMenu *option, ECalConfigTargetSource *t) { char *refresh_str; GtkWidget *spin; spin = g_object_get_data (G_OBJECT (option), "spin"); refresh_str = get_refresh_minutes (spin, (GtkWidget *) option); e_source_set_property (t->source, "refresh", refresh_str); g_free (refresh_str); } GtkWidget * e_calendar_weather_refresh (EPlugin *epl, EConfigHookItemFactoryData *data) { static GtkWidget *label; GtkWidget *option, *spin, *menu, *hbox, *parent; GtkWidget *times[4]; int row, i; ECalConfigTargetSource *t = (ECalConfigTargetSource *) data->target; ESource *source = t->source; EUri *uri; char *uri_text; static GtkWidget *hidden = NULL; if (!hidden) hidden = gtk_label_new (""); if (data->old) gtk_widget_destroy (label); uri_text = e_source_get_uri (t->source); uri = e_uri_new (uri_text); g_free (uri_text); if (strcmp (uri->protocol, "weather")) { e_uri_free (uri); return hidden; } e_uri_free (uri); parent = data->parent; row = ((GtkTable*)parent)->nrows; label = gtk_label_new_with_mnemonic (_("_Refresh:")); gtk_widget_show (label); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (parent), label, 0, 1, row, row+1, GTK_FILL, 0, 0, 0); hbox = gtk_hbox_new (FALSE, 6); gtk_widget_show (hbox); spin = gtk_spin_button_new_with_range (0, 100, 1); gtk_label_set_mnemonic_widget (GTK_LABEL (label), spin); gtk_widget_show (spin); gtk_box_pack_start (GTK_BOX (hbox), spin, FALSE, TRUE, 0); option = gtk_option_menu_new (); gtk_widget_show (option); times[0] = gtk_menu_item_new_with_label (_("minutes")); times[1] = gtk_menu_item_new_with_label (_("hours")); times[2] = gtk_menu_item_new_with_label (_("days")); times[3] = gtk_menu_item_new_with_label (_("weeks")); menu = gtk_menu_new (); gtk_widget_show (menu); for (i = 0; i < 4; i++) { gtk_widget_show (times[i]); gtk_menu_shell_append (GTK_MENU_SHELL (menu), times[i]); } gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu); set_refresh_time (source, spin, option); gtk_box_pack_start (GTK_BOX (hbox), option, FALSE, TRUE, 0); g_object_set_data (G_OBJECT (option), "spin", spin); g_signal_connect (G_OBJECT (option), "changed", G_CALLBACK (option_changed), t); g_object_set_data (G_OBJECT (spin), "option", option); g_signal_connect (G_OBJECT (spin), "value-changed", G_CALLBACK (spin_changed), t); gtk_table_attach (GTK_TABLE (parent), hbox, 1, 2, row, row+1, GTK_EXPAND | GTK_FILL, 0, 0, 0); return hbox; } static void set_units (ESource *source, GtkWidget *option) { const char *format = e_source_get_property (source, "units"); if (format == NULL) { format = e_source_get_property (source, "temperature"); if (format == NULL) { e_source_set_property (source, "units", "metric"); gtk_option_menu_set_history (GTK_OPTION_MENU (option), 0); } else if (strcmp (format, "fahrenheit") == 0) { /* old format, convert */ e_source_set_property (source, "units", "imperial"); gtk_option_menu_set_history (GTK_OPTION_MENU (option), 1); } else { e_source_set_property (source, "units", "metric"); gtk_option_menu_set_history (GTK_OPTION_MENU (option), 0); } } else { if (strcmp (format, "metric") == 0) gtk_option_menu_set_history (GTK_OPTION_MENU (option), 0); else gtk_option_menu_set_history (GTK_OPTION_MENU (option), 1); } } static void units_changed (GtkOptionMenu *option, ECalConfigTargetSource *t) { int choice = gtk_option_menu_get_history (GTK_OPTION_MENU (option)); if (choice == 0) e_source_set_property (t->source, "units", "metric"); else e_source_set_property (t->source, "units", "imperial"); } GtkWidget * e_calendar_weather_units (EPlugin *epl, EConfigHookItemFactoryData *data) { static GtkWidget *label; GtkWidget *option, *menu, *parent; GtkWidget *formats[2]; int row, i; ECalConfigTargetSource *t = (ECalConfigTargetSource *) data->target; ESource *source = t->source; EUri *uri; char *uri_text; static GtkWidget *hidden = NULL; if (!hidden) hidden = gtk_label_new (""); if (data->old) gtk_widget_destroy (label); uri_text = e_source_get_uri (t->source); uri = e_uri_new (uri_text); g_free (uri_text); if (strcmp (uri->protocol, "weather")) { e_uri_free (uri); return hidden; } e_uri_free (uri); parent = data->parent; row = ((GtkTable*)parent)->nrows; label = gtk_label_new_with_mnemonic (_("_Units:")); gtk_widget_show (label); gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); gtk_table_attach (GTK_TABLE (parent), label, 0, 1, row, row+1, GTK_FILL, 0, 0, 0); option = gtk_option_menu_new (); gtk_widget_show (option); formats[0] = gtk_menu_item_new_with_label (_("Metric (Celsius, cm, etc)")); formats[1] = gtk_menu_item_new_with_label (_("Imperial (Fahrenheit, inches, etc)")); menu = gtk_menu_new (); gtk_widget_show (menu); for (i = 0; i < 2; i++) { gtk_widget_show (formats[i]); gtk_menu_shell_append (GTK_MENU_SHELL (menu), formats[i]); } gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu); set_units (source, option); gtk_label_set_mnemonic_widget (GTK_LABEL (label), option); g_signal_connect (G_OBJECT (option), "changed", G_CALLBACK (units_changed), t); gtk_table_attach (GTK_TABLE (parent), option, 1, 2, row, row+1, GTK_FILL, 0, 0, 0); return option; } gboolean e_calendar_weather_check (EPlugin *epl, EConfigHookPageCheckData *data) { /* FIXME - check pageid */ ECalConfigTargetSource *t = (ECalConfigTargetSource *) data->target; EUri *uri; gboolean ok = FALSE; ESourceGroup *group = e_source_peek_group (t->source); /* always return TRUE if this isn't a weather source */ if (strncmp (e_source_group_peek_base_uri (group), "weather", 7)) return TRUE; uri = e_uri_new (e_source_get_uri (t->source)); /* make sure that the protocol is weather:// and that the path isn't empty */ ok = (uri->path && strlen (uri->path)); e_uri_free (uri); return ok; }