void
e_cal_component_set_outofsync (ECalComponent *comp, gboolean outofsync)
{
    GSList *categ_list, *iter;
    gboolean updated = FALSE;

    g_return_if_fail (comp != NULL);
    g_return_if_fail (E_IS_CAL_COMPONENT (comp));

    e_cal_component_get_categories_list (comp, &categ_list);

    if (outofsync)
    {
        for (iter = categ_list; iter != NULL; iter = iter->next)
        {
            gchar *cat = (gchar *) iter->data;
            if (!g_strcmp0 (cat, "outofsync"))
                goto exit;
        }

        categ_list = g_slist_prepend (categ_list, g_strdup ("outofsync"));
        updated = TRUE;
    }
    else
    {
        GSList *next;

        for (iter = categ_list; iter != NULL; iter = next)
        {
            next = iter->next;
            gchar *cat = (gchar *) iter->data;
            if (!g_strcmp0 (cat, "outofsync"))
            {
                categ_list = g_slist_delete_link (categ_list, iter);
                g_free (cat);
                updated = TRUE;
            }
        }
    }

    if (updated)
        e_cal_component_set_categories_list (comp, categ_list);
exit:
    e_cal_component_free_categories_list (categ_list);
}
Beispiel #2
0
static void
do_save_calendar_rdf (FormatHandler *handler,
                      ESourceSelector *selector,
                      ECalClientSourceType type,
                      gchar *dest_uri)
{

	/*
	 * According to some documentation about CSV, newlines 'are' allowed
	 * in CSV-files. But you 'do' have to put the value between quotes.
	 * The helper 'string_needsquotes' will check for that
	 *
	 * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
	 * http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
	 */

	ESource *primary_source;
	EClient *source_client;
	GError *error = NULL;
	GSList *objects = NULL;
	gchar *temp = NULL;
	GOutputStream *stream;

	if (!dest_uri)
		return;

	/* open source client */
	primary_source = e_source_selector_ref_primary_selection (selector);
	source_client = e_cal_client_connect_sync (
		primary_source, type, NULL, &error);
	g_object_unref (primary_source);

	/* Sanity check. */
	g_return_if_fail (
		((source_client != NULL) && (error == NULL)) ||
		((source_client == NULL) && (error != NULL)));

	if (source_client == NULL) {
		display_error_message (
			gtk_widget_get_toplevel (GTK_WIDGET (selector)),
			error->message);
		g_error_free (error);
		return;
	}

	stream = open_for_writing (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))), dest_uri, &error);

	if (stream && e_cal_client_get_object_list_as_comps_sync (E_CAL_CLIENT (source_client), "#t", &objects, NULL, NULL)) {
		GSList *iter;

		xmlBufferPtr buffer = xmlBufferCreate ();
		xmlDocPtr doc = xmlNewDoc ((xmlChar *) "1.0");
		xmlNodePtr fnode;

		doc->children = xmlNewDocNode (doc, NULL, (const guchar *)"rdf:RDF", NULL);
		xmlSetProp (doc->children, (const guchar *)"xmlns:rdf", (const guchar *)"http://www.w3.org/1999/02/22-rdf-syntax-ns#");
		xmlSetProp (doc->children, (const guchar *)"xmlns", (const guchar *)"http://www.w3.org/2002/12/cal/ical#");

		fnode = xmlNewChild (doc->children, NULL, (const guchar *)"Vcalendar", NULL);

		/* Should Evolution publicise these? */
		xmlSetProp (fnode, (const guchar *)"xmlns:x-wr", (const guchar *)"http://www.w3.org/2002/12/cal/prod/Apple_Comp_628d9d8459c556fa#");
		xmlSetProp (fnode, (const guchar *)"xmlns:x-lic", (const guchar *)"http://www.w3.org/2002/12/cal/prod/Apple_Comp_628d9d8459c556fa#");

		/* Not sure if it's correct like this */
		xmlNewChild (fnode, NULL, (const guchar *)"prodid", (const guchar *)"-//" PACKAGE_STRING "//iCal 1.0//EN");

		/* Assuming GREGORIAN is the only supported calendar scale */
		xmlNewChild (fnode, NULL, (const guchar *)"calscale", (const guchar *)"GREGORIAN");

		temp = calendar_config_get_timezone ();
		xmlNewChild (fnode, NULL, (const guchar *)"x-wr:timezone", (guchar *) temp);
		g_free (temp);

		xmlNewChild (fnode, NULL, (const guchar *)"method", (const guchar *)"PUBLISH");

		xmlNewChild (fnode, NULL, (const guchar *)"x-wr:relcalid", (guchar *) e_source_get_uid (primary_source));

		xmlNewChild (fnode, NULL, (const guchar *)"x-wr:calname", (guchar *) e_source_get_display_name (primary_source));

		/* Version of this RDF-format */
		xmlNewChild (fnode, NULL, (const guchar *)"version", (const guchar *)"2.0");

		for (iter = objects; iter; iter = iter->next) {
			ECalComponent *comp = iter->data;
			const gchar *temp_constchar;
			gchar *tmp_str = NULL;
			GSList *temp_list;
			ECalComponentDateTime temp_dt;
			struct icaltimetype *temp_time;
			gint *temp_int;
			ECalComponentText temp_comptext;
			xmlNodePtr c_node = xmlNewChild (fnode, NULL, (const guchar *)"component", NULL);
			xmlNodePtr node = xmlNewChild (c_node, NULL, (const guchar *)"Vevent", NULL);

			/* Getting the stuff */
			e_cal_component_get_uid (comp, &temp_constchar);
			tmp_str = g_strdup_printf ("#%s", temp_constchar);
			xmlSetProp (node, (const guchar *)"about", (guchar *) tmp_str);
			g_free (tmp_str);
			add_string_to_rdf (node, "uid",temp_constchar);

			e_cal_component_get_summary (comp, &temp_comptext);
			add_string_to_rdf (node, "summary", temp_comptext.value);

			e_cal_component_get_description_list (comp, &temp_list);
			add_list_to_rdf (node, "description", temp_list, ECALCOMPONENTTEXT);
			if (temp_list)
				e_cal_component_free_text_list (temp_list);

			e_cal_component_get_categories_list (comp, &temp_list);
			add_list_to_rdf (node, "categories", temp_list, CONSTCHAR);
			if (temp_list)
				e_cal_component_free_categories_list (temp_list);

			e_cal_component_get_comment_list (comp, &temp_list);
			add_list_to_rdf (node, "comment", temp_list, ECALCOMPONENTTEXT);

			if (temp_list)
				e_cal_component_free_text_list (temp_list);

			e_cal_component_get_completed (comp, &temp_time);
			add_time_to_rdf (node, "completed", temp_time);
			if (temp_time)
				e_cal_component_free_icaltimetype (temp_time);

			e_cal_component_get_created (comp, &temp_time);
			add_time_to_rdf (node, "created", temp_time);
			if (temp_time)
				e_cal_component_free_icaltimetype (temp_time);

			e_cal_component_get_contact_list (comp, &temp_list);
			add_list_to_rdf (node, "contact", temp_list, ECALCOMPONENTTEXT);
			if (temp_list)
				e_cal_component_free_text_list (temp_list);

			e_cal_component_get_dtstart (comp, &temp_dt);
			add_time_to_rdf (node, "dtstart", temp_dt.value ? temp_dt.value : NULL);
			e_cal_component_free_datetime (&temp_dt);

			e_cal_component_get_dtend (comp, &temp_dt);
			add_time_to_rdf (node, "dtend", temp_dt.value ? temp_dt.value : NULL);
			e_cal_component_free_datetime (&temp_dt);

			e_cal_component_get_due (comp, &temp_dt);
			add_time_to_rdf (node, "due", temp_dt.value ? temp_dt.value : NULL);
			e_cal_component_free_datetime (&temp_dt);

			e_cal_component_get_percent (comp, &temp_int);
			add_nummeric_to_rdf (node, "percentComplete", temp_int);

			e_cal_component_get_priority (comp, &temp_int);
			add_nummeric_to_rdf (node, "priority", temp_int);

			e_cal_component_get_url (comp, &temp_constchar);
			add_string_to_rdf (node, "URL", temp_constchar);

			if (e_cal_component_has_attendees (comp)) {
				e_cal_component_get_attendee_list (comp, &temp_list);
				add_list_to_rdf (node, "attendee", temp_list, ECALCOMPONENTATTENDEE);
				if (temp_list)
					e_cal_component_free_attendee_list (temp_list);
			}

			e_cal_component_get_location (comp, &temp_constchar);
			add_string_to_rdf (node, "location", temp_constchar);

			e_cal_component_get_last_modified (comp, &temp_time);
			add_time_to_rdf (node, "lastModified",temp_time);

			/* Important note!
			 * The documentation is not requiring this!
			 *
			 * if (temp_time) e_cal_component_free_icaltimetype (temp_time);
			 *
			 * Please uncomment and fix documentation if untrue
			 * http://www.gnome.org/projects/evolution/developer-doc/libecal/ECalComponent.html
			 *	#e-cal-component-get-last-modified
			 */
		}

		/* I used a buffer rather than xmlDocDump: I want gio support */
		xmlNodeDump (buffer, doc, doc->children, 2, 1);

		g_output_stream_write_all (stream, xmlBufferContent (buffer), xmlBufferLength (buffer), NULL, NULL, &error);
		g_output_stream_close (stream, NULL, NULL);

		e_cal_client_free_ecalcomp_slist (objects);

		xmlBufferFree (buffer);
		xmlFreeDoc (doc);
	}

	if (stream)
		g_object_unref (stream);

	g_object_unref (source_client);

	if (error != NULL) {
		display_error_message (
			gtk_widget_get_toplevel (GTK_WIDGET (selector)),
			error->message);
		g_error_free (error);
	}
}
Beispiel #3
0
static void
do_save_calendar_csv (FormatHandler *handler,
                      ESourceSelector *selector,
                      ECalClientSourceType type,
                      gchar *dest_uri)
{

	/*
	 * According to some documentation about CSV, newlines 'are' allowed
	 * in CSV-files. But you 'do' have to put the value between quotes.
	 * The helper 'string_needsquotes' will check for that
	 *
	 * http://www.creativyst.com/Doc/Articles/CSV/CSV01.htm
	 * http://www.creativyst.com/cgi-bin/Prod/15/eg/csv2xml.pl
	 */

	ESource *primary_source;
	EClient *source_client;
	GError *error = NULL;
	GSList *objects = NULL;
	GOutputStream *stream;
	GString *line = NULL;
	CsvConfig *config = NULL;
	CsvPluginData *d = handler->data;
	const gchar *tmp = NULL;

	if (!dest_uri)
		return;

	/* open source client */
	primary_source = e_source_selector_ref_primary_selection (selector);
	source_client = e_cal_client_connect_sync (
		primary_source, type, NULL, &error);
	g_object_unref (primary_source);

	/* Sanity check. */
	g_return_if_fail (
		((source_client != NULL) && (error == NULL)) ||
		((source_client == NULL) && (error != NULL)));

	if (source_client == NULL) {
		display_error_message (
			gtk_widget_get_toplevel (GTK_WIDGET (selector)),
			error);
		g_error_free (error);
		return;
	}

	config = g_new (CsvConfig, 1);

	tmp = gtk_entry_get_text (GTK_ENTRY (d->delimiter_entry));
	config->delimiter = userstring_to_systemstring (tmp ? tmp:", ");
	tmp = gtk_entry_get_text (GTK_ENTRY (d->newline_entry));
	config->newline = userstring_to_systemstring (tmp ? tmp:"\\n");
	tmp = gtk_entry_get_text (GTK_ENTRY (d->quote_entry));
	config->quote = userstring_to_systemstring (tmp ? tmp:"\"");
	config->header = gtk_toggle_button_get_active (
		GTK_TOGGLE_BUTTON (d->header_check));

	stream = open_for_writing (
		GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (selector))),
		dest_uri, &error);

	if (stream && e_cal_client_get_object_list_as_comps_sync (E_CAL_CLIENT (source_client), "#t", &objects, NULL, NULL)) {
		GSList *iter;

		if (config->header) {

			gint i = 0;

			static const gchar *labels[] = {
				 N_("UID"),
				 N_("Summary"),
				 N_("Description List"),
				 N_("Categories List"),
				 N_("Comment List"),
				 N_("Completed"),
				 N_("Created"),
				 N_("Contact List"),
				 N_("Start"),
				 N_("End"),
				 N_("Due"),
				 N_("percent Done"),
				 N_("Priority"),
				 N_("URL"),
				 N_("Attendees List"),
				 N_("Location"),
				 N_("Modified"),
			};

			line = g_string_new ("");
			for (i = 0; i < G_N_ELEMENTS (labels); i++) {
				if (i > 0)
					g_string_append (line, config->delimiter);
				g_string_append (line, _(labels[i]));
			}

			g_string_append (line, config->newline);

			g_output_stream_write_all (
				stream, line->str, line->len,
				NULL, NULL, NULL);
			g_string_free (line, TRUE);
		}

		for (iter = objects; iter; iter = iter->next) {
			ECalComponent *comp = iter->data;
			gchar *delimiter_temp = NULL;
			const gchar *temp_constchar;
			GSList *temp_list;
			ECalComponentDateTime temp_dt;
			struct icaltimetype *temp_time;
			gint *temp_int;
			ECalComponentText temp_comptext;

			line = g_string_new ("");

			/* Getting the stuff */
			e_cal_component_get_uid (comp, &temp_constchar);
			line = add_string_to_csv (line, temp_constchar, config);

			e_cal_component_get_summary (comp, &temp_comptext);
			line = add_string_to_csv (
				line, temp_comptext.value, config);

			e_cal_component_get_description_list (comp, &temp_list);
			line = add_list_to_csv (
				line, temp_list, config, ECALCOMPONENTTEXT);
			if (temp_list)
				e_cal_component_free_text_list (temp_list);

			e_cal_component_get_categories_list (comp, &temp_list);
			line = add_list_to_csv (
				line, temp_list, config, CONSTCHAR);
			if (temp_list)
				e_cal_component_free_categories_list (temp_list);

			e_cal_component_get_comment_list (comp, &temp_list);
			line = add_list_to_csv (
				line, temp_list, config, ECALCOMPONENTTEXT);
			if (temp_list)
				e_cal_component_free_text_list (temp_list);

			e_cal_component_get_completed (comp, &temp_time);
			line = add_time_to_csv (line, temp_time, config);
			if (temp_time)
				e_cal_component_free_icaltimetype (temp_time);

			e_cal_component_get_created (comp, &temp_time);
			line = add_time_to_csv (line, temp_time, config);
			if (temp_time)
				e_cal_component_free_icaltimetype (temp_time);

			e_cal_component_get_contact_list (comp, &temp_list);
			line = add_list_to_csv (
				line, temp_list, config, ECALCOMPONENTTEXT);
			if (temp_list)
				e_cal_component_free_text_list (temp_list);

			e_cal_component_get_dtstart (comp, &temp_dt);
			line = add_time_to_csv (
				line, temp_dt.value ?
				temp_dt.value : NULL, config);
			e_cal_component_free_datetime (&temp_dt);

			e_cal_component_get_dtend (comp, &temp_dt);
			line = add_time_to_csv (
				line, temp_dt.value ?
				temp_dt.value : NULL, config);
			e_cal_component_free_datetime (&temp_dt);

			e_cal_component_get_due (comp, &temp_dt);
			line = add_time_to_csv (
				line, temp_dt.value ?
				temp_dt.value : NULL, config);
			e_cal_component_free_datetime (&temp_dt);

			e_cal_component_get_percent (comp, &temp_int);
			line = add_nummeric_to_csv (line, temp_int, config);

			e_cal_component_get_priority (comp, &temp_int);
			line = add_nummeric_to_csv (line, temp_int, config);

			e_cal_component_get_url (comp, &temp_constchar);
			line = add_string_to_csv (line, temp_constchar, config);

			if (e_cal_component_has_attendees (comp)) {
				e_cal_component_get_attendee_list (comp, &temp_list);
				line = add_list_to_csv (
					line, temp_list, config,
					ECALCOMPONENTATTENDEE);
				if (temp_list)
					e_cal_component_free_attendee_list (temp_list);
			} else {
				line = add_list_to_csv (
					line, NULL, config,
					ECALCOMPONENTATTENDEE);
			}

			e_cal_component_get_location (comp, &temp_constchar);
			line = add_string_to_csv (line, temp_constchar, config);

			e_cal_component_get_last_modified (comp, &temp_time);

			/* Append a newline (record delimiter) */
			delimiter_temp = config->delimiter;
			config->delimiter = config->newline;

			line = add_time_to_csv (line, temp_time, config);

			/* And restore for the next record */
			config->delimiter = delimiter_temp;

			/* Important note!
			 * The documentation is not requiring this!
			 *
			 * if (temp_time)
			 *     e_cal_component_free_icaltimetype (temp_time);
			 *
			 * Please uncomment and fix documentation if untrue
			 * http://www.gnome.org/projects/evolution/
			 *	developer-doc/libecal/ECalComponent.html
			 *	#e-cal-component-get-last-modified
			 */
			g_output_stream_write_all (
				stream, line->str, line->len,
				NULL, NULL, &error);

			/* It's written, so we can free it */
			g_string_free (line, TRUE);
		}

		g_output_stream_close (stream, NULL, NULL);

		e_cal_client_free_ecalcomp_slist (objects);
	}

	if (stream)
		g_object_unref (stream);

	g_object_unref (source_client);

	g_free (config->delimiter);
	g_free (config->quote);
	g_free (config->newline);
	g_free (config);

	if (error != NULL) {
		display_error_message (
			gtk_widget_get_toplevel (GTK_WIDGET (selector)),
			error);
		g_error_free (error);
	}
}
Beispiel #4
0
static void
cal_component_preview_write_html (ECalComponentPreview *preview,
                                  GString *buffer)
{
	ECalClient *client;
	ECalComponent *comp;
	icaltimezone *default_zone;
	ECalComponentText text;
	ECalComponentDateTime dt;
	gchar *str;
	GString *string;
	GSList *list, *iter;
	icalcomponent *icalcomp;
	icalproperty *icalprop;
	icalproperty_status status;
	const gchar *location;
	gint *priority_value;

	client = preview->priv->client;
	comp = preview->priv->comp;
	default_zone = preview->priv->timezone;

	/* write document header */
	e_cal_component_get_summary (comp, &text);

	g_string_append (buffer, HTML_HEADER);
	g_string_append (buffer, "<body class=\"-e-web-view-background-color -e-web-view-text-color\">");

	if (text.value)
		g_string_append_printf (buffer, "<h2>%s</h2>", text.value);
	else
		g_string_append_printf (buffer, "<h2><i>%s</i></h2>",_("Untitled"));

	g_string_append (buffer, "<table border=\"0\" cellspacing=\"5\">");

	/* write icons for the categories */
	string = g_string_new (NULL);
	e_cal_component_get_categories_list (comp, &list);
	if (list != NULL)
		g_string_append_printf (buffer, "<tr><th>%s</th><td>", _("Categories:"));
	for (iter = list; iter != NULL; iter = iter->next) {
		const gchar *category = iter->data;
		gchar *icon_file;

		icon_file = e_categories_dup_icon_file_for (category);
		if (icon_file && g_file_test (icon_file, G_FILE_TEST_EXISTS)) {
			gchar *uri;

			uri = g_filename_to_uri (icon_file, NULL, NULL);
			g_string_append_printf (
				buffer, "<img alt=\"%s\" src=\"evo-%s\">",
				category, uri);
			g_free (uri);
		} else {
			if (iter != list)
				g_string_append_len (string, ", ", 2);
			g_string_append (string, category);
		}

		g_free (icon_file);
	}
	if (string->len > 0)
		g_string_append_printf (buffer, "%s", string->str);
	if (list != NULL)
		g_string_append (buffer, "</td></tr>");
	e_cal_component_free_categories_list (list);
	g_string_free (string, TRUE);

	/* write location */
	e_cal_component_get_location (comp, &location);
	if (location)
		g_string_append_printf (
			buffer, "<tr><th>%s</th><td>%s</td></tr>",
			_("Summary:"), text.value);

	/* write start date */
	e_cal_component_get_dtstart (comp, &dt);
	if (dt.value != NULL) {
		str = timet_to_str_with_zone (&dt, client, default_zone);
		g_string_append_printf (
			buffer, "<tr><th>%s</th><td>%s</td></tr>",
			_("Start Date:"), str);
		g_free (str);
	}
	e_cal_component_free_datetime (&dt);

	/* write end date */
	e_cal_component_get_dtend (comp, &dt);
	if (dt.value != NULL) {
		str = timet_to_str_with_zone (&dt, client, default_zone);
		g_string_append_printf (
			buffer,"<tr><th>%s</th><td>%s</td></tr>",
			_("End Date:"), str);
		g_free (str);
	}
	e_cal_component_free_datetime (&dt);

	/* write Due Date */
	e_cal_component_get_due (comp, &dt);
	if (dt.value != NULL) {
		str = timet_to_str_with_zone (&dt, client, default_zone);
		g_string_append_printf (
			buffer, "<tr><th>%s</th><td>%s</td></tr>",
			_("Due Date:"), str);
		g_free (str);
	}
	e_cal_component_free_datetime (&dt);

	/* write status */
	icalcomp = e_cal_component_get_icalcomponent (comp);
	icalprop = icalcomponent_get_first_property (
		icalcomp, ICAL_STATUS_PROPERTY);
	if (icalprop != NULL) {
		g_string_append_printf (
			buffer, "<tr><th>%s</th>",
			_("Status:"));
		e_cal_component_get_status (comp, &status);
		switch (status) {
		case ICAL_STATUS_INPROCESS :
			str = g_strdup (_("In Progress"));
			break;
		case ICAL_STATUS_COMPLETED :
			str = g_strdup (_("Completed"));
			break;
		case ICAL_STATUS_CANCELLED :
			str = g_strdup (_("Cancelled"));
			break;
		case ICAL_STATUS_NONE :
		default :
			str = g_strdup (_("Not Started"));
			break;
		}

		g_string_append_printf (buffer, "<td>%s</td></tr>", str);
		g_free (str);
	}

	/* write priority */
	e_cal_component_get_priority (comp, &priority_value);
	if (priority_value && *priority_value != 0) {
		g_string_append_printf (
			buffer, "<tr><th>%s</th>",
			_("Priority:"));
		if (*priority_value <= 4)
			str = g_strdup (_("High"));
		else if (*priority_value == 5)
			str = g_strdup (_("Normal"));
		else
			str = g_strdup (_("Low"));

		g_string_append_printf (buffer, "<td>%s</td></tr>", str);

		g_free (str);
	}

	if (priority_value)
		e_cal_component_free_priority (priority_value);

	/* write description and URL */
	g_string_append (buffer, "<tr><td colspan=\"2\"><hr></td></tr>");

	e_cal_component_get_description_list (comp, &list);
	if (list) {
		GSList *node;

		g_string_append_printf (
			buffer, "<tr><th>%s</th>",
			_("Description:"));

		g_string_append (buffer, "<td class=\"description\">");

		for (node = list; node != NULL; node = node->next) {
			gchar *html;

			text = * (ECalComponentText *) node->data;
			html = camel_text_to_html (
				text.value ? text.value : "",
				CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
				CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
				CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
				CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES, 0);

			if (html)
				g_string_append_printf (buffer, "%s", html);

			g_free (html);
		}

		g_string_append (buffer, "</td></tr>");

		e_cal_component_free_text_list (list);
	}

	/* URL */
	e_cal_component_get_url (comp, (const gchar **) &str);
	if (str) {
		g_string_append_printf (
			buffer, "<tr><th>%s</th><td><a href=\"%s\">%s</a></td></tr>",
			_("Web Page:"), str, str);
	}

	g_string_append (buffer, "</table>");

	/* close document */
	g_string_append (buffer, "</body></html>");
}
/* (has-categories? STR+)
 * (has-categories? #f)
 *
 * STR - At least one string specifying a category
 * Or you can specify a single #f (boolean false) value for components
 * that have no categories assigned to them ("unfiled").
 *
 * Returns a boolean indicating whether the component has all the specified
 * categories.
 */
static ESExpResult *
func_has_categories (ESExp *esexp,
                     gint argc,
                     ESExpResult **argv,
                     gpointer data)
{
	SearchContext *ctx = data;
	gboolean unfiled;
	gint i;
	GSList *categories;
	gboolean matches;
	ESExpResult *result;

	/* Check argument types */

	if (argc < 1) {
		e_sexp_fatal_error (
			esexp, _("\"%s\" expects at least one "
			"argument"),
			"has-categories");
		return NULL;
	}

	if (argc == 1 && argv[0]->type == ESEXP_RES_BOOL)
		unfiled = TRUE;
	else
		unfiled = FALSE;

	if (!unfiled)
		for (i = 0; i < argc; i++)
			if (argv[i]->type != ESEXP_RES_STRING) {
				e_sexp_fatal_error (
					esexp, _("\"%s\" expects "
					"all arguments to "
					"be strings or "
					"one and only one "
					"argument to be a "
					"boolean false "
					"(#f)"),
					"has-categories");
				return NULL;
			}

	/* Search categories.  First, if there are no categories we return
	 * whether unfiled components are supposed to match.
	 */

	e_cal_component_get_categories_list (ctx->comp, &categories);
	if (!categories) {
		result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
		result->value.boolean = unfiled;

		return result;
	}

	/* Otherwise, we *do* have categories but unfiled components were
	 * requested, so this component does not match.
	 */
	if (unfiled) {
		result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
		result->value.boolean = FALSE;

		return result;
	}

	matches = TRUE;

	for (i = 0; i < argc; i++) {
		const gchar *sought;
		GSList *l;
		gboolean has_category;

		sought = argv[i]->value.string;

		has_category = FALSE;

		for (l = categories; l; l = l->next) {
			const gchar *category;

			category = l->data;

			if (strcmp (category, sought) == 0) {
				has_category = TRUE;
				break;
			}
		}

		if (!has_category) {
			matches = FALSE;
			break;
		}
	}

	e_cal_component_free_categories_list (categories);

	result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
	result->value.boolean = matches;

	return result;
}