From eff50668d4f20187a28f628c944070870b33eda6 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Wed, 15 Nov 2017 02:03:26 -0200 Subject: [PATCH] month-view: introduce new overflow popover window This ~almost~ implements a perfectly semantic transition (which is kinda impossible now). --- data/calendar.gresource.xml | 1 + data/meson.build | 1 + data/theme/gtk-styles.css | 14 + data/ui/month-popover.ui | 99 ++++ data/ui/month-view.ui | 61 --- src/meson.build | 2 + src/views/gcal-month-popover.c | 865 +++++++++++++++++++++++++++++++++ src/views/gcal-month-popover.h | 50 ++ src/views/gcal-month-view.c | 136 ++---- 9 files changed, 1064 insertions(+), 165 deletions(-) create mode 100644 data/ui/month-popover.ui create mode 100644 src/views/gcal-month-popover.c create mode 100644 src/views/gcal-month-popover.h 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 475589eb..4cecb1ea 100644 --- a/data/theme/gtk-styles.css +++ b/data/theme/gtk-styles.css @@ -465,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-popover.ui b/data/ui/month-popover.ui new file mode 100644 index 00000000..3fc31166 --- /dev/null +++ b/data/ui/month-popover.ui @@ -0,0 +1,99 @@ + + + + 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/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-popover.c b/src/views/gcal-month-popover.c new file mode 100644 index 00000000..cf0e8363 --- /dev/null +++ b/src/views/gcal-month-popover.c @@ -0,0 +1,865 @@ +/* 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); +} + + +/* + * 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); +} + +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 daf246dc..7387e4e9 100644 --- a/src/views/gcal-month-view.c +++ b/src/views/gcal-month-view.c @@ -27,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" @@ -41,9 +42,7 @@ struct _GcalMonthView { GtkContainer parent; - GtkWidget *events_box; - GtkWidget *overflow_popover; - GtkWidget *popover_title; + GcalMonthPopover *overflow_popover; GdkWindow *event_window; @@ -185,11 +184,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); } @@ -201,7 +201,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); } @@ -385,78 +385,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 @@ -760,16 +697,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) @@ -799,12 +726,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); @@ -2045,7 +1972,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); } @@ -2159,7 +2086,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); @@ -2168,14 +2094,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 @@ -2193,17 +2118,6 @@ gcal_month_view_init (GcalMonthView *self) 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; @@ -2214,6 +2128,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 */