diff --git a/data/calendar.gresource.xml b/data/calendar.gresource.xml index ccfe72d9..1c11d23f 100644 --- a/data/calendar.gresource.xml +++ b/data/calendar.gresource.xml @@ -8,6 +8,7 @@ ui/edit-dialog.ui ui/event-widget.ui ui/month-cell.ui + ui/month-popover.ui ui/month-view.ui ui/multi-choice.ui ui/online-account-row.ui diff --git a/data/meson.build b/data/meson.build index baa50cba..f99ff608 100644 --- a/data/meson.build +++ b/data/meson.build @@ -55,6 +55,7 @@ resource_data = files( 'ui/help-overlay.ui', 'ui/menus.ui', 'ui/month-cell.ui', + 'ui/month-popover.ui', 'ui/month-view.ui', 'ui/multi-choice.ui', 'ui/quick-add-popover.ui', diff --git a/data/theme/gtk-styles.css b/data/theme/gtk-styles.css index 01e5bfb2..4cecb1ea 100644 --- a/data/theme/gtk-styles.css +++ b/data/theme/gtk-styles.css @@ -443,6 +443,10 @@ monthcell label { font-size: 0.9rem; } +monthcell label.day-label { + font-size: 1rem; +} + monthcell.out-of-month label { color: alpha(@theme_fg_color, 0.66); } @@ -461,3 +465,17 @@ monthcell button { border-bottom: none; border-right: none; } + +/* + * Month popover + */ + +monthpopover { background: transparent; } + +monthpopover > box { + margin: 4px; + border: 1px solid @borders; + box-shadow: 0px 0px 2px @wm_shadow; + padding: 12px; + background: @theme_bg_color; +} diff --git a/data/ui/month-cell.ui b/data/ui/month-cell.ui index fa4aaf2d..5af53880 100644 --- a/data/ui/month-cell.ui +++ b/data/ui/month-cell.ui @@ -11,6 +11,32 @@ True True + + + True + False + start + 6 + 6 + 0 + + + True + False + + + + + + False + gtk-missing-image + + + + + + + + true + + + + true + 12 + 0.0 + true + + + + + + + + + true + end + start + none + + + + true + window-close-symbolic + + + + + + + + + + + + false + 200 + slide-down + true + + + vertical + true + + + true + + + never + true + true + + + true + none + + + + + + + + + + + + + + + + true + none + New Event… + + + + + + + + diff --git a/data/ui/month-view.ui b/data/ui/month-view.ui index 7b2aa44c..afb6977a 100644 --- a/data/ui/month-view.ui +++ b/data/ui/month-view.ui @@ -142,65 +142,4 @@ - - - - false - bottom - - - - - - true - false - 6 - vertical - - - true - false - 6 - 6 - 6 - 0.0 - - - - - - - true - false - never - automatic - 400 - true - - - true - vertical - 2 - 2 - 2 - - - - - - - - true - true - Add Event… - - - - - - diff --git a/gnome-calendar.doap b/gnome-calendar.doap index 824bc0b5..fb85de60 100644 --- a/gnome-calendar.doap +++ b/gnome-calendar.doap @@ -13,13 +13,6 @@ C - - - Erick Pérez Castellanos - - erickpc - - Georges Basile Stavracas Neto @@ -27,11 +20,4 @@ gbsneto - - - Isaque Galdino - - igaldino - - diff --git a/meson.build b/meson.build index 7f59b6e9..e38a7ac2 100644 --- a/meson.build +++ b/meson.build @@ -170,6 +170,7 @@ libedataserverui_dep = dependency('libedataserverui-1.2', version: '>= 3.17.1') libedataserver_dep = dependency('libedataserver-1.2', version: '>= 3.17.1') libecal_dep = dependency('libecal-1.2', version: '>= 3.13.90') libsoup_dep = dependency('libsoup-2.4') +libdazzle_dep = dependency('libdazzle-1.0', version: '>= 3.26.1') glib_dep = dependency('glib-2.0', version: '>= 2.43.4') gtk_dep = dependency('gtk+-3.0', version: '>= 3.22.20') gio_dep = dependency('gio-2.0', version: '>= 2.43.4') diff --git a/src/gcal-event-widget.c b/src/gcal-event-widget.c index f4036748..5990199e 100644 --- a/src/gcal-event-widget.c +++ b/src/gcal-event-widget.c @@ -1136,6 +1136,11 @@ gcal_event_widget_sort_events (GcalEventWidget *widget1, gint diff; dt_time1 = dt_time2 = NULL; + diff = gcal_event_is_multiday (widget2->event) - gcal_event_is_multiday (widget1->event); + + if (diff != 0) + return diff; + diff = gcal_event_widget_compare_by_start_date (widget1, widget2); if (diff != 0) diff --git a/src/meson.build b/src/meson.build index b5a29e27..3b30e80b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -8,6 +8,7 @@ gcal_deps = [ libedataserver_dep, libevolution_dep, libecal_dep, + libdazzle_dep, libsoup_dep, libical_dep, glib_dep, @@ -19,6 +20,7 @@ gcal_deps = [ sources = files( 'views/gcal-month-cell.c', + 'views/gcal-month-popover.c', 'views/gcal-month-view.c', 'views/gcal-range-tree.c', 'views/gcal-view.c', diff --git a/src/views/gcal-month-cell.c b/src/views/gcal-month-cell.c index 623fd3a3..d55216db 100644 --- a/src/views/gcal-month-cell.c +++ b/src/views/gcal-month-cell.c @@ -32,6 +32,7 @@ struct _GcalMonthCell guint n_overflow; GtkLabel *day_label; + GtkWidget *header_box; GtkWidget *overflow_button; GtkWidget *overflow_label; GtkWidget *overlay; @@ -422,6 +423,7 @@ gcal_month_cell_class_init (GcalMonthCellClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/month-cell.ui"); gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, day_label); + gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, header_box); gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, overflow_button); gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, overflow_label); gtk_widget_class_bind_template_child (widget_class, GcalMonthCell, overlay); @@ -561,12 +563,12 @@ gcal_month_cell_set_overflow (GcalMonthCell *self, self->n_overflow = n_overflow; - text = g_strdup_printf (g_dngettext (GETTEXT_PACKAGE, "Other event", "Other %d events", n_overflow), n_overflow); + text = g_strdup_printf ("+%d", n_overflow); gtk_label_set_text (GTK_LABEL (self->overflow_label), text); } gint -gcal_month_cell_get_real_height (GcalMonthCell *self) +gcal_month_cell_get_content_space (GcalMonthCell *self) { GtkStyleContext *context; GtkBorder padding; @@ -579,11 +581,39 @@ gcal_month_cell_get_real_height (GcalMonthCell *self) gtk_style_context_get_border (context, gtk_style_context_get_state (context), &border); return gtk_widget_get_allocated_height (GTK_WIDGET (self)) - - gtk_widget_get_allocated_height (self->overflow_button) - + gcal_month_cell_get_header_height (self) - padding.top - padding.bottom - border.top - border.bottom; } +gint +gcal_month_cell_get_header_height (GcalMonthCell *self) +{ + GtkStyleContext *context; + GtkBorder padding; + GtkBorder border; + + g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), -1); + + context = gtk_widget_get_style_context (GTK_WIDGET (self->header_box)); + gtk_style_context_get_padding (context, gtk_style_context_get_state (context), &padding); + gtk_style_context_get_border (context, gtk_style_context_get_state (context), &border); + + return gtk_widget_get_allocated_height (self->header_box) + + gtk_widget_get_margin_top (self->header_box) + + gtk_widget_get_margin_bottom (self->header_box) + + padding.top + padding.bottom + + border.top + border.bottom; +} + +gint +gcal_month_cell_get_overflow_height (GcalMonthCell *self) +{ + g_return_val_if_fail (GCAL_IS_MONTH_CELL (self), -1); + + return gtk_widget_get_allocated_height (self->overflow_button); +} + gboolean gcal_month_cell_get_selected (GcalMonthCell *self) { diff --git a/src/views/gcal-month-cell.h b/src/views/gcal-month-cell.h index ccea347c..bdf32fec 100644 --- a/src/views/gcal-month-cell.h +++ b/src/views/gcal-month-cell.h @@ -51,7 +51,11 @@ guint gcal_month_cell_get_overflow (GcalMonthCell void gcal_month_cell_set_overflow (GcalMonthCell *self, guint n_overflow); -gint gcal_month_cell_get_real_height (GcalMonthCell *self); +gint gcal_month_cell_get_content_space (GcalMonthCell *self); + +gint gcal_month_cell_get_header_height (GcalMonthCell *self); + +gint gcal_month_cell_get_overflow_height (GcalMonthCell *self); gboolean gcal_month_cell_get_selected (GcalMonthCell *self); diff --git a/src/views/gcal-month-popover.c b/src/views/gcal-month-popover.c new file mode 100644 index 00000000..cdc103df --- /dev/null +++ b/src/views/gcal-month-popover.c @@ -0,0 +1,886 @@ +/* gcal-month-popover.c + * + * Copyright © 2017 Georges Basile Stavracas Neto + * + * 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 . + */ + +#define G_LOG_DOMAIN "GcalMonthPopover" + +#include "gcal-event-widget.h" +#include "gcal-month-popover.h" +#include "gcal-utils.h" + +#include + +#define RATIO_TO_RELATIVE 1.25 + +struct _GcalMonthPopover +{ + GtkWindow parent; + + GtkLabel *day_label; + GtkWidget *listbox; + GtkRevealer *revealer; + GtkWidget *scrolled_window; + + GtkWidget *relative_to; + GtkWindow *transient_for; + + GcalManager *manager; + + DzlAnimation *opacity_animation; + DzlAnimation *position_animation; + DzlAnimation *size_animation; + + gulong delete_event_handler; + gulong configure_event_handler; + gulong size_allocate_handler; + + /* Positioning flags - range from [0, 1] */ + gdouble x_ratio; + gdouble y_ratio; + + GDateTime *date; +}; + +static void event_activated_cb (GcalEventWidget *event_widget, + GcalMonthPopover *self); + +G_DEFINE_TYPE (GcalMonthPopover, gcal_month_popover, GTK_TYPE_WINDOW) + +enum +{ + PROP_0, + PROP_MANAGER, + PROP_RELATIVE_TO, + PROP_X, + PROP_Y, + N_PROPS +}; + +enum +{ + EVENT_ACTIVATED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; +static GParamSpec *properties [N_PROPS] = { NULL, }; + + +/* + * Auxiliary functions + */ + +static void +adjust_margin (GcalMonthPopover *self, + GdkRectangle *area) +{ + GtkStyleContext *style_context; + GtkBorder margin; + + style_context = gtk_widget_get_style_context (gtk_bin_get_child (GTK_BIN (self))); + gtk_style_context_get_margin (style_context, + gtk_style_context_get_state (style_context), + &margin); + + area->x -= margin.right; + area->y -= margin.top; + area->width += margin.right + margin.left; + area->height += margin.top + margin.bottom; +} + +static void +animate_opacity (GcalMonthPopover *self, + gdouble target) +{ + DzlAnimation *animation; + + if (self->opacity_animation) + dzl_animation_stop (self->opacity_animation); + + /* Animate the opacity */ + animation = dzl_object_animate (self, + DZL_ANIMATION_EASE_IN_OUT_CUBIC, + 250, + gtk_widget_get_frame_clock (GTK_WIDGET (self)), + "opacity", target, + NULL); + dzl_set_weak_pointer (&self->opacity_animation, animation); +} + +static void +animate_position (GcalMonthPopover *self, + gdouble x_ratio, + gdouble y_ratio) +{ + DzlAnimation *animation; + + if (self->position_animation) + dzl_animation_stop (self->position_animation); + + /* Animate the opacity */ + animation = dzl_object_animate (self, + DZL_ANIMATION_EASE_IN_OUT_CUBIC, + 200, + gtk_widget_get_frame_clock (GTK_WIDGET (self)), + "x", x_ratio, + "y", y_ratio, + NULL); + dzl_set_weak_pointer (&self->position_animation, animation); +} + +static void +animate_size (GcalMonthPopover *self, + gint target_width, + gint target_height) +{ + DzlAnimation *animation; + + if (self->size_animation) + dzl_animation_stop (self->size_animation); + + /* Animate the opacity */ + animation = dzl_object_animate (self, + DZL_ANIMATION_EASE_IN_OUT_CUBIC, + 200, + gtk_widget_get_frame_clock (GTK_WIDGET (self)), + "width-request", target_width, + "height-request", target_height, + NULL); + dzl_set_weak_pointer (&self->size_animation, animation); +} + +void +update_maximum_height (GcalMonthPopover *self) +{ + GdkWindow *window; + gint clip_height; + + clip_height = 3000; + window = gtk_widget_get_window (GTK_WIDGET (self->relative_to)); + + if (window) + { + GdkDisplay *display; + GdkMonitor *monitor; + + display = gtk_widget_get_display (GTK_WIDGET (self)); + monitor = gdk_display_get_monitor_at_window (display, window); + + if (monitor) + { + GdkRectangle workarea; + + gdk_monitor_get_workarea (monitor, &workarea); + + clip_height = workarea.height / 2; + } + } + + g_object_set (self->scrolled_window, + "max-content-height", clip_height, + NULL); +} + +static void +update_position (GcalMonthPopover *self) +{ + GtkAllocation alloc; + GtkWidget *toplevel; + GdkWindow *window; + gint diff_w; + gint diff_h; + gint x; + gint y; + + if (!gtk_widget_get_realized (GTK_WIDGET (self))) + return; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self->relative_to)); + window = gtk_widget_get_window (toplevel); + + gtk_widget_get_allocation (GTK_WIDGET (self->relative_to), &alloc); + + alloc.x = 0; + alloc.y = 0; + + gtk_widget_translate_coordinates (GTK_WIDGET (self->relative_to), toplevel, 0, 0, &alloc.x, &alloc.y); + + gdk_window_get_position (window, &x, &y); + alloc.x += x; + alloc.y += y; + + adjust_margin (self, &alloc); + + diff_w = (RATIO_TO_RELATIVE - 1) * alloc.width * (1.0 - self->x_ratio); + diff_h = (RATIO_TO_RELATIVE - 1) * alloc.height * (1.0 - self->y_ratio); + + /* Animate the margins, not the (x, y) position) */ + gtk_widget_set_margin_top (gtk_bin_get_child (GTK_BIN (self)), diff_h / 2); + + if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL) + gtk_widget_set_margin_end (gtk_bin_get_child (GTK_BIN (self)), diff_w / 2); + else + gtk_widget_set_margin_start (gtk_bin_get_child (GTK_BIN (self)), diff_w / 2); +} + +static void +reposition_popover (GcalMonthPopover *self, + gboolean animate) +{ + GtkAllocation alloc; + GtkWidget *toplevel; + GdkWindow *window; + gint diff_w; + gint diff_h; + gint x; + gint y; + + if (!gtk_widget_get_realized (GTK_WIDGET (self))) + return; + + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self->relative_to)); + window = gtk_widget_get_window (toplevel); + + gtk_widget_get_allocation (GTK_WIDGET (self->relative_to), &alloc); + + alloc.x = 0; + alloc.y = 0; + + gtk_widget_translate_coordinates (GTK_WIDGET (self->relative_to), toplevel, 0, 0, &alloc.x, &alloc.y); + + gdk_window_get_position (window, &x, &y); + alloc.x += x; + alloc.y += y; + + adjust_margin (self, &alloc); + update_maximum_height (self); + + diff_w = (RATIO_TO_RELATIVE - 1) * alloc.width; + diff_h = (RATIO_TO_RELATIVE - 1) * alloc.height; + + gtk_window_move (GTK_WINDOW (self), alloc.x - diff_w / 2, alloc.y - diff_h / 2); + + if (animate) + { + gtk_widget_set_size_request (GTK_WIDGET (self), alloc.width, alloc.height); + + animate_position (self, 1.0, 1.0); + animate_size (self, alloc.width * RATIO_TO_RELATIVE, alloc.height * RATIO_TO_RELATIVE); + } + else + { + gtk_widget_set_size_request (GTK_WIDGET (self), alloc.width * RATIO_TO_RELATIVE, alloc.height * RATIO_TO_RELATIVE); + } +} + +static void +update_event_list (GcalMonthPopover *self) +{ + g_autofree icaltimetype *start = NULL; + g_autofree icaltimetype *end = NULL; + g_autoptr (GDateTime) start_dt = NULL; + g_autoptr (GDateTime) end_dt = NULL; + g_autoptr (GList) events = NULL; + GList *l; + + gtk_container_foreach (GTK_CONTAINER (self->listbox), (GtkCallback) gtk_widget_destroy, NULL); + + if (!gtk_widget_get_realized (GTK_WIDGET (self)) || + !gtk_widget_get_visible (GTK_WIDGET (self)) || + !self->date) + { + return; + } + + start_dt = g_date_time_new_local (g_date_time_get_year (self->date), + g_date_time_get_month (self->date), + g_date_time_get_day_of_month (self->date), + 0, 0, 0); + end_dt = g_date_time_add_days (start_dt, 1); + + start = datetime_to_icaltime (start_dt); + end = datetime_to_icaltime (end_dt); + + events = gcal_manager_get_events (self->manager, start, end); + + for (l = events; l; l = l->next) + { + g_autoptr (GDateTime) event_start = NULL; + g_autoptr (GDateTime) event_end = NULL; + g_autoptr (GTimeZone) tz = NULL; + GtkWidget *event_widget; + GcalEvent *event; + + event = l->data; + + if (gcal_event_get_all_day (event)) + tz = g_time_zone_new_utc (); + else + tz = g_time_zone_new_local (); + + event_start = g_date_time_new (tz, + g_date_time_get_year (start_dt), + g_date_time_get_month (start_dt), + g_date_time_get_day_of_month (start_dt), + 0, 0, 0); + + event_end = g_date_time_add_days (event_start, 1); + + event_widget = gcal_event_widget_new (event); + gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (event_widget), event_start); + gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (event_widget), event_end); + + gtk_container_add (GTK_CONTAINER (self->listbox), event_widget); + + g_signal_connect (event_widget, + "activate", + G_CALLBACK (event_activated_cb), + self); + } + + gtk_widget_show_all (self->listbox); +} + + +/* + * GtkListBox functions + */ + +static gint +sort_func (GtkListBoxRow *a, + GtkListBoxRow *b, + gpointer user_data) +{ + GcalEventWidget *event_a; + GcalEventWidget *event_b; + + event_a = GCAL_EVENT_WIDGET (gtk_bin_get_child (GTK_BIN (a))); + event_b = GCAL_EVENT_WIDGET (gtk_bin_get_child (GTK_BIN (b))); + + return gcal_event_widget_sort_events (event_a, event_b); +} + + +/* + * Callbacks + */ + +static void +event_activated_cb (GcalEventWidget *event_widget, + GcalMonthPopover *self) +{ + g_signal_emit (self, signals[EVENT_ACTIVATED], 0, event_widget); +} + +static void +close_button_clicked_cb (GcalMonthPopover *self) +{ + gcal_month_popover_popdown (self); +} + +static void +new_event_button_clicked_cb (GtkWidget *button, + GcalMonthPopover *self) +{ + GActionMap *map; + GAction *action; + + map = G_ACTION_MAP (g_application_get_default ()); + action = g_action_map_lookup_action (map, "new"); + + g_action_activate (action, NULL); + + //gcal_month_popover_popdown (self); +} + +static void +revealer_notify_child_revealed_cb (GcalMonthPopover *self, + GParamSpec *pspec, + GtkRevealer *revealer) +{ + if (!gtk_revealer_get_reveal_child (self->revealer)) + gtk_widget_hide (GTK_WIDGET (self)); +} + +static gboolean +transient_for_configure_event_cb (GcalMonthPopover *self, + GdkEvent *event, + GtkWindow *toplevel) +{ + gtk_widget_hide (GTK_WIDGET (self)); + return FALSE; +} + +static gboolean +transient_for_delete_event_cb (GcalMonthPopover *self, + GdkEvent *event, + GtkWindow *toplevel) +{ + gtk_widget_hide (GTK_WIDGET (self)); + return FALSE; +} + +static void +transient_for_size_allocate_cb (GcalMonthPopover *self, + GtkAllocation *allocation, + GtkWindow *toplevel) +{ + reposition_popover (self, FALSE); +} + + +/* + * GtkWidget overrides + */ + +static void +gcal_month_popover_destroy (GtkWidget *widget) +{ + GcalMonthPopover *self = (GcalMonthPopover *)widget; + + if (self->transient_for) + { + g_signal_handler_disconnect (self->transient_for, self->size_allocate_handler); + g_signal_handler_disconnect (self->transient_for, self->configure_event_handler); + g_signal_handler_disconnect (self->transient_for, self->delete_event_handler); + + self->size_allocate_handler = 0; + self->configure_event_handler = 0; + self->delete_event_handler = 0; + + self->transient_for = NULL; + } + + if (self->opacity_animation) + { + dzl_animation_stop (self->opacity_animation); + dzl_clear_weak_pointer (&self->opacity_animation); + } + + if (self->position_animation) + { + dzl_animation_stop (self->position_animation); + dzl_clear_weak_pointer (&self->position_animation); + } + + if (self->size_animation) + { + dzl_animation_stop (self->size_animation); + dzl_clear_weak_pointer (&self->size_animation); + } + + gcal_month_popover_set_relative_to (self, NULL); + + GTK_WIDGET_CLASS (gcal_month_popover_parent_class)->destroy (widget); +} + +static gboolean +gcal_month_popover_focus_out_event (GtkWidget *widget, + GdkEventFocus *event_focus) +{ + gcal_month_popover_popdown (GCAL_MONTH_POPOVER (widget)); + + return GTK_WIDGET_CLASS (gcal_month_popover_parent_class)->focus_out_event (widget, event_focus); +} + +static void +gcal_month_popover_hide (GtkWidget *widget) +{ + GcalMonthPopover *self = (GcalMonthPopover *)widget; + + if (self->transient_for) + gtk_window_group_remove_window (gtk_window_get_group (self->transient_for), GTK_WINDOW (self)); + + g_signal_handler_disconnect (self->transient_for, self->delete_event_handler); + g_signal_handler_disconnect (self->transient_for, self->size_allocate_handler); + g_signal_handler_disconnect (self->transient_for, self->configure_event_handler); + + self->delete_event_handler = 0; + self->size_allocate_handler = 0; + self->configure_event_handler = 0; + + self->transient_for = NULL; + + gcal_month_popover_popdown (self); + + GTK_WIDGET_CLASS (gcal_month_popover_parent_class)->hide (widget); +} + +static void +gcal_month_popover_show (GtkWidget *widget) +{ + GcalMonthPopover *self = (GcalMonthPopover *)widget; + + if (self->relative_to) + { + GtkWidget *toplevel; + + toplevel = gtk_widget_get_ancestor (GTK_WIDGET (self->relative_to), GTK_TYPE_WINDOW); + + if (GTK_IS_WINDOW (toplevel)) + { + self->transient_for = GTK_WINDOW (toplevel); + + gtk_window_group_add_window (gtk_window_get_group (self->transient_for), GTK_WINDOW (self)); + + self->delete_event_handler = + g_signal_connect_object (toplevel, + "delete-event", + G_CALLBACK (transient_for_delete_event_cb), + self, + G_CONNECT_SWAPPED); + + self->size_allocate_handler = + g_signal_connect_object (toplevel, + "size-allocate", + G_CALLBACK (transient_for_size_allocate_cb), + self, + G_CONNECT_SWAPPED | G_CONNECT_AFTER); + + self->configure_event_handler = + g_signal_connect_object (toplevel, + "configure-event", + G_CALLBACK (transient_for_configure_event_cb), + self, + G_CONNECT_SWAPPED); + + gtk_window_set_transient_for (GTK_WINDOW (self), self->transient_for); + + reposition_popover (self, FALSE); + } + } + + GTK_WIDGET_CLASS (gcal_month_popover_parent_class)->show (widget); +} + +static void +gcal_month_popover_realize (GtkWidget *widget) +{ + GcalMonthPopover *self = (GcalMonthPopover *)widget; + GdkScreen *screen; + GdkVisual *visual; + + screen = gtk_widget_get_screen (widget); + visual = gdk_screen_get_rgba_visual (screen); + + if (visual) + gtk_widget_set_visual (widget, visual); + + GTK_WIDGET_CLASS (gcal_month_popover_parent_class)->realize (widget); + + reposition_popover (self, TRUE); +} + + +/* + * GObject overrides + */ + +static void +gcal_month_popover_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GcalMonthPopover *self = GCAL_MONTH_POPOVER (object); + + switch (prop_id) + { + case PROP_MANAGER: + g_value_set_object (value, self->manager); + break; + + case PROP_RELATIVE_TO: + g_value_set_object (value, self->relative_to); + break; + + case PROP_X: + g_value_set_double (value, self->x_ratio); + break; + + case PROP_Y: + g_value_set_double (value, self->y_ratio); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_month_popover_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GcalMonthPopover *self = GCAL_MONTH_POPOVER (object); + + switch (prop_id) + { + case PROP_MANAGER: + self->manager = g_value_dup_object (value); + break; + + case PROP_RELATIVE_TO: + gcal_month_popover_set_relative_to (self, g_value_get_object (value)); + break; + + case PROP_X: + self->x_ratio = g_value_get_double (value); + update_position (self); + break; + + + case PROP_Y: + self->y_ratio = g_value_get_double (value); + update_position (self); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gcal_month_popover_class_init (GcalMonthPopoverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = gcal_month_popover_get_property; + object_class->set_property = gcal_month_popover_set_property; + + widget_class->destroy = gcal_month_popover_destroy; + widget_class->focus_out_event = gcal_month_popover_focus_out_event; + widget_class->hide = gcal_month_popover_hide; + widget_class->show = gcal_month_popover_show; + widget_class->realize = gcal_month_popover_realize; + + properties [PROP_MANAGER] = g_param_spec_object ("manager", + "Manager", + "Manager", + GCAL_TYPE_MANAGER, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_RELATIVE_TO] = g_param_spec_object ("relative-to", + "Relative To", + "The widget to be relative to", + GTK_TYPE_WIDGET, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_X] = g_param_spec_double ("x", + "X Percent position", + "X Percent position", + 0.0, + 1.0, + 0.0, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + properties [PROP_Y] = g_param_spec_double ("y", + "Y Percent position", + "Y Percent position", + 0.0, + 1.0, + 0.0, + (G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); + + signals[EVENT_ACTIVATED] = g_signal_new ("event-activated", + GCAL_TYPE_MONTH_POPOVER, + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, + 1, + GCAL_TYPE_EVENT_WIDGET); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/month-popover.ui"); + + gtk_widget_class_bind_template_callback (widget_class, close_button_clicked_cb); + gtk_widget_class_bind_template_callback (widget_class, new_event_button_clicked_cb); + + gtk_widget_class_bind_template_child (widget_class, GcalMonthPopover, day_label); + gtk_widget_class_bind_template_child (widget_class, GcalMonthPopover, listbox); + gtk_widget_class_bind_template_child (widget_class, GcalMonthPopover, revealer); + gtk_widget_class_bind_template_child (widget_class, GcalMonthPopover, scrolled_window); + + gtk_widget_class_set_css_name (widget_class, "monthpopover"); +} + +static void +gcal_month_popover_init (GcalMonthPopover *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); + + gtk_window_set_type_hint (GTK_WINDOW (self), GDK_WINDOW_TYPE_HINT_COMBO); + gtk_window_set_decorated (GTK_WINDOW (self), FALSE); + gtk_window_set_resizable (GTK_WINDOW (self), FALSE); + gtk_window_set_skip_pager_hint (GTK_WINDOW (self), TRUE); + gtk_window_set_skip_taskbar_hint (GTK_WINDOW (self), TRUE); + + g_signal_connect_object (self->revealer, + "notify::child-revealed", + G_CALLBACK (revealer_notify_child_revealed_cb), + self, + G_CONNECT_SWAPPED); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->listbox), sort_func, NULL, NULL); +} + +GtkWidget* +gcal_month_popover_new (void) +{ + return g_object_new (GCAL_TYPE_MONTH_POPOVER, + "type", GTK_WINDOW_POPUP, + NULL); +} + +void +gcal_month_popover_set_relative_to (GcalMonthPopover *self, + GtkWidget *relative_to) +{ + g_return_if_fail (GCAL_MONTH_POPOVER (self)); + g_return_if_fail (!relative_to || GTK_IS_WIDGET (relative_to)); + + if (self->relative_to == relative_to) + return; + + if (self->relative_to) + { + g_signal_handlers_disconnect_by_func (self->relative_to, gtk_widget_destroyed, &self->relative_to); + self->relative_to = NULL; + } + + if (relative_to) + { + self->relative_to = relative_to; + g_signal_connect (self->relative_to, + "destroy", + G_CALLBACK (gtk_widget_destroyed), + &self->relative_to); + } + + g_object_notify_by_pspec (G_OBJECT (self), properties [PROP_RELATIVE_TO]); +} + +void +gcal_month_popover_popup (GcalMonthPopover *self) +{ + gint duration = 250; + + g_return_if_fail (GCAL_IS_MONTH_POPOVER (self)); + + if (self->relative_to) + { + GtkAllocation alloc; + GdkDisplay *display; + GdkMonitor *monitor; + GdkWindow *window; + gint min_height; + gint nat_height; + + display = gtk_widget_get_display (GTK_WIDGET (self->relative_to)); + window = gtk_widget_get_window (GTK_WIDGET (self->relative_to)); + monitor = gdk_display_get_monitor_at_window (display, window); + + gtk_widget_get_preferred_height (GTK_WIDGET (self), &min_height, &nat_height); + gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); + + duration = dzl_animation_calculate_duration (monitor, alloc.height, nat_height); + } + + gtk_widget_show (GTK_WIDGET (self)); + + gtk_revealer_set_transition_duration (self->revealer, duration); + gtk_revealer_set_reveal_child (self->revealer, TRUE); + + update_event_list (self); + + gtk_widget_grab_focus (GTK_WIDGET (self)); + + /* Animations */ + animate_opacity (self, 1.0); + reposition_popover (self, TRUE); +} + +void +gcal_month_popover_popdown (GcalMonthPopover *self) +{ + GtkAllocation alloc; + GdkDisplay *display; + GdkMonitor *monitor; + GdkWindow *window; + guint duration; + + g_return_if_fail (GCAL_IS_MONTH_POPOVER (self)); + + if (!gtk_widget_get_realized (GTK_WIDGET (self))) + return; + + display = gtk_widget_get_display (GTK_WIDGET (self->relative_to)); + window = gtk_widget_get_window (GTK_WIDGET (self->relative_to)); + monitor = gdk_display_get_monitor_at_window (display, window); + + gtk_widget_get_allocation (GTK_WIDGET (self), &alloc); + + duration = dzl_animation_calculate_duration (monitor, alloc.height, 0); + + gtk_revealer_set_transition_duration (self->revealer, duration); + gtk_revealer_set_reveal_child (self->revealer, FALSE); + + /* Animations */ + gtk_widget_get_allocation (GTK_WIDGET (self->relative_to), &alloc); + gtk_widget_translate_coordinates (self->relative_to, + gtk_widget_get_toplevel (self->relative_to), 0, 0, &alloc.x, &alloc.y); + adjust_margin (self, &alloc); + + animate_opacity (self, 0.0); + animate_position (self, 0.0, 0.0); + animate_size (self, alloc.width, alloc.height); +} + +GDateTime* +gcal_month_popover_get_date (GcalMonthPopover *self) +{ + g_return_val_if_fail (GCAL_IS_MONTH_POPOVER (self), NULL); + + return self->date; +} + +void +gcal_month_popover_set_date (GcalMonthPopover *self, + GDateTime *date) +{ + g_return_if_fail (GCAL_IS_MONTH_POPOVER (self)); + + if (date && self->date && datetime_compare_date (self->date, date) == 0) + return; + + gcal_clear_datetime (&self->date); + + if (date) + { + g_autofree gchar *label; + + self->date = g_date_time_ref (date); + + label = g_strdup_printf ("%d", g_date_time_get_day_of_month (date)); + gtk_label_set_label (self->day_label, label); + } + + update_event_list (self); +} diff --git a/src/views/gcal-month-popover.h b/src/views/gcal-month-popover.h new file mode 100644 index 00000000..83c7b45d --- /dev/null +++ b/src/views/gcal-month-popover.h @@ -0,0 +1,50 @@ +/* gcal-month-popover.h + * + * Copyright © 2017 Georges Basile Stavracas Neto + * + * 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 . + */ + +#ifndef GCAL_MONTH_POPOVER_H +#define GCAL_MONTH_POPOVER_H + +#include "gcal-manager.h" + +#include + +G_BEGIN_DECLS + +#define GCAL_TYPE_MONTH_POPOVER (gcal_month_popover_get_type()) + +G_DECLARE_FINAL_TYPE (GcalMonthPopover, gcal_month_popover, GCAL, MONTH_POPOVER, GtkWindow) + +GtkWidget* gcal_month_popover_new (void); + + +void gcal_month_popover_set_relative_to (GcalMonthPopover *self, + GtkWidget *relative_to); + +void gcal_month_popover_popup (GcalMonthPopover *self); + +void gcal_month_popover_popdown (GcalMonthPopover *self); + +GDateTime* gcal_month_popover_get_date (GcalMonthPopover *self); + +void gcal_month_popover_set_date (GcalMonthPopover *self, + GDateTime *date); + +G_END_DECLS + +#endif /* GCAL_MONTH_POPOVER_H */ + diff --git a/src/views/gcal-month-view.c b/src/views/gcal-month-view.c index a1485159..b679ca51 100644 --- a/src/views/gcal-month-view.c +++ b/src/views/gcal-month-view.c @@ -1,12 +1,11 @@ -/* -*- mode: c; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ -/* - * gcal-month-view.c +/* gcal-month-view.c * - * Copyright (C) 2015 - Erick Pérez Castellanos + * Copyright © 2015 - Erick Pérez Castellanos + * 2017 Georges Basile Stavracas Neto * - * This program is free software; you can redistribute it and/or modify + * 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 + * 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, @@ -28,6 +27,7 @@ #include "gcal-debug.h" #include "gcal-month-cell.h" +#include "gcal-month-popover.h" #include "gcal-month-view.h" #include "gcal-utils.h" #include "gcal-view.h" @@ -42,9 +42,7 @@ struct _GcalMonthView { GtkContainer parent; - GtkWidget *events_box; - GtkWidget *overflow_popover; - GtkWidget *popover_title; + GcalMonthPopover *overflow_popover; GdkWindow *event_window; @@ -89,11 +87,6 @@ struct _GcalMonthView */ GHashTable *overflow_cells; - /* - * Set containing the master widgets hidden for delete; - */ - GHashTable *hidden_as_overflow; - /* * the cell on which its drawn the first day of the month, in the first row, 0 for the first * cell, 1 for the second, and so on, this takes first_weekday into account already. @@ -127,15 +120,14 @@ struct _GcalMonthView /* Storage for the accumulated scrolling */ gdouble scroll_value; - guint update_grid_id; /* property */ icaltimetype *date; GcalManager *manager; -}; -#define MIRROR(val,start,end) (start + (val / end) * end + (end - val % end)) + gboolean pending_event_allocation; +}; static void gcal_view_interface_init (GcalViewInterface *iface); @@ -171,16 +163,6 @@ static guint signals[NUM_SIGNALS] = { 0, }; * Auxiliary functions */ -static inline gint -real_cell (gint cell, - gboolean rtl) -{ - if (cell < 0) - return cell; - - return rtl ? MIRROR (cell, 0, 7) - 1 : cell; -} - static inline void cancel_selection (GcalMonthView *self) { @@ -189,11 +171,12 @@ cancel_selection (GcalMonthView *self) } static void -event_activated (GcalEventWidget *widget, - GcalMonthView *self) +event_activated (GcalMonthView *self, + GcalEventWidget *widget, + gpointer unused) { cancel_selection (self); - gtk_widget_hide (self->overflow_popover); + gcal_month_popover_popdown (self->overflow_popover); g_signal_emit (self, signals[EVENT_ACTIVATED], 0, widget); } @@ -205,7 +188,7 @@ setup_child_widget (GcalMonthView *self, if (!gtk_widget_get_parent (widget)) gtk_widget_set_parent (widget, GTK_WIDGET (self)); - g_signal_connect (widget, "activate", G_CALLBACK (event_activated), self); + g_signal_connect_swapped (widget, "activate", G_CALLBACK (event_activated), self); g_signal_connect_swapped (widget, "hide", G_CALLBACK (gtk_widget_queue_resize), self); g_signal_connect_swapped (widget, "show", G_CALLBACK (gtk_widget_queue_resize), self); } @@ -306,22 +289,30 @@ get_month_cell_at_position (GcalMonthView *self, return NULL; } -static gboolean -get_widget_parts (gint first_cell, - gint last_cell, - gint natural_height, - gdouble *vertical_cell_space, - gdouble *size_left, - GArray *cells, - GArray *lengths) +static void +calculate_multiday_event_blocks (GcalMonthView *self, + gint first_cell, + gint last_cell, + gint natural_height, + gdouble *vertical_cell_space, + gdouble *size_left, + GArray *cells, + GArray *lengths, + GArray *length_visible, + gint *events_at_day, + gint *allocated_events_at_day) { + gboolean was_visible; gdouble old_y; gdouble y; gint current_part_length; gint i; + GCAL_ENTRY; + old_y = -1.0; current_part_length = 0; + was_visible = FALSE; if (last_cell < first_cell) { @@ -330,28 +321,58 @@ get_widget_parts (gint first_cell, first_cell = swap; } + GCAL_TRACE_MSG ("Calculating event blocks from cell %d to cell %d", first_cell, last_cell); + for (i = first_cell; i <= last_cell; i++) { - if (size_left[i] < natural_height) - return FALSE; + gboolean visible_at_range; + gboolean different_row; + gboolean will_overflow; + gdouble real_height; + gint remaining_events; + + real_height = size_left[i]; + + /* Count this event at this cell */ + different_row = i / 7 != (i - 1) / 7; + remaining_events = events_at_day[i] - allocated_events_at_day[i]; + will_overflow = remaining_events * natural_height > real_height; + + if (will_overflow) + real_height -= gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (self->month_cell[i / 7][i % 7])); + + visible_at_range = real_height >= natural_height; + + if (visible_at_range) + allocated_events_at_day[i]++; y = vertical_cell_space[i] - size_left[i]; - if (old_y == -1.0 || y != old_y) + /* At the first iteration, make was_visible and visible_at_range equal */ + if (i == first_cell) + was_visible = visible_at_range; + + if (old_y == -1.0 || y != old_y || different_row || was_visible != visible_at_range) { + GCAL_TRACE_MSG ("Breaking event at cell %d (previous section was %d)", i, current_part_length); + current_part_length = 1; + old_y = y; + g_array_append_val (cells, i); g_array_append_val (lengths, current_part_length); - old_y = y; + g_array_append_val (length_visible, visible_at_range); } else { current_part_length++; g_array_index (lengths, gint, lengths->len - 1) = current_part_length; } + + was_visible = visible_at_range; } - return TRUE; + GCAL_EXIT; } static void @@ -359,78 +380,15 @@ show_overflow_popover_cb (GcalMonthCell *cell, GtkWidget *button, GcalMonthView *self) { - g_autofree gchar *label_title = NULL; - GtkWidget *child_widget; - GDateTime *dt; - GList *l; - gint day, cell_nr; + GcalMonthPopover *popover; - dt = gcal_month_cell_get_date (cell); - day = g_date_time_get_day_of_month (dt); - cell_nr = day + self->days_delay - 1; - - if (!g_hash_table_contains (self->overflow_cells, GINT_TO_POINTER (cell_nr))) - { - g_critical ("No overflow events for day %d. Something is wrong.", cell_nr); - return; - } - - /* Popover title */ - label_title = g_date_time_format (dt, _("%B %d")); - gtk_label_set_text (GTK_LABEL (self->popover_title), label_title); - - /* Clean all the widgets */ - gtk_container_foreach (GTK_CONTAINER (self->events_box), (GtkCallback) gtk_widget_destroy, NULL); - - l = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (cell_nr)); - - /* Setup the start & end dates of the events as the begin & end of day */ - for (; l != NULL; l = g_list_next (l)) - { - g_autoptr (GDateTime) current_date = NULL; - g_autoptr (GDateTime) dt_start = NULL; - g_autoptr (GDateTime) dt_end = NULL; - g_autoptr (GTimeZone) tz = NULL; - GtkWidget *cloned_event; - GcalEvent *event; - - event = gcal_event_widget_get_event (l->data); - - if (gcal_event_get_all_day (event)) - tz = g_time_zone_new_utc (); - else - tz = g_time_zone_new_local (); - - current_date = icaltime_to_datetime (self->date); - dt_start = g_date_time_new (tz, - g_date_time_get_year (current_date), - g_date_time_get_month (current_date), - day, 0, 0, 0); - - dt_end = g_date_time_add_days (dt_start, 1); - - cloned_event = gcal_event_widget_clone (l->data); - gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (cloned_event), dt_start); - gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (cloned_event), dt_end); - - gtk_container_add (GTK_CONTAINER (self->events_box), cloned_event); - setup_child_widget (self, cloned_event); - } - - gtk_popover_set_relative_to (GTK_POPOVER (self->overflow_popover), button); - - /* sizing hack */ - child_widget = gtk_bin_get_child (GTK_BIN (self->overflow_popover)); - gtk_widget_set_size_request (child_widget, 200, -1); - gtk_widget_show_all (child_widget); - - g_object_set_data (G_OBJECT (self->overflow_popover), - "selected-day", - GINT_TO_POINTER (day)); - - gtk_popover_popup (GTK_POPOVER (self->overflow_popover)); + popover = GCAL_MONTH_POPOVER (self->overflow_popover); cancel_selection (self); + + gcal_month_popover_set_relative_to (popover, GTK_WIDGET (cell)); + gcal_month_popover_set_date (popover, gcal_month_cell_get_date (cell)); + gcal_month_popover_popup (popover); } static void @@ -498,6 +456,7 @@ update_month_cells (GcalMonthView *self) /* Cell date */ cell_date = g_date_time_add_days (dt, row * 7 + col - self->days_delay); + gcal_month_cell_set_date (cell, cell_date); /* Different month */ @@ -718,16 +677,6 @@ gcal_month_view_key_press (GtkWidget *widget, return GDK_EVENT_STOP; } -static gboolean -cancel_dnd_from_overflow_popover (GtkPopover *popover) -{ - GCAL_ENTRY; - - gtk_popover_popdown (popover); - - GCAL_RETURN (FALSE); -} - static gboolean gcal_month_view_scroll_event (GtkWidget *widget, GdkEventScroll *scroll_event) @@ -757,12 +706,12 @@ add_new_event_button_cb (GtkWidget *button, gpointer user_data) { GcalMonthView *self; - gint day; GDateTime *start_date; + gint day; self = GCAL_MONTH_VIEW (user_data); - gtk_widget_hide (self->overflow_popover); + gcal_month_popover_popdown (self->overflow_popover); day = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (self->overflow_popover), "selected-day")); start_date = g_date_time_new_local (self->date->year, self->date->month, day, 0, 0, 0); @@ -880,11 +829,13 @@ gcal_month_view_component_added (ECalDataModelSubscriber *subscriber, ECalClient *client, ECalComponent *comp) { + GcalMonthView *self; GtkWidget *event_widget; GcalEvent *event; GError *error; error = NULL; + self = GCAL_MONTH_VIEW (subscriber); event = gcal_event_new (e_client_get_source (E_CLIENT (client)), comp, &error); if (error) @@ -900,6 +851,8 @@ gcal_month_view_component_added (ECalDataModelSubscriber *subscriber, gtk_widget_show (event_widget); gtk_container_add (GTK_CONTAINER (subscriber), event_widget); + self->pending_event_allocation = TRUE; + g_clear_object (&event); } @@ -944,6 +897,8 @@ gcal_month_view_component_modified (ECalDataModelSubscriber *subscriber, gtk_widget_destroy (new_widget); } + self->pending_event_allocation = TRUE; + g_clear_object (&event); } @@ -978,6 +933,8 @@ gcal_month_view_component_removed (ECalDataModelSubscriber *subscriber, } gtk_widget_destroy (l->data); + + self->pending_event_allocation = TRUE; } static void @@ -1176,8 +1133,6 @@ gcal_month_view_remove (GtkContainer *container, g_hash_table_remove (self->children, uuid); else g_hash_table_replace (self->children, g_strdup (uuid), l); - - g_hash_table_remove (self->hidden_as_overflow, uuid); } out: @@ -1296,7 +1251,6 @@ gcal_month_view_finalize (GObject *object) g_clear_pointer (&self->children, g_hash_table_destroy); g_clear_pointer (&self->single_cell_children, g_hash_table_destroy); g_clear_pointer (&self->overflow_cells, g_hash_table_destroy); - g_clear_pointer (&self->hidden_as_overflow, g_hash_table_destroy); g_clear_pointer (&self->multi_cell_children, g_list_free); g_clear_object (&self->manager); @@ -1398,16 +1352,19 @@ remove_cell_border_and_padding (GtkWidget *cell, GtkStyleContext *cell_context; GtkBorder cell_padding; GtkBorder cell_border; + gint header_height; cell_context = gtk_widget_get_style_context (cell); gtk_style_context_get_border (cell_context, gtk_style_context_get_state (cell_context), &cell_border); gtk_style_context_get_padding (cell_context, gtk_style_context_get_state (cell_context), &cell_padding); + header_height = gcal_month_cell_get_header_height (GCAL_MONTH_CELL (cell)); + if (x) *x += cell_border.left + cell_padding.left; if (y) - *y += cell_border.top + cell_padding.top; + *y += cell_border.top + cell_padding.top + header_height; if (width) { @@ -1416,6 +1373,92 @@ remove_cell_border_and_padding (GtkWidget *cell, } } +static void +count_events_per_day (GcalMonthView *self, + gint *events_per_day) +{ + GHashTableIter iter; + gpointer key; + GList *children; + GList *l; + + /* Multiday events */ + for (l = self->multi_cell_children; l; l = l->next) + { + g_autoptr (GDateTime) start_date = NULL; + g_autoptr (GDateTime) end_date = NULL; + GcalEvent *event; + gboolean all_day; + gint first_cell; + gint last_cell; + gint i; + + event = gcal_event_widget_get_event (l->data); + all_day = gcal_event_get_all_day (event); + + /* Start date */ + first_cell = 1; + start_date = gcal_event_get_date_start (event); + start_date = all_day ? g_date_time_ref (start_date) : g_date_time_to_local (start_date); + + if (g_date_time_get_year (start_date) == self->date->year && + g_date_time_get_month (start_date) == self->date->month) + { + first_cell = g_date_time_get_day_of_month (start_date); + } + + first_cell += self->days_delay - 1; + + /* + * The logic for the end date is the same, except that we have to check + * if the event is all day or not. + */ + last_cell = icaltime_days_in_month (self->date->month, self->date->year); + end_date = gcal_event_get_date_end (event); + end_date = all_day ? g_date_time_ref (end_date) : g_date_time_to_local (end_date); + + if (g_date_time_get_year (start_date) == self->date->year && + g_date_time_get_month (end_date) == self->date->month) + { + last_cell = g_date_time_get_day_of_month (end_date); + + /* If the event is all day, we have to subtract 1 to find the the real date */ + if (all_day) + last_cell--; + } + + last_cell += self->days_delay - 1; + + for (i = first_cell; i <= last_cell; i++) + events_per_day[i]++; + } + + /* Single day events */ + g_hash_table_iter_init (&iter, self->single_cell_children); + while (g_hash_table_iter_next (&iter, &key, (gpointer*) &children)) + { + gint cell; + + cell = GPOINTER_TO_INT (key) + self->days_delay - 1; + + for (l = children; l; l = l->next) + events_per_day[cell]++; + } +} + +static gdouble +get_real_event_widget_height (GtkWidget *widget) +{ + gint min_height; + + gtk_widget_get_preferred_height (widget, &min_height, NULL); + + min_height += gtk_widget_get_margin_top (widget); + min_height += gtk_widget_get_margin_bottom (widget); + + return min_height; +} + static void gcal_month_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) @@ -1424,6 +1467,7 @@ gcal_month_view_size_allocate (GtkWidget *widget, GHashTableIter iter; GtkAllocation child_allocation; GtkAllocation cell_alloc; + GtkAllocation old_alloc; GcalMonthView *self; GtkWidget *child_widget; GtkWidget *month_cell; @@ -1433,28 +1477,20 @@ gcal_month_view_size_allocate (GtkWidget *widget, const gchar *uuid; gdouble vertical_cell_space [42]; gdouble size_left [42]; + gint allocated_events_at_day [42] = { 0, }; + gint events_at_day [42] = { 0, }; gdouble pos_x, pos_y; gint minimum_height; gint header_height; gint grid_height; - gint i, j, sw; + gint i, j; - self = gcal_month_view_get_instance_private (GCAL_MONTH_VIEW (widget)); + GCAL_ENTRY; - /* Remove every widget' parts, but the master widget */ - widgets = g_hash_table_get_values (self->children); - - for (aux = widgets; aux != NULL; aux = g_list_next (aux)) - l = g_list_concat (l, g_list_copy (g_list_next (aux->data))); - - g_list_free (widgets); - - g_list_free_full (l, (GDestroyNotify) gtk_widget_destroy); - - /* Clean overflow information */ - g_hash_table_remove_all (self->overflow_cells); + self = GCAL_MONTH_VIEW (widget); /* Allocate the widget */ + gtk_widget_get_allocation (widget, &old_alloc); gtk_widget_set_allocation (widget, allocation); if (gtk_widget_get_realized (widget)) @@ -1480,29 +1516,60 @@ gcal_month_view_size_allocate (GtkWidget *widget, gtk_widget_size_allocate (self->grid, &child_allocation); + /* + * At this point, the internal widgets are already allocated and happy. Thus, if no new event + * was added and the size is the same, we don't need to create and add new event widgets. This + * breaks the indirect allocation cycle. + */ + if (!self->pending_event_allocation && + allocation->width == old_alloc.width && + allocation->height == old_alloc.height) + { + GCAL_TRACE_MSG ("Events didn't change, stopping here"); + GCAL_GOTO (out); + } + + /* Remove every widget' parts, but the master widget */ + widgets = g_hash_table_get_values (self->children); + + for (aux = widgets; aux != NULL; aux = g_list_next (aux)) + l = g_list_concat (l, g_list_copy (g_list_next (aux->data))); + + g_list_free (widgets); + + g_list_free_full (l, (GDestroyNotify) gtk_widget_destroy); + + /* Clean overflow information */ + g_hash_table_remove_all (self->overflow_cells); + /* Event widgets */ + count_events_per_day (self, events_at_day); + for (i = 0; i < 42; i++) { - gint h = gcal_month_cell_get_real_height (GCAL_MONTH_CELL (self->month_cell[i / 7][i % 7])); + gint h = gcal_month_cell_get_content_space (GCAL_MONTH_CELL (self->month_cell[i / 7][i % 7])); vertical_cell_space[i] = h; size_left[i] = h; } - sw = 1 - 2 * self->k; - /* * Allocate multidays events before single day events, as they have a * higher priority. Multiday events have a master event, and clones * to represent the multiple sections of the event. */ - for (l = self->multi_cell_children; l != NULL; l = g_list_next (l)) + for (l = self->multi_cell_children; l; l = g_list_next (l)) { - GDateTime *date; + g_autoptr (GDateTime) start_date = NULL; + g_autoptr (GDateTime) end_date = NULL; + g_autoptr (GArray) visible_at_length = NULL; + g_autoptr (GArray) lengths = NULL; + g_autoptr (GArray) blocks = NULL; GcalEvent *event; - GArray *cells, *lengths; - gboolean visible, all_day; - gint first_cell, last_cell, first_row, last_row, start, end; + gboolean all_day; + gint event_block; + gint first_cell; + gint last_cell; child_widget = (GtkWidget*) l->data; event = gcal_event_widget_get_event (l->data); @@ -1510,189 +1577,189 @@ gcal_month_view_size_allocate (GtkWidget *widget, all_day = gcal_event_get_all_day (event); child_context = gtk_widget_get_style_context (l->data); - if (!gtk_widget_is_visible (child_widget) && !g_hash_table_contains (self->hidden_as_overflow, uuid)) - continue; - - gtk_widget_show (child_widget); - gtk_widget_get_preferred_height (child_widget, &minimum_height, NULL); - /* * If the month of the event's start date is equal to the month * we're visualizing, the first cell is the event's day of the * month. Otherwise, the first cell is the 1st day of the month. */ - j = 1; - date = gcal_event_get_date_start (event); - date = all_day ? g_date_time_ref (date) : g_date_time_to_local (date); + first_cell = 1; + start_date = gcal_event_get_date_start (event); + start_date = all_day ? g_date_time_ref (start_date) : g_date_time_to_local (start_date); - if (g_date_time_get_year (date) == self->date->year && - g_date_time_get_month (date) == self->date->month) + if (g_date_time_get_year (start_date) == self->date->year && + g_date_time_get_month (start_date) == self->date->month) { - j = g_date_time_get_day_of_month (date); + first_cell = g_date_time_get_day_of_month (start_date); } - j += self->days_delay; - - g_clear_pointer (&date, g_date_time_unref); - - /* - * Calculate the first cell position according to the locale - * and the start date. - */ - first_cell = 7 * ((j - 1) / 7)+ 6 * self->k + sw * ((j - 1) % 7); + first_cell += self->days_delay - 1; /* * The logic for the end date is the same, except that we have to check * if the event is all day or not. */ - j = icaltime_days_in_month (self->date->month, self->date->year); - date = gcal_event_get_date_end (event); - date = all_day ? g_date_time_ref (date) : g_date_time_to_local (date); + last_cell = icaltime_days_in_month (self->date->month, self->date->year); + end_date = gcal_event_get_date_end (event); + end_date = all_day ? g_date_time_ref (end_date) : g_date_time_to_local (end_date); - if (g_date_time_get_month (date) == self->date->month) + if (g_date_time_get_month (end_date) == self->date->month) { - j = g_date_time_get_day_of_month (date); + last_cell = g_date_time_get_day_of_month (end_date); /* If the event is all day, we have to subtract 1 to find the the real date */ if (all_day) - j--; + last_cell--; } - j += self->days_delay; - - g_clear_pointer (&date, g_date_time_unref); - - last_cell = 7 * ((j - 1) / 7)+ 6 * self->k + sw * ((j - 1) % 7); + last_cell += self->days_delay - 1; /* * Now that we found the start & end dates, we have to find out * whether the event is visible or not. */ - first_row = first_cell / 7; - last_row = last_cell / 7; - visible = TRUE; - cells = g_array_sized_new (TRUE, TRUE, sizeof (gint), 16); + + GCAL_TRACE_MSG ("Positioning '%s' (multiday) from %d to %d", gcal_event_get_summary (event), first_cell, last_cell); + + blocks = g_array_sized_new (TRUE, TRUE, sizeof (gint), 16); lengths = g_array_sized_new (TRUE, TRUE, sizeof (gint), 16); + visible_at_length = g_array_sized_new (TRUE, TRUE, sizeof (gboolean), 16); - for (i = first_row; i <= last_row && visible; i++) + /* Position and allocate the child widget */ + minimum_height = get_real_event_widget_height (child_widget); + + gtk_style_context_get_margin (child_context, + gtk_style_context_get_state (child_context), + &margin); + + calculate_multiday_event_blocks (self, + first_cell, last_cell, + minimum_height + margin.top + margin.bottom, + vertical_cell_space, + size_left, + blocks, + lengths, + visible_at_length, + events_at_day, + allocated_events_at_day); + + for (event_block = 0; event_block < blocks->len; event_block++) { - start = i * 7 + self->k * 6; - end = i * 7 + (1 - self->k) * 6; + g_autoptr (GDateTime) dt_start = NULL; + g_autoptr (GDateTime) dt_end = NULL; + gboolean visible; + gdouble width; + gint last_block_cell; + gint length; + gint cell; + gint day; - if (i == first_row) - start = first_cell; - if (i == last_row) - end = last_cell; + visible = g_array_index (visible_at_length, gboolean, event_block); + length = g_array_index (lengths, gint, event_block); + cell = g_array_index (blocks, gint, event_block); + last_block_cell = cell + length - 1; + day = cell - self->days_delay + 1; - visible = get_widget_parts (start, end, minimum_height, vertical_cell_space, size_left, cells, lengths); - } - - if (visible) - { - for (i = 0; i < cells->len; i++) + /* + * Setup a new event widget when the cell index changes. This happens when + * the event has a different y position, or when the row changed. + */ + if (event_block != 0) { - GDateTime *dt_start, *dt_end; - gdouble width; - gint cell_idx = g_array_index (cells, gint, i); - gint length = g_array_index (lengths, gint, i); - gint row = cell_idx / 7; - gint column = cell_idx % 7; - gint day = cell_idx - self->days_delay + 1; + child_widget = gcal_event_widget_clone (GCAL_EVENT_WIDGET (child_widget)); + gtk_widget_show (child_widget); - /* Day number is calculated differently on RTL languages */ - if (self->k) - day = 7 * row + MIRROR (cell_idx % 7, 0, 7) - self->days_delay - length + 1; + setup_child_widget (self, child_widget); - if (i != 0) - { - child_widget = gcal_event_widget_clone (GCAL_EVENT_WIDGET (child_widget)); + child_context = gtk_widget_get_style_context (child_widget); - setup_child_widget (self, child_widget); - gtk_widget_show (child_widget); - - child_context = gtk_widget_get_style_context (child_widget); - - aux = g_hash_table_lookup (self->children, uuid); - aux = g_list_append (aux, child_widget); - } - - /* Retrieve the cell widget */ - if (self->k) - month_cell = self->month_cell[row][6 - column]; - else - month_cell = self->month_cell[row][column]; - - gtk_widget_get_allocation (month_cell, &cell_alloc); - - gtk_widget_get_preferred_height (child_widget, &minimum_height, NULL); - - /* - * Setup the widget's start date as the first day of the row, - * and the widget's end date as the last day of the row. We don't - * have to worry about the dates, since GcalEventWidget performs - * some checks and only applies the dates when it's valid. - */ - dt_start = g_date_time_new (gcal_event_get_timezone (event), - self->date->year, - self->date->month, - day, - 0, 0, 0); - - dt_end = g_date_time_add_days (dt_start, length); - - gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (child_widget), dt_start); - gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (child_widget), dt_end); - - /* Position and allocate the child widget */ - gtk_style_context_get_margin (gtk_widget_get_style_context (child_widget), - gtk_style_context_get_state (child_context), - &margin); - - - pos_x = cell_alloc.x + margin.left; - pos_y = cell_alloc.y + margin.top; - width = cell_alloc.width * length; - - remove_cell_border_and_padding (month_cell, &pos_x, &pos_y, &width); - - child_allocation.x = pos_x; - child_allocation.y = pos_y + vertical_cell_space[cell_idx] - size_left[cell_idx]; - child_allocation.width = width - (margin.left + margin.right); - child_allocation.height = minimum_height; - - gtk_widget_size_allocate (child_widget, &child_allocation); - g_hash_table_remove (self->hidden_as_overflow, uuid); - - /* update size_left */ - for (j = 0; j < length; j++) - { - size_left[cell_idx + j] -= minimum_height; - size_left[cell_idx + j] -= margin.top + margin.bottom; - } - - g_clear_pointer (&dt_start, g_date_time_unref); - g_clear_pointer (&dt_end, g_date_time_unref); - } - } - else - { - gtk_widget_hide (child_widget); - g_hash_table_add (self->hidden_as_overflow, g_strdup (uuid)); - - for (i = first_cell; i <= last_cell; i++) - { - aux = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (i)); + aux = g_hash_table_lookup (self->children, uuid); aux = g_list_append (aux, child_widget); + } - if (g_list_length (aux) == 1) - g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (i), aux); - else - g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (i), g_list_copy (aux)); + /* No space left, add to the overflow and continue */ + if (!visible) + { + gint idx; + + gtk_widget_set_child_visible (child_widget, FALSE); + + for (idx = cell; idx < cell + length; idx++) + { + aux = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (idx)); + aux = g_list_append (aux, child_widget); + + if (g_list_length (aux) == 1) + g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (idx), aux); + else + g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (idx), g_list_copy (aux)); + } + + continue; + } + + + /* + * Retrieve the cell widget. On RTL languages, we use the last month cell as the starting + * point. + */ + if (self->k) + month_cell = self->month_cell[last_block_cell / 7][last_block_cell % 7]; + else + month_cell = self->month_cell[cell / 7][cell % 7]; + + gtk_widget_get_allocation (month_cell, &cell_alloc); + + gtk_widget_set_child_visible (child_widget, TRUE); + + /* + * Setup the widget's start date as the first day of the row, + * and the widget's end date as the last day of the row. We don't + * have to worry about the dates, since GcalEventWidget performs + * some checks and only applies the dates when it's valid. + */ + dt_start = g_date_time_new (gcal_event_get_timezone (event), + self->date->year, + self->date->month, + day, + 0, 0, 0); + + dt_end = g_date_time_add_days (dt_start, length); + + gcal_event_widget_set_date_start (GCAL_EVENT_WIDGET (child_widget), dt_start); + gcal_event_widget_set_date_end (GCAL_EVENT_WIDGET (child_widget), dt_end); + + /* Position and allocate the child widget */ + gtk_style_context_get_margin (gtk_widget_get_style_context (child_widget), + gtk_style_context_get_state (child_context), + &margin); + + + pos_x = cell_alloc.x + margin.left; + pos_y = cell_alloc.y + margin.top; + width = cell_alloc.width * length; + + remove_cell_border_and_padding (month_cell, &pos_x, &pos_y, &width); + + /* + * We can only get the minimum height after making all these calculations, + * otherwise GTK complains about allocating without calling get_preferred_height. + */ + minimum_height = get_real_event_widget_height (child_widget); + + child_allocation.x = pos_x; + child_allocation.y = pos_y + vertical_cell_space[cell] - size_left[cell]; + child_allocation.width = width - (margin.left + margin.right); + child_allocation.height = minimum_height; + + gtk_widget_size_allocate (child_widget, &child_allocation); + + /* update size_left */ + for (j = 0; j < length; j++) + { + size_left[cell + j] -= minimum_height; + size_left[cell + j] -= margin.top + margin.bottom; } } - - g_array_free (cells, TRUE); - g_array_free (lengths, TRUE); } /* @@ -1704,72 +1771,84 @@ gcal_month_view_size_allocate (GtkWidget *widget, while (g_hash_table_iter_next (&iter, &key, &value)) { - j = GPOINTER_TO_INT (key); - j += self->days_delay; - i = 7 * ((j - 1) / 7) + 6 * self->k + sw * ((j - 1) % 7); + gboolean will_overflow; + gint cell; + gint day; - if (self->k) - month_cell = self->month_cell[i / 7][6 - i % 7]; - else - month_cell = self->month_cell[i / 7][i % 7]; + day = GPOINTER_TO_INT (key); + cell = day + self->days_delay - 1; + month_cell = self->month_cell[cell / 7][cell % 7]; gtk_widget_get_allocation (month_cell, &cell_alloc); l = (GList*) value; - for (aux = l; aux != NULL; aux = g_list_next (aux)) + for (aux = l; aux; aux = g_list_next (aux)) { GcalEvent *event; + gdouble real_height; gdouble width; + gint remaining_events; child_widget = aux->data; + child_context = gtk_widget_get_style_context (child_widget); event = gcal_event_widget_get_event (aux->data); uuid = gcal_event_get_uid (event); - if (!gtk_widget_is_visible (child_widget) && !g_hash_table_contains (self->hidden_as_overflow, uuid)) - continue; + gtk_style_context_get_margin (child_context, gtk_style_context_get_state (child_context), &margin); + minimum_height = get_real_event_widget_height (child_widget) + margin.top + margin.bottom; - gtk_widget_show (child_widget); - gtk_widget_get_preferred_height (child_widget, &minimum_height, NULL); + /* Check for overflow */ + remaining_events = events_at_day[cell] - allocated_events_at_day[cell]; + will_overflow = remaining_events * minimum_height >= size_left[cell]; + real_height = size_left[cell]; - if (size_left[i] > minimum_height) + if (will_overflow) + real_height -= gcal_month_cell_get_overflow_height (GCAL_MONTH_CELL (month_cell)); + + /* No space left, add to the overflow and continue */ + if (real_height < minimum_height) { - child_context = gtk_widget_get_style_context (child_widget); + gtk_widget_set_child_visible (child_widget, FALSE); - gtk_style_context_get_margin (child_context, gtk_style_context_get_state (child_context), &margin); - - pos_x = cell_alloc.x + margin.left; - pos_y = cell_alloc.y + margin.top; - width = cell_alloc.width; - - remove_cell_border_and_padding (month_cell, &pos_x, &pos_y, &width); - - child_allocation.x = pos_x; - child_allocation.y = pos_y + vertical_cell_space[i] - size_left[i]; - child_allocation.width = width - (margin.left + margin.right); - child_allocation.height = minimum_height; - gtk_widget_show (child_widget); - gtk_widget_size_allocate (child_widget, &child_allocation); - g_hash_table_remove (self->hidden_as_overflow, uuid); - - size_left[i] -= minimum_height + margin.top + margin.bottom; - } - else - { - gtk_widget_hide (child_widget); - g_hash_table_add (self->hidden_as_overflow, g_strdup (uuid)); - - l = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (i)); + l = g_hash_table_lookup (self->overflow_cells, GINT_TO_POINTER (cell)); l = g_list_append (l, child_widget); if (g_list_length (l) == 1) - g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (i), l); + g_hash_table_insert (self->overflow_cells, GINT_TO_POINTER (cell), l); else - g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (i), g_list_copy (l)); + g_hash_table_replace (self->overflow_cells, GINT_TO_POINTER (cell), g_list_copy (l)); + + continue; } + + allocated_events_at_day[cell]++; + + gtk_widget_set_child_visible (child_widget, TRUE); + + pos_x = cell_alloc.x + margin.left; + pos_y = cell_alloc.y + margin.top; + width = cell_alloc.width; + + remove_cell_border_and_padding (month_cell, &pos_x, &pos_y, &width); + + child_allocation.x = pos_x; + child_allocation.y = pos_y + vertical_cell_space[cell] - size_left[cell]; + child_allocation.width = width - (margin.left + margin.right); + child_allocation.height = minimum_height; + + gtk_widget_set_child_visible (child_widget, TRUE); + gtk_widget_size_allocate (child_widget, &child_allocation); + + size_left[cell] -= minimum_height + margin.top + margin.bottom; } } +out: queue_update_month_cells (self); + + self->pending_event_allocation = FALSE; + + GCAL_EXIT; } static gboolean @@ -1791,8 +1870,6 @@ gcal_month_view_button_press (GtkWidget *widget, get_month_cell_at_position (self, x, y, &clicked_cell); - clicked_cell = real_cell (clicked_cell, self->k); - if (clicked_cell >= self->days_delay && clicked_cell < days) { g_clear_pointer (&self->start_mark_cell, g_date_time_unref); @@ -1850,7 +1927,7 @@ gcal_month_view_motion_notify_event (GtkWidget *widget, } else { - if (gtk_widget_is_visible (self->overflow_popover)) + if (gtk_widget_is_visible (GTK_WIDGET (self->overflow_popover))) GCAL_RETURN (GDK_EVENT_PROPAGATE); } @@ -1964,7 +2041,6 @@ gcal_month_view_class_init (GcalMonthViewClass *klass) gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/calendar/month-view.ui"); - gtk_widget_class_bind_template_child (widget_class, GcalMonthView, events_box); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_0); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_1); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_2); @@ -1973,14 +2049,13 @@ gcal_month_view_class_init (GcalMonthViewClass *klass) gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_5); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, label_6); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, month_label); - gtk_widget_class_bind_template_child (widget_class, GcalMonthView, overflow_popover); - gtk_widget_class_bind_template_child (widget_class, GcalMonthView, popover_title); gtk_widget_class_bind_template_child (widget_class, GcalMonthView, year_label); gtk_widget_class_bind_template_callback (widget_class, add_new_event_button_cb); - gtk_widget_class_bind_template_callback (widget_class, cancel_dnd_from_overflow_popover); gtk_widget_class_set_css_name (widget_class, "calendar-view"); + + g_type_ensure (GCAL_TYPE_MONTH_POPOVER); } static void @@ -1993,21 +2068,10 @@ gcal_month_view_init (GcalMonthView *self) self->children = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_list_free); self->single_cell_children = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free); self->overflow_cells = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) g_list_free); - self->hidden_as_overflow = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + self->pending_event_allocation = FALSE; self->k = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL; - /* - * Also set the overflow popover as a drop destination, so we can hide - * it when the user starts dragging an event that is inside the overflow - * list. - */ - gtk_drag_dest_set (GTK_WIDGET (self->overflow_popover), - 0, - NULL, - 0, - GDK_ACTION_MOVE); - /* Weekday header labels */ self->weekday_label[0] = self->label_0; self->weekday_label[1] = self->label_1; @@ -2018,6 +2082,20 @@ gcal_month_view_init (GcalMonthView *self) self->weekday_label[6] = self->label_6; update_weekday_labels (self); + + /* Overflow popover */ + self->overflow_popover = (GcalMonthPopover*) gcal_month_popover_new (); + + g_object_bind_property (self, + "manager", + self->overflow_popover, + "manager", + G_BINDING_DEFAULT); + + g_signal_connect_swapped (self->overflow_popover, + "event-activated", + G_CALLBACK (event_activated), + self); } /* Public API */