From 3bff2b89816c83b467d183485b788c960a0b50da Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Thu, 25 Apr 2019 20:59:08 -0300 Subject: [PATCH] utils: Move to utils/ subdir We will split this file into more, specialized, files so it makes sense to not pollute the top source dir. --- src/meson.build | 3 +- src/utils/gcal-utils.c | 1369 ++++++++++++++++++++++++++++++++++++++++ src/utils/gcal-utils.h | 140 ++++ 3 files changed, 1511 insertions(+), 1 deletion(-) create mode 100644 src/utils/gcal-utils.c create mode 100644 src/utils/gcal-utils.h diff --git a/src/meson.build b/src/meson.build index adef2634..07014f69 100644 --- a/src/meson.build +++ b/src/meson.build @@ -20,6 +20,7 @@ gcal_deps = [ ] sources = files( + 'utils/gcal-utils.c', 'views/gcal-month-cell.c', 'views/gcal-month-popover.c', 'views/gcal-month-view.c', @@ -52,7 +53,6 @@ sources = files( 'gcal-source-dialog.c', 'gcal-time-selector.c', 'gcal-time-zone-monitor.c', - 'gcal-utils.c', 'gcal-window.c', 'gcal-timer.c' ) @@ -106,6 +106,7 @@ sources += configure_file( incs = [ top_inc, + include_directories('utils'), include_directories('views'), include_directories('weather'), ] diff --git a/src/utils/gcal-utils.c b/src/utils/gcal-utils.c new file mode 100644 index 00000000..c7646565 --- /dev/null +++ b/src/utils/gcal-utils.c @@ -0,0 +1,1369 @@ +/* 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" + +/* langinfo.h in glibc 2.27 defines ALTMON_* only if _GNU_SOURCE is defined. */ +#define _GNU_SOURCE + +#include "gcal-enums.h" +#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] = +{ + /* ALTMON_* constants have been introduced in glibc 2.27 (Feb 1, 2018), also + * have been supported in *BSD family (but not in OS X) since 1990s. + * If they exist they are the correct way to obtain the month names in + * nominative case, standalone, without the day number, as used in the + * calendar header. This is obligatory in some languages (Slavic, Baltic, + * Greek, etc.) but also recommended to use in all languages because for + * other languages there is no difference between ALTMON_* and MON_*. + * If ALTMON_* is not supported then we must use MON_*. + */ +#ifdef HAVE_ALTMON + ALTMON_1, + ALTMON_2, + ALTMON_3, + ALTMON_4, + ALTMON_5, + ALTMON_6, + ALTMON_7, + ALTMON_8, + ALTMON_9, + ALTMON_10, + ALTMON_11, + ALTMON_12 +#else + MON_1, + MON_2, + MON_3, + MON_4, + MON_5, + MON_6, + MON_7, + MON_8, + MON_9, + MON_10, + MON_11, + MON_12 +#endif +}; + +#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) +{ + return g_memdup (date, sizeof (icaltimetype)); +} + +/** + * 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; + + 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; + gint week_1stday = 0; + gint first_weekday = 1; + guint week_origin; + + 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 + + gchar *gtk_week_start; + + + /* 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); +} + +/** + * 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; +} diff --git a/src/utils/gcal-utils.h b/src/utils/gcal-utils.h new file mode 100644 index 00000000..bda43b95 --- /dev/null +++ b/src/utils/gcal-utils.h @@ -0,0 +1,140 @@ +/* + * gcal-window.h + * Copyright (C) 2015 Erick Pérez Castellanos + * + * gnome-calendar 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. + * + * gnome-calendar 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 . + */ + +#ifndef __GCAL_UTILS_H__ +#define __GCAL_UTILS_H__ + +#include "gcal-manager.h" +#include +#include +#include +#include + +#define ICAL_TIME_TYPE (icaltime_get_type ()) + +#define ALIGNED(x) (round (x) + 0.5) +#define MINUTES_PER_DAY 1440 +#define MAX_MINUTES (7 * MINUTES_PER_DAY) + +#define gcal_clear_datetime(dt) g_clear_pointer (dt, g_date_time_unref) +#define gcal_clear_timeout(pp) { if (pp && *pp) { g_source_remove (*pp); *pp = 0; } } + +#if !EDS_CHECK_VERSION (3, 31, 90) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ESource, g_object_unref) +#endif + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ECalComponent, g_object_unref) +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GWeatherLocation, gweather_location_unref) + +GType icaltime_get_type (void) G_GNUC_CONST; + +gint datetime_compare_date (GDateTime *dt1, + GDateTime *dt2); + +icaltimetype* datetime_to_icaltime (GDateTime *dt); + +gboolean datetime_is_date (GDateTime *dt); + +GDateTime* icaltime_to_datetime (const icaltimetype *date); + +icaltimetype* gcal_dup_icaltime (const icaltimetype *date); + +gchar* gcal_get_weekday (gint i); + +gchar* gcal_get_month_name (gint i); + +cairo_surface_t* gcal_get_surface_from_color (GdkRGBA *color, + gint size); + +cairo_surface_t* get_circle_surface_from_color (GdkRGBA *color, + gint size); + +void get_color_name_from_source (ESource *source, + GdkRGBA *out_color); + +gchar* get_desc_from_component (ECalComponent *component, + const gchar *joint_char); + +gchar* get_uuid_from_component (ESource *source, + ECalComponent *component); + +gint get_first_weekday (void); + +ECalComponent* build_component_from_details (const gchar *summary, + GDateTime *initial_date, + GDateTime *final_date); + +gint icaltime_compare_date (const icaltimetype *date1, + const icaltimetype *date2); + +gint icaltime_compare_with_current (const icaltimetype *date1, + const icaltimetype *date2, + time_t *current_time_t); + +GDateTime* get_start_of_week (icaltimetype *date); + +GDateTime* get_end_of_week (icaltimetype *date); + +gboolean is_clock_format_24h (void); + +/* code brought from evolution */ +gsize e_strftime_fix_am_pm (gchar *str, + gsize max, + const gchar *fmt, + const struct tm *tm); + +gsize e_utf8_strftime_fix_am_pm (gchar *str, + gsize max, + const gchar *fmt, + const struct tm *tm); + +void fix_popover_menu_icons (GtkPopover *popover); + + +void get_source_parent_name_color (GcalManager *manager, + ESource *source, + gchar **name, + gchar **color); + +gchar* format_utc_offset (gint64 offset); + +gint get_alarm_trigger_minutes (GcalEvent *event, + ECalComponentAlarm *alarm); + +gboolean should_change_date_for_scroll (gdouble *scroll_value, + GdkEventScroll *scroll_event); + +gboolean is_source_enabled (ESource *source); + +gboolean ask_recurrence_modification_type (GtkWidget *parent, + GcalRecurrenceModType *modtype, + ESource *source); + +gboolean is_workday (guint day); + +GList* filter_event_list_by_uid_and_modtype (GList *widgets, + GcalRecurrenceModType mod, + const gchar *uid); + +gboolean gcal_translate_child_window_position (GtkWidget *target, + GdkWindow *child_window, + gdouble src_x, + gdouble src_y, + gdouble *real_x, + gdouble *real_y); +#endif /* __GCAL_UTILS_H__ */