/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* * gcal-utils.c * * Copyright (C) 2012 - Erick Pérez Castellanos * * 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 . */ #include "config.h" #define G_LOG_DOMAIN "Utils" #include "gcal-utils.h" #include "gcal-event-widget.h" #include "gcal-view.h" #include #include #include #include #include #include #include /** * SECTION:gcal-utils * @short_description: Utility functions * @title:Utility functions */ static const gint ab_day[7] = { ABDAY_1, ABDAY_2, ABDAY_3, ABDAY_4, ABDAY_5, ABDAY_6, ABDAY_7, }; static const gint month_item[12] = { MON_1, MON_2, MON_3, MON_4, MON_5, MON_6, MON_7, MON_8, MON_9, MON_10, MON_11, MON_12 }; #define SCROLL_HARDNESS 10.0 G_DEFINE_BOXED_TYPE (icaltimetype, icaltime, gcal_dup_icaltime, g_free) /** * datetime_compare_date: * @dt1: (nullable): a #GDateTime * @dt2: (nullable): a #GDateTime * * Compares the dates of @dt1 and @dt2. The times are * ignored. * * Returns: negative, 0 or positive */ gint datetime_compare_date (GDateTime *dt1, GDateTime *dt2) { if (!dt1 && !dt2) return 0; else if (!dt1) return -1; else if (!dt2) return 1; if (g_date_time_get_year (dt1) != g_date_time_get_year (dt2)) return (g_date_time_get_year (dt1) - g_date_time_get_year (dt2)) * 360; if (g_date_time_get_month (dt1) != g_date_time_get_month (dt2)) return (g_date_time_get_month (dt1) - g_date_time_get_month (dt2)) * 30; if (g_date_time_get_day_of_month (dt1) != g_date_time_get_day_of_month (dt2)) return g_date_time_get_day_of_month (dt1) - g_date_time_get_day_of_month (dt2); return 0; } /** * datetime_to_icaltime: * @dt: a #GDateTime * * Converts the #GDateTime's @dt to an #icaltimetype. * * Returns: (transfer full): a #icaltimetype. */ icaltimetype* datetime_to_icaltime (GDateTime *dt) { icaltimetype *idt; if (!dt) return NULL; idt = g_new0 (icaltimetype, 1); idt->year = g_date_time_get_year (dt); idt->month = g_date_time_get_month (dt); idt->day = g_date_time_get_day_of_month (dt); idt->hour = g_date_time_get_hour (dt); idt->minute = g_date_time_get_minute (dt); idt->second = g_date_time_get_seconds (dt); idt->is_date = (idt->hour == 0 && idt->minute == 0 && idt->second == 0); return idt; } /** * icaltime_to_datetime: * @date: an #icaltimetype * * Converts the #icaltimetype's @date to a #GDateTime. The * timezone is preserved. * * Returns: (transfer full): a #GDateTime. */ GDateTime* icaltime_to_datetime (const icaltimetype *date) { GDateTime *dt; GTimeZone *tz; tz = date->zone ? g_time_zone_new (icaltime_get_tzid (*date)) : g_time_zone_new_utc (); dt = g_date_time_new (tz, date->year, date->month, date->day, date->is_date ? 0 : date->hour, date->is_date ? 0 : date->minute, date->is_date ? 0 : date->second); g_clear_pointer (&tz, g_time_zone_unref); return dt; } /** * datetime_is_date: * @dt: a #GDateTime * * Checks if @dt represents a date. A pure date * has the values of hour, minutes and seconds set * to 0. * * Returns: %TRUE if @dt is a date, %FALSE if it's a * timed datetime. */ gboolean datetime_is_date (GDateTime *dt) { return g_date_time_get_hour (dt) == 0 && g_date_time_get_minute (dt) == 0 && g_date_time_get_seconds (dt) == 0; } /** * gcal_dup_icaltime: * @date: an #icaltimetype * * Creates an exact copy of @date. * * Returns: (transfer full): an #icaltimetype */ icaltimetype* gcal_dup_icaltime (const icaltimetype *date) { icaltimetype *new_date; if (date == NULL) return NULL; new_date= g_new (icaltimetype, 1); new_date->year = date->year; new_date->month = date->month; new_date->day = date->day; new_date->hour = date->hour; new_date->minute = date->minute; new_date->second = date->second; new_date->is_utc = date->is_utc; new_date->is_date = date->is_date; new_date->is_daylight = date->is_daylight; new_date->zone = date->zone; return new_date; } /** * gcal_get_weekday: * @i: the weekday index * * Retrieves the weekday name. * * Returns: (transfer full): the weekday name */ gchar* gcal_get_weekday (gint i) { return nl_langinfo (ab_day[i]); } /** * gcal_get_month_name: * @i: the month index * * Retrieves the month name. * * Returns: (transfer full): the month name */ gchar* gcal_get_month_name (gint i) { return nl_langinfo (month_item[i]); } /** * gcal_get_surface_from_color: * @color: a #GdkRGBA * @size: the size of the surface * * Creates a squared surface filled with @color. The * surface is always @size x @size. * * Returns: (transfer full): a #cairo_surface_t */ cairo_surface_t* gcal_get_surface_from_color (GdkRGBA *color, gint size) { cairo_surface_t *surface; cairo_t *cr; /* TODO: review size here, maybe not hardcoded */ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size); cr = cairo_create (surface); cairo_set_source_rgba (cr, color->red, color->green, color->blue, color->alpha); cairo_rectangle (cr, 0, 0, size, size); cairo_fill (cr); cairo_destroy (cr); return surface; } /** * get_circle_surface_from_color: * @color: a #GdkRGBA * @size: the size of the surface * * Creates a circular surface filled with @color. The * surface is always @size x @size. * * Returns: (transfer full): a #cairo_surface_t */ cairo_surface_t* get_circle_surface_from_color (GdkRGBA *color, gint size) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size); cr = cairo_create (surface); cairo_set_source_rgba (cr, color->red, color->green, color->blue, color->alpha); cairo_arc (cr, size / 2.0, size / 2.0, size / 2.0, 0., 2 * M_PI); cairo_fill (cr); cairo_destroy (cr); return surface; } /** * get_color_name_from_source: * @source: an #ESource * @out_color: return value for the color * * Utility function to retrieve the color from @source. */ void get_color_name_from_source (ESource *source, GdkRGBA *out_color) { ESourceSelectable *extension = E_SOURCE_SELECTABLE (e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR)); /* FIXME: We should handle calendars colours better */ if (!gdk_rgba_parse (out_color, e_source_selectable_get_color (extension))) gdk_rgba_parse (out_color, "#becedd"); /* calendar default colour */ } /** * get_desc_from_component: * @component: an #ECalComponent * @joint_char: the character to use when merging event comments * * Utility method to handle the extraction of the description from an * #ECalComponent. This cycle through the list of #ECalComponentText * and concatenate each string into one. * * Returns: (nullable)(transfer full) a new allocated string with the * description **/ gchar* get_desc_from_component (ECalComponent *component, const gchar *joint_char) { GSList *text_list; GSList *l; gchar *desc = NULL; e_cal_component_get_description_list (component, &text_list); for (l = text_list; l != NULL; l = l->next) { if (l->data != NULL) { ECalComponentText *text; gchar *carrier; text = l->data; if (desc != NULL) { carrier = g_strconcat (desc, joint_char, text->value, NULL); g_free (desc); desc = carrier; } else { desc = g_strdup (text->value); } } } e_cal_component_free_text_list (text_list); return desc != NULL ? g_strstrip (desc) : NULL; } /** * get_uuid_from_component: * @source: an {@link ESource} * @component: an {@link ECalComponent} * * Obtains the uuid from a component in the form * "source_uid:event_uid:event_rid" or "source:uid:event_uid" if the * component doesn't hold a recurrence event * * Returns: (Transfer full) a new allocated string with the description **/ gchar* get_uuid_from_component (ESource *source, ECalComponent *component) { gchar *uuid; ECalComponentId *id; id = e_cal_component_get_id (component); if (id->rid != NULL) { uuid = g_strdup_printf ("%s:%s:%s", e_source_get_uid (source), id->uid, id->rid); } else { uuid = g_strdup_printf ("%s:%s", e_source_get_uid (source), id->uid); } e_cal_component_free_id (id); return uuid; } /** * get_first_weekday: * * Copied from Clocks, which by itself is * copied from GtkCalendar. * * Returns: the first weekday, from 0 to 6 */ gint get_first_weekday (void) { int week_start; #ifdef HAVE__NL_TIME_FIRST_WEEKDAY union { unsigned int word; char *string; } langinfo; int week_1stday = 0; int first_weekday = 1; guint week_origin; #else char *gtk_week_start; #endif #ifdef HAVE__NL_TIME_FIRST_WEEKDAY langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY); first_weekday = langinfo.string[0]; langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY); week_origin = langinfo.word; if (week_origin == 19971130) /* Sunday */ week_1stday = 0; else if (week_origin == 19971201) /* Monday */ week_1stday = 1; else g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.\n"); week_start = (week_1stday + first_weekday - 1) % 7; #else /* Use a define to hide the string from xgettext */ # define GTK_WEEK_START "calendar:week_start:0" gtk_week_start = dgettext ("gtk30", GTK_WEEK_START); if (strncmp (gtk_week_start, "calendar:week_start:", 20) == 0) week_start = *(gtk_week_start + 20) - '0'; else week_start = -1; if (week_start < 0 || week_start > 6) { g_warning ("Whoever translated calendar:week_start:0 for GTK+ " "did so wrongly.\n"); week_start = 0; } #endif return week_start; } /** * build_component_from_details: * @summary: * @initial_date: * @final_date: * * Create a component with the provided details * * Returns: (transfer full): an {@link ECalComponent} object **/ ECalComponent* build_component_from_details (const gchar *summary, GDateTime *initial_date, GDateTime *final_date) { ECalComponent *event; ECalComponentDateTime dt; ECalComponentText summ; icaltimezone *zone; gboolean all_day; event = e_cal_component_new (); e_cal_component_set_new_vtype (event, E_CAL_COMPONENT_EVENT); /* * Check if the event is all day. Notice that it can be all day even * without the final date. */ all_day = datetime_is_date (initial_date) && (final_date ? datetime_is_date (final_date) : TRUE); /* * When the event is all day, we consider UTC timezone by default. Otherwise, * we always use the system timezone to create new events */ if (all_day) { zone = icaltimezone_get_utc_timezone (); } else { gchar *system_tz = e_cal_system_timezone_get_location (); zone = icaltimezone_get_builtin_timezone (system_tz); g_free (system_tz); } /* Start date */ dt.value = datetime_to_icaltime (initial_date); icaltime_set_timezone (dt.value, zone); dt.value->is_date = all_day; dt.tzid = icaltimezone_get_tzid (zone); e_cal_component_set_dtstart (event, &dt); g_free (dt.value); /* End date */ if (!final_date) final_date = g_date_time_add_days (initial_date, 1); dt.value = datetime_to_icaltime (final_date); icaltime_set_timezone (dt.value, zone); dt.value->is_date = all_day; dt.tzid = icaltimezone_get_tzid (zone); e_cal_component_set_dtend (event, &dt); g_free (dt.value); /* Summary */ summ.altrep = NULL; summ.value = summary; e_cal_component_set_summary (event, &summ); e_cal_component_commit_sequence (event); return event; } /** * icaltime_compare_date: * @date1: an #icaltimetype * @date2: an #icaltimetype * * Compare date parts of #icaltimetype objects. Returns negative value, * 0 or positive value accordingly if @date1 is before, same day of * after date2. * * As a bonus it returns the amount of days passed between two days on the * same year. * * Returns: negative, 0 or positive **/ gint icaltime_compare_date (const icaltimetype *date1, const icaltimetype *date2) { if (date2 == NULL) return 0; if (date1->year < date2->year) return -1; else if (date1->year > date2->year) return 1; else return time_day_of_year (date1->day, date1->month - 1, date1->year) - time_day_of_year (date2->day, date2->month - 1, date2->year); } /** * icaltime_compare_with_current: * @date1: an #icaltimetype * @date2: an #icaltimetype * @current_time_t: the current time * * Compares @date1 and @date2 against the current time. Dates * closer to the current date are sorted before. * * Returns: negative if @date1 comes after @date2, 0 if they're * equal, positive otherwise */ gint icaltime_compare_with_current (const icaltimetype *date1, const icaltimetype *date2, time_t *current_time_t) { gint result = 0; time_t start1, start2, diff1, diff2; start1 = icaltime_as_timet_with_zone (*date1, date1->zone != NULL ? date1->zone : e_cal_util_get_system_timezone ()); start2 = icaltime_as_timet_with_zone (*date2, date2->zone != NULL ? date2->zone : e_cal_util_get_system_timezone ()); diff1 = start1 - *current_time_t; diff2 = start2 - *current_time_t; if (diff1 != diff2) { if (diff1 == 0) result = -1; else if (diff2 == 0) result = 1; if (diff1 > 0 && diff2 < 0) result = -1; else if (diff2 > 0 && diff1 < 0) result = 1; else if (diff1 < 0 && diff2 < 0) result = ABS (diff1) - ABS (diff2); else if (diff1 > 0 && diff2 > 0) result = diff1 - diff2; } return result; } /** * get_start_of_week: * @date: an #icaltimetype * * Retrieves the start of the week that @date * falls in. This function already takes the * first weekday into account. * * Returns: (transfer full): a #GDateTime with * the start of the week. */ GDateTime* get_start_of_week (icaltimetype *date) { icaltimetype *new_date; GDateTime *dt; new_date = g_new0 (icaltimetype, 1); *new_date = icaltime_from_day_of_year (icaltime_start_doy_week (*date, get_first_weekday () + 1), date->year); new_date->is_date = 0; new_date->hour = 0; new_date->minute = 0; new_date->second = 0; dt = g_date_time_new_local (new_date->year, new_date->month, new_date->day, 0, 0, 0); g_clear_pointer (&new_date, g_free); return dt; } /** * get_end_of_week: * @date: an #icaltimetype * * Retrieves the end of the week that @date * falls in. This function already takes the * first weekday into account. * * Returns: (transfer full): a #GDateTime with * the end of the week. */ GDateTime* get_end_of_week (icaltimetype *date) { GDateTime *week_start, *week_end; week_start = get_start_of_week (date); week_end = g_date_time_add_days (week_start, 7); g_clear_pointer (&week_start, g_date_time_unref); return week_end; } /** * is_clock_format_24h: * * Retrieves whether the current clock format is * 12h or 24h based. * * Returns: %TRUE if the clock format is 24h, %FALSE * otherwise. */ gboolean is_clock_format_24h (void) { static GSettings *settings = NULL; g_autofree gchar *clock_format = NULL; if (!settings) settings = g_settings_new ("org.gnome.desktop.interface"); clock_format = g_settings_get_string (settings, "clock-format"); return g_strcmp0 (clock_format, "24h") == 0; } /** * e_strftime_fix_am_pm: * * Function to do a last minute fixup of the AM/PM stuff if the locale * and gettext haven't done it right. Most English speaking countries * except the USA use the 24 hour clock (UK, Australia etc). However * since they are English nobody bothers to write a language * translation (gettext) file. So the locale turns off the AM/PM, but * gettext does not turn on the 24 hour clock. Leaving a mess. * * This routine checks if AM/PM are defined in the locale, if not it * forces the use of the 24 hour clock. * * The function itself is a front end on strftime and takes exactly * the same arguments. * * TODO: Actually remove the '%p' from the fixed up string so that * there isn't a stray space. */ gsize e_strftime_fix_am_pm (gchar *str, gsize max, const gchar *fmt, const struct tm *tm) { gchar buf[10]; gchar *sp; gchar *ffmt; gsize ret; if (strstr(fmt, "%p")==NULL && strstr(fmt, "%P")==NULL) { /* No AM/PM involved - can use the fmt string directly */ ret = e_strftime (str, max, fmt, tm); } else { /* Get the AM/PM symbol from the locale */ e_strftime (buf, 10, "%p", tm); if (buf[0]) { /* AM/PM have been defined in the locale * so we can use the fmt string directly. */ ret = e_strftime (str, max, fmt, tm); } else { /* No AM/PM defined by locale * must change to 24 hour clock. */ ffmt = g_strdup (fmt); for (sp=ffmt; (sp=strstr(sp, "%l")); sp++) { /* Maybe this should be 'k', but I have never * seen a 24 clock actually use that format. */ sp[1]='H'; } for (sp=ffmt; (sp=strstr(sp, "%I")); sp++) { sp[1]='H'; } ret = e_strftime (str, max, ffmt, tm); g_free (ffmt); } } return (ret); } /** * e_utf8_strftime_fix_am_pm: * * Stolen from Evolution codebase. Selects the * correct time format. * * Returns: the size of the string */ gsize e_utf8_strftime_fix_am_pm (gchar *str, gsize max, const gchar *fmt, const struct tm *tm) { gsize sz, ret; gchar *locale_fmt, *buf; locale_fmt = g_locale_from_utf8 (fmt, -1, NULL, &sz, NULL); if (!locale_fmt) return 0; ret = e_strftime_fix_am_pm (str, max, locale_fmt, tm); if (!ret) { g_free (locale_fmt); return 0; } buf = g_locale_to_utf8 (str, ret, NULL, &sz, NULL); if (!buf) { g_free (locale_fmt); return 0; } if (sz >= max) { gchar *tmp = buf + max - 1; tmp = g_utf8_find_prev_char (buf, tmp); if (tmp) sz = tmp - buf; else sz = 0; } memcpy (str, buf, sz); str[sz] = '\0'; g_free (locale_fmt); g_free (buf); return sz; } /** * fix_popover_menu_icons: * @window: a #GtkPopover * * Hackish code that inspects the popover's children, * retrieve the hidden GtkImage buried under lots of * widgets, and make it visible again. * * Hopefully, we'll find a better way to do this in * the long run. * */ void fix_popover_menu_icons (GtkPopover *popover) { GtkWidget *popover_stack; GtkWidget *menu_section; GtkWidget *menu_section_box; GList *stack_children; GList *menu_section_children; GList *menu_section_box_children, *aux; popover_stack = gtk_bin_get_child (GTK_BIN (popover)); stack_children = gtk_container_get_children (GTK_CONTAINER (popover_stack)); /** * At the moment, the popover stack surely contains only * one child of type GtkMenuSectionBox, which contains * a single GtkBox. */ menu_section = stack_children->data; menu_section_children = gtk_container_get_children (GTK_CONTAINER (menu_section)); /** * Get the unique box's children. */ menu_section_box = menu_section_children->data; menu_section_box_children = gtk_container_get_children (GTK_CONTAINER (menu_section_box)); gtk_style_context_add_class (gtk_widget_get_style_context (menu_section_box), "calendars-list"); /** * Iterate through the GtkModelButtons inside the menu section box. */ for (aux = menu_section_box_children; aux != NULL; aux = aux->next) { GtkWidget *button_box; GList *button_box_children, *aux2; button_box = gtk_bin_get_child (GTK_BIN (aux->data)); button_box_children = gtk_container_get_children (GTK_CONTAINER (button_box)); /** * Since there is no guarantee that the first child is * the GtkImage we're looking for, we have to iterate * through the children and check if the types match. */ for (aux2 = button_box_children; aux2 != NULL; aux2 = aux2->next) { GtkWidget *button_box_child; button_box_child = aux2->data; if (g_type_is_a (G_OBJECT_TYPE (button_box_child), GTK_TYPE_IMAGE)) { gtk_style_context_add_class (gtk_widget_get_style_context (button_box_child), "calendar-color-image"); gtk_widget_show (button_box_child); break; } } g_list_free (button_box_children); } g_list_free (stack_children); g_list_free (menu_section_children); g_list_free (menu_section_box_children); } /** * uri_get_fields: * @uri: the URI * @schema: (nullable): return location for the schema of the URI * @host: (nullable): return location for the host of the URI * @path: (nullable): return location for the resource path * * Split the given URI into the fields. * * Returns: #TRUE if @uri could be parsed, #FALSE otherwise */ gboolean uri_get_fields (const gchar *uri, gchar **schema, gchar **host, gchar **path, gboolean *is_file) { GRegex *regex; GMatchInfo *match; gboolean valid; g_return_val_if_fail (uri != NULL, FALSE); match = NULL; valid = FALSE; regex = g_regex_new ("([a-zA-Z0-9\\+\\.\\-]*:\\/\\/){0,1}([-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b)([-a-zA-Z0-9@:%_\\+.//=]*)", G_REGEX_CASELESS, 0, NULL); /* * Retrieved matching URI. The whole url is * checked and the regex groups are: * 1. schema * 2. host * 3. server path */ if (g_regex_match (regex, uri, 0, &match)) { valid = TRUE; if (schema != NULL) *schema = g_match_info_fetch (match, 1); if (host != NULL) *host = g_match_info_fetch (match, 2); if (path != NULL) *path = g_match_info_fetch (match, 3); g_match_info_free (match); } else { if (schema != NULL) *schema = NULL; if (host != NULL) *host = NULL; if (path != NULL) *path = NULL; } /* File extension */ if (is_file) { GRegex *extension_regex; extension_regex = g_regex_new ("(\\.[a-zA-Z0-9]+)$", G_REGEX_CASELESS, 0, NULL); *is_file = g_regex_match (extension_regex, uri, 0, NULL); g_regex_unref (extension_regex); } g_regex_unref (regex); return valid; } /** * get_source_parent_name_color: * @manager: a #GcalManager * @source: an #ESource * @name: (nullable): return location for the name * @color: (nullable): return location for the color * * Retrieves the name and the color of the #ESource that is * parent of @source. */ void get_source_parent_name_color (GcalManager *manager, ESource *source, gchar **name, gchar **color) { ESource *parent_source; g_assert (source && E_IS_SOURCE (source)); parent_source = gcal_manager_get_source (manager, e_source_get_parent (source)); if (name != NULL) *name = e_source_dup_display_name (parent_source); if (color != NULL) { GdkRGBA c; get_color_name_from_source (parent_source, &c); *color = gdk_rgba_to_string (&c); } } /** * format_utc_offset: * @offset: an UTC offset * * Formats the UTC offset to a string that GTimeZone can * parse. E.g. "-0300" or "+0530". * * Returns: (transfer full): a string representing the * offset */ gchar* format_utc_offset (gint64 offset) { const char *sign = "+"; gint hours, minutes, seconds; if (offset < 0) { offset = -offset; sign = "-"; } /* offset can be seconds or microseconds */ if (offset >= 1000000) offset = offset / 1000000; hours = offset / 3600; minutes = (offset % 3600) / 60; seconds = offset % 60; if (seconds > 0) return g_strdup_printf ("%s%02i%02i%02i", sign, hours, minutes, seconds); else return g_strdup_printf ("%s%02i%02i", sign, hours, minutes); } /** * get_alarm_trigger_minutes: * @event: a #GcalEvent * @alarm: a #ECalComponentAlarm * * Calculates the number of minutes before @event's * start time that the alarm should be triggered. * * Returns: the number of minutes before the event * start that @alarm will be triggered. */ gint get_alarm_trigger_minutes (GcalEvent *event, ECalComponentAlarm *alarm) { ECalComponentAlarmTrigger trigger; GDateTime *alarm_dt; gint diff; e_cal_component_alarm_get_trigger (alarm, &trigger); /* * We only support alarms relative to the start date, and solely * ignore whetever different it may be. */ if (trigger.type != E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START) return -1; alarm_dt = g_date_time_add_full (gcal_event_get_date_start (event), 0, 0, - (trigger.u.rel_duration.days + trigger.u.rel_duration.weeks * 7), - trigger.u.rel_duration.hours, - trigger.u.rel_duration.minutes, - trigger.u.rel_duration.seconds); diff = g_date_time_difference (gcal_event_get_date_start (event), alarm_dt) / G_TIME_SPAN_MINUTE; g_clear_pointer (&alarm_dt, g_date_time_unref); return diff; } /** * should_change_date_for_scroll: * @scroll_value: the current scroll value * @scroll_event: the #GdkEventScroll that is being parsed * * Utility function to check if the date should change based * on the scroll. The date is changed when the user scrolls * too much on touchpad, or performs a rotation of the scroll * button in a mouse. * * Returns: %TRUE if the date should change, %FALSE otherwise. */ gboolean should_change_date_for_scroll (gdouble *scroll_value, GdkEventScroll *scroll_event) { gdouble delta_y; switch (scroll_event->direction) { case GDK_SCROLL_DOWN: *scroll_value = SCROLL_HARDNESS; break; case GDK_SCROLL_UP: *scroll_value = -SCROLL_HARDNESS; break; case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas ((GdkEvent*) scroll_event, NULL, &delta_y); *scroll_value += delta_y; break; /* Ignore horizontal scrolling for now */ case GDK_SCROLL_LEFT: case GDK_SCROLL_RIGHT: default: break; } if (*scroll_value <= -SCROLL_HARDNESS || *scroll_value >= SCROLL_HARDNESS) return TRUE; return FALSE; } /** * is_source_enabled: * @source: an #ESource * * Retrieves whether the @source is enabled or not. * Disabled sources don't show their events. * * Returns: %TRUE if @source is enabled, %FALSE otherwise. */ gboolean is_source_enabled (ESource *source) { ESourceSelectable *selectable; g_return_val_if_fail (E_IS_SOURCE (source), FALSE); selectable = e_source_get_extension (source, E_SOURCE_EXTENSION_CALENDAR); return e_source_selectable_get_selected (selectable); } /** * ask_recurrence_modification_type: * @parent: a #GtkWidget * @modtype: an #ECalObjModType * @source: an #ESource * * Assigns the appropriate modtype while modifying an event * based on user's choice in the GtkMessageDialog that pops up. * The modtype helps the user choose the part of recurrent events * to modify. Such as Only This Event, Subsequent events * or All events. * * Returns: %TRUE if user chooses appropriate option and * @modtype is assigned, %FALSE otherwise. */ gboolean ask_recurrence_modification_type (GtkWidget *parent, GcalRecurrenceModType *modtype, ESource *source) { GtkWidget *dialog; GtkDialogFlags flags; gint result; gboolean is_set; EClient *client; flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT; *modtype = GCAL_RECURRENCE_MOD_THIS_ONLY; dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (parent)), flags, GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("The event you are trying to modify is recurring. The changes you have selected should be applied to:")); gtk_dialog_add_buttons (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Only This Event"), GTK_RESPONSE_ACCEPT, NULL); client = g_object_get_data (G_OBJECT (source), "client"); if (!e_client_check_capability (E_CLIENT (client), CAL_STATIC_CAPABILITY_NO_THISANDFUTURE)) gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Subsequent events"), GTK_RESPONSE_OK); gtk_dialog_add_button (GTK_DIALOG (dialog), _("_All events"), GTK_RESPONSE_YES); gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (gtk_widget_get_toplevel (parent))); result = gtk_dialog_run (GTK_DIALOG (dialog)); switch (result) { case GTK_RESPONSE_CANCEL: is_set = FALSE; break; case GTK_RESPONSE_ACCEPT: *modtype = GCAL_RECURRENCE_MOD_THIS_ONLY; is_set = TRUE; break; case GTK_RESPONSE_OK: *modtype = GCAL_RECURRENCE_MOD_THIS_AND_FUTURE; is_set = TRUE; break; case GTK_RESPONSE_YES: *modtype = GCAL_RECURRENCE_MOD_ALL; is_set = TRUE; break; default: is_set = FALSE; break; } gtk_widget_destroy (GTK_WIDGET (dialog)); return is_set; } struct { const gchar *territory; GcalWeekDay no_work_days; } no_work_day_per_locale[] = { { "AE", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* United Arab Emirates */, { "AF", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Afghanistan */, { "BD", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Bangladesh */, { "BH", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Bahrain */, { "BN", GCAL_WEEK_DAY_SUNDAY | GCAL_WEEK_DAY_FRIDAY } /* Brunei Darussalam */, { "CR", GCAL_WEEK_DAY_SATURDAY } /* Costa Rica */, { "DJ", GCAL_WEEK_DAY_FRIDAY } /* Djibouti */, { "DZ", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Algeria */, { "EG", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Egypt */, { "GN", GCAL_WEEK_DAY_SATURDAY } /* Equatorial Guinea */, { "HK", GCAL_WEEK_DAY_SATURDAY } /* Hong Kong */, { "IL", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Israel */, { "IQ", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Iraq */, { "IR", GCAL_WEEK_DAY_THURSDAY | GCAL_WEEK_DAY_FRIDAY } /* Iran */, { "KW", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Kuwait */, { "KZ", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Kazakhstan */, { "LY", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Libya */, { "MX", GCAL_WEEK_DAY_SATURDAY } /* Mexico */, { "MY", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Malaysia */, { "NP", GCAL_WEEK_DAY_SATURDAY } /* Nepal */, { "OM", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Oman */, { "QA", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Qatar */, { "SA", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Saudi Arabia */, { "SU", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Sudan */, { "SY", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Syria */, { "UG", GCAL_WEEK_DAY_SUNDAY } /* Uganda */, { "YE", GCAL_WEEK_DAY_FRIDAY | GCAL_WEEK_DAY_SATURDAY } /* Yemen */, }; /** * is_workday: * @day: a guint representing the day of a week (0…Sunday, 6…Saturday) * * Checks whether @day is workday or not based on the Territory part of Locale. * * Returns: %TRUE if @day is a workday, %FALSE otherwise. */ gboolean is_workday (guint day) { GcalWeekDay no_work_days; gchar *locale; gchar territory[3] = { 0, }; guint i; if (day > 6) return FALSE; no_work_days = GCAL_WEEK_DAY_SATURDAY | GCAL_WEEK_DAY_SUNDAY; locale = getenv ("LC_ALL"); if (!locale || g_utf8_strlen (locale, -1) < 5) locale = getenv ("LC_TIME"); if (!locale) { g_warning ("Locale is NULL, assuming Saturday and Sunday as non workdays"); return !(no_work_days & 1 << day); } g_return_val_if_fail (g_utf8_strlen (locale, -1) >= 5, TRUE); territory[0] = locale[3]; territory[1] = locale[4]; for (i = 0; i < G_N_ELEMENTS (no_work_day_per_locale); i++) { if (g_strcmp0 (territory, no_work_day_per_locale[i].territory) == 0) { no_work_days = no_work_day_per_locale[i].no_work_days; break; } } return !(no_work_days & 1 << day); } GList* filter_event_list_by_uid_and_modtype (GList *widgets, GcalRecurrenceModType mod, const gchar *uid) { GcalEvent *event; GList *result; GList *l; event = NULL; result = NULL; /* First pass: find the GcalEvent */ for (l = widgets; l != NULL; l = l->next) { GcalEventWidget *event_widget; GcalEvent *ev; event_widget = l->data; /* Safeguard against stray widgets */ if (!GCAL_IS_EVENT_WIDGET (event_widget)) continue; ev = gcal_event_widget_get_event (event_widget); /* * We can assume only one event will have the exact uuid. Even among * recurrencies. */ if (g_str_equal (uid, gcal_event_get_uid (ev))) { result = g_list_prepend (result, event_widget); event = ev; } } /* Second pass: find the other related events */ if (event && mod != GCAL_RECURRENCE_MOD_THIS_ONLY) { ECalComponentId *id; ECalComponent *component; ESource *source; g_autofree gchar *id_prefix; component = gcal_event_get_component (event); source = gcal_event_get_source (event); id = e_cal_component_get_id (component); id_prefix = g_strdup_printf ("%s:%s", e_source_get_uid (source), id->uid); for (l = widgets; l != NULL; l = l->next) { GcalEventWidget *event_widget; GcalEvent *ev; event_widget = l->data; /* Safeguard against stray widgets */ if (!GCAL_IS_EVENT_WIDGET (event_widget)) continue; ev = gcal_event_widget_get_event (event_widget); if (g_str_equal (gcal_event_get_uid (ev), uid)) continue; if (!g_str_has_prefix (gcal_event_get_uid (ev), id_prefix)) continue; if (mod == GCAL_RECURRENCE_MOD_ALL) { result = g_list_prepend (result, event_widget); } else if (mod == GCAL_RECURRENCE_MOD_THIS_AND_FUTURE) { if (g_date_time_compare (gcal_event_get_date_start (event), gcal_event_get_date_start (ev)) < 0) result = g_list_prepend (result, event_widget); } } e_cal_component_free_id (id); } return result; } gboolean gcal_translate_child_window_position (GtkWidget *target, GdkWindow *child_window, gdouble src_x, gdouble src_y, gdouble *real_x, gdouble *real_y) { GdkWindow *window; gdouble x, y; x = src_x; y = src_y; /* Find the (x, y) values relative to the workbench */ window = child_window; while (window && window != gtk_widget_get_window (target)) { gdk_window_coords_to_parent (window, x, y, &x, &y); window = gdk_window_get_parent (window); } if (!window) return FALSE; if (real_x) *real_x = x; if (real_y) *real_y = y; return TRUE; }