[evolution] I#1645 - Tasks: Support ESTIMATED-DURATION
- From: Milan Crha <mcrha src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution] I#1645 - Tasks: Support ESTIMATED-DURATION
- Date: Thu, 2 Dec 2021 17:45:53 +0000 (UTC)
commit 8aceb0c28d0aefced19b4e0cd3501383bc405ed1
Author: Milan Crha <mcrha redhat com>
Date: Thu Dec 2 18:44:41 2021 +0100
I#1645 - Tasks: Support ESTIMATED-DURATION
Closes https://gitlab.gnome.org/GNOME/evolution/-/issues/1645
po/POTFILES.in | 1 +
src/calendar/gui/CMakeLists.txt | 2 +
src/calendar/gui/e-cal-component-preview.c | 21 +
src/calendar/gui/e-comp-editor-property-parts.c | 137 ++++++
src/calendar/gui/e-comp-editor-property-parts.h | 3 +
src/calendar/gui/e-comp-editor-task.c | 15 +-
src/calendar/gui/e-estimated-duration-entry.c | 522 +++++++++++++++++++++
src/calendar/gui/e-estimated-duration-entry.h | 57 +++
src/calendar/gui/print.c | 31 ++
.../itip-formatter/itip-view-elements-defines.h | 1 +
src/modules/itip-formatter/itip-view.c | 28 ++
11 files changed, 815 insertions(+), 3 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index af0c350485..6faa645e63 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -96,6 +96,7 @@ src/calendar/gui/e-comp-editor.c
src/calendar/gui/e-day-view.c
src/calendar/gui/e-day-view-time-item.c
src/calendar/gui/e-day-view-top-item.c
+src/calendar/gui/e-estimated-duration-entry.c
src/calendar/gui/e-meeting-list-view.c
src/calendar/gui/e-meeting-store.c
src/calendar/gui/e-meeting-time-sel.c
diff --git a/src/calendar/gui/CMakeLists.txt b/src/calendar/gui/CMakeLists.txt
index 7514dc3de3..657f70aa69 100644
--- a/src/calendar/gui/CMakeLists.txt
+++ b/src/calendar/gui/CMakeLists.txt
@@ -61,6 +61,7 @@ set(SOURCES
e-day-view-main-item.c
e-day-view-time-item.c
e-day-view-top-item.c
+ e-estimated-duration-entry.c
e-meeting-attendee.c
e-meeting-list-view.c
e-meeting-store.c
@@ -130,6 +131,7 @@ set(HEADERS
e-comp-editor-property-part.h
e-comp-editor-property-parts.h
e-comp-editor-task.h
+ e-estimated-duration-entry.h
e-date-time-list.h
e-day-view-layout.h
e-day-view-main-item.h
diff --git a/src/calendar/gui/e-cal-component-preview.c b/src/calendar/gui/e-cal-component-preview.c
index a27abafed4..c50371e33a 100644
--- a/src/calendar/gui/e-cal-component-preview.c
+++ b/src/calendar/gui/e-cal-component-preview.c
@@ -318,6 +318,27 @@ cal_component_preview_write_html (ECalComponentPreview *preview,
icomp = e_cal_component_get_icalcomponent (comp);
+ prop = i_cal_component_get_first_property (icomp, I_CAL_ESTIMATEDDURATION_PROPERTY);
+ if (prop) {
+ ICalDuration *duration;
+
+ duration = i_cal_property_get_estimatedduration (prop);
+
+ if (duration) {
+ gint seconds;
+
+ seconds = i_cal_duration_as_int (duration);
+ if (seconds > 0) {
+ str = e_cal_util_seconds_to_string (seconds);
+ cal_component_preview_add_table_line (buffer, _("Estimated duration:"), str);
+ g_free (str);
+ }
+ }
+
+ g_clear_object (&duration);
+ g_object_unref (prop);
+ }
+
if (e_cal_util_component_has_recurrences (icomp)) {
str = e_cal_recur_describe_recurrence_ex (icomp,
calendar_config_get_week_start_day (),
diff --git a/src/calendar/gui/e-comp-editor-property-parts.c b/src/calendar/gui/e-comp-editor-property-parts.c
index 8278590f23..99e993dbb9 100644
--- a/src/calendar/gui/e-comp-editor-property-parts.c
+++ b/src/calendar/gui/e-comp-editor-property-parts.c
@@ -28,6 +28,7 @@
#include "comp-util.h"
#include "e-cal-model.h"
#include "e-timezone-entry.h"
+#include "e-estimated-duration-entry.h"
#include "e-comp-editor-property-part.h"
#include "e-comp-editor-property-parts.h"
@@ -2424,3 +2425,139 @@ e_comp_editor_property_part_color_new (void)
{
return g_object_new (E_TYPE_COMP_EDITOR_PROPERTY_PART_COLOR, NULL);
}
+
+/* ************************************************************************* */
+
+#define E_TYPE_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION \
+ (e_comp_editor_property_part_estimated_duration_get_type ())
+#define E_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION,
ECompEditorPropertyParteEtimatedDuration))
+#define E_IS_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION))
+
+typedef struct _ECompEditorPropertyPartEstimatedDuration ECompEditorPropertyPartEstimatedDuration;
+typedef struct _ECompEditorPropertyPartEstimatedDurationClass ECompEditorPropertyPartEstimatedDurationClass;
+
+struct _ECompEditorPropertyPartEstimatedDuration {
+ ECompEditorPropertyPart parent;
+};
+
+struct _ECompEditorPropertyPartEstimatedDurationClass {
+ ECompEditorPropertyPartClass parent_class;
+};
+
+GType e_comp_editor_property_part_estimated_duration_get_type (void) G_GNUC_CONST;
+
+G_DEFINE_TYPE (ECompEditorPropertyPartEstimatedDuration, e_comp_editor_property_part_estimated_duration,
E_TYPE_COMP_EDITOR_PROPERTY_PART)
+
+static void
+ecepp_estimated_duration_create_widgets (ECompEditorPropertyPart *property_part,
+ GtkWidget **out_label_widget,
+ GtkWidget **out_edit_widget)
+{
+ g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION (property_part));
+ g_return_if_fail (out_label_widget != NULL);
+ g_return_if_fail (out_edit_widget != NULL);
+
+ *out_label_widget = gtk_label_new_with_mnemonic (_("Esti_mated duration:"));
+
+ g_object_set (G_OBJECT (*out_label_widget),
+ "hexpand", FALSE,
+ "halign", GTK_ALIGN_END,
+ "vexpand", FALSE,
+ "valign", GTK_ALIGN_CENTER,
+ NULL);
+
+ gtk_widget_show (*out_label_widget);
+
+ *out_edit_widget = e_estimated_duration_entry_new ();
+ gtk_widget_show (*out_edit_widget);
+
+ gtk_label_set_mnemonic_widget (GTK_LABEL (*out_label_widget), *out_edit_widget);
+
+ g_signal_connect_swapped (*out_edit_widget, "changed",
+ G_CALLBACK (e_comp_editor_property_part_emit_changed), property_part);
+}
+
+static void
+ecepp_estimated_duration_fill_widget (ECompEditorPropertyPart *property_part,
+ ICalComponent *component)
+{
+ GtkWidget *edit_widget;
+ ICalProperty *prop;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION (property_part));
+
+ edit_widget = e_comp_editor_property_part_get_edit_widget (property_part);
+ g_return_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (edit_widget));
+
+ prop = i_cal_component_get_first_property (component, I_CAL_ESTIMATEDDURATION_PROPERTY);
+ if (prop) {
+ ICalDuration *duration = i_cal_property_get_estimatedduration (prop);
+
+ e_estimated_duration_entry_set_value (E_ESTIMATED_DURATION_ENTRY (edit_widget), duration);
+
+ g_clear_object (&duration);
+ g_clear_object (&prop);
+ } else {
+ e_estimated_duration_entry_set_value (E_ESTIMATED_DURATION_ENTRY (edit_widget), NULL);
+ }
+}
+
+static void
+ecepp_estimated_duration_fill_component (ECompEditorPropertyPart *property_part,
+ ICalComponent *component)
+{
+ GtkWidget *edit_widget;
+ ICalProperty *prop;
+ ICalDuration *duration;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION (property_part));
+
+ edit_widget = e_comp_editor_property_part_get_edit_widget (property_part);
+ g_return_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (edit_widget));
+
+ duration = e_estimated_duration_entry_get_value (E_ESTIMATED_DURATION_ENTRY (edit_widget));
+
+ prop = i_cal_component_get_first_property (component, I_CAL_ESTIMATEDDURATION_PROPERTY);
+
+ if (duration) {
+ if (prop) {
+ i_cal_property_set_estimatedduration (prop, duration);
+ } else {
+ prop = i_cal_property_new_estimatedduration (duration);
+ i_cal_component_add_property (component, prop);
+ }
+ } else {
+ if (prop)
+ i_cal_component_remove_property (component, prop);
+ }
+
+ g_clear_object (&prop);
+}
+
+static void
+e_comp_editor_property_part_estimated_duration_init (ECompEditorPropertyPartEstimatedDuration
*part_estimated_duration)
+{
+}
+
+static void
+e_comp_editor_property_part_estimated_duration_class_init (ECompEditorPropertyPartEstimatedDurationClass
*klass)
+{
+ ECompEditorPropertyPartClass *part_class;
+
+ part_class = E_COMP_EDITOR_PROPERTY_PART_CLASS (klass);
+ part_class->create_widgets = ecepp_estimated_duration_create_widgets;
+ part_class->fill_widget = ecepp_estimated_duration_fill_widget;
+ part_class->fill_component = ecepp_estimated_duration_fill_component;
+}
+
+ECompEditorPropertyPart *
+e_comp_editor_property_part_estimated_duration_new (void)
+{
+ return g_object_new (E_TYPE_COMP_EDITOR_PROPERTY_PART_ESTIMATED_DURATION, NULL);
+}
+
+/* ************************************************************************* */
diff --git a/src/calendar/gui/e-comp-editor-property-parts.h b/src/calendar/gui/e-comp-editor-property-parts.h
index 5a3954f337..e0cf5114e7 100644
--- a/src/calendar/gui/e-comp-editor-property-parts.h
+++ b/src/calendar/gui/e-comp-editor-property-parts.h
@@ -62,6 +62,9 @@ ECompEditorPropertyPart *
e_comp_editor_property_part_transparency_new (void);
ECompEditorPropertyPart *
e_comp_editor_property_part_color_new (void);
+ECompEditorPropertyPart *
+ e_comp_editor_property_part_estimated_duration_new
+ (void);
G_END_DECLS
diff --git a/src/calendar/gui/e-comp-editor-task.c b/src/calendar/gui/e-comp-editor-task.c
index 81ee16cb0c..3fde93a7ac 100644
--- a/src/calendar/gui/e-comp-editor-task.c
+++ b/src/calendar/gui/e-comp-editor-task.c
@@ -45,6 +45,7 @@ struct _ECompEditorTaskPrivate {
ECompEditorPropertyPart *completed_date;
ECompEditorPropertyPart *percentcomplete;
ECompEditorPropertyPart *status;
+ ECompEditorPropertyPart *estimated_duration;
ECompEditorPropertyPart *timezone;
ECompEditorPropertyPart *description;
@@ -221,6 +222,7 @@ ece_task_notify_target_client_cb (GObject *object,
gboolean was_allday;
gboolean can_recur;
gboolean can_reminders;
+ gboolean can_estimated_duration;
g_return_if_fail (E_IS_COMP_EDITOR_TASK (object));
@@ -256,6 +258,9 @@ ece_task_notify_target_client_cb (GObject *object,
can_recur = !cal_client || e_client_check_capability (E_CLIENT (cal_client),
E_CAL_STATIC_CAPABILITY_TASK_CAN_RECUR);
gtk_widget_set_visible (GTK_WIDGET (task_editor->priv->recurrence_page), can_recur);
+
+ can_estimated_duration = !cal_client || e_client_check_capability (E_CLIENT (cal_client),
E_CAL_STATIC_CAPABILITY_TASK_ESTIMATED_DURATION);
+ e_comp_editor_property_part_set_visible (task_editor->priv->estimated_duration,
can_estimated_duration);
}
static void
@@ -964,16 +969,20 @@ e_comp_editor_task_constructed (GObject *object)
part = e_comp_editor_property_part_classification_new ();
e_comp_editor_page_add_property_part (page, part, 2, 7, 2, 1);
- part = e_comp_editor_property_part_timezone_new ();
+ part = e_comp_editor_property_part_estimated_duration_new ();
e_comp_editor_page_add_property_part (page, part, 0, 8, 4, 1);
+ task_editor->priv->estimated_duration = part;
+
+ part = e_comp_editor_property_part_timezone_new ();
+ e_comp_editor_page_add_property_part (page, part, 0, 9, 4, 1);
task_editor->priv->timezone = part;
part = e_comp_editor_property_part_categories_new (focus_tracker);
- e_comp_editor_page_add_property_part (page, part, 0, 9, 4, 1);
+ e_comp_editor_page_add_property_part (page, part, 0, 10, 4, 1);
task_editor->priv->categories = part;
part = e_comp_editor_property_part_description_new (focus_tracker);
- e_comp_editor_page_add_property_part (page, part, 0, 10, 4, 1);
+ e_comp_editor_page_add_property_part (page, part, 0, 11, 4, 1);
task_editor->priv->description = part;
e_comp_editor_add_page (comp_editor, C_("ECompEditorPage", "General"), page);
diff --git a/src/calendar/gui/e-estimated-duration-entry.c b/src/calendar/gui/e-estimated-duration-entry.c
new file mode 100644
index 0000000000..168831ec30
--- /dev/null
+++ b/src/calendar/gui/e-estimated-duration-entry.c
@@ -0,0 +1,522 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2021 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "evolution-config.h"
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "e-estimated-duration-entry.h"
+
+struct _EEstimatedDurationEntryPrivate {
+ ICalDuration *value;
+
+ GtkWidget *popover;
+ GtkWidget *days_spin;
+ GtkWidget *hours_spin;
+ GtkWidget *minutes_spin;
+ GtkWidget *set_button;
+ GtkWidget *unset_button;
+ GtkSizeGroup *size_group;
+
+ GtkWidget *entry;
+ GtkWidget *button;
+};
+
+enum {
+ PROP_0,
+ PROP_VALUE
+};
+
+enum {
+ CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_TYPE_WITH_PRIVATE (EEstimatedDurationEntry, e_estimated_duration_entry, GTK_TYPE_BOX)
+
+static void
+estimated_duration_entry_emit_changed (EEstimatedDurationEntry *self)
+{
+ g_signal_emit (self, signals[CHANGED], 0);
+}
+
+static void
+estimated_duration_entry_update_entry (EEstimatedDurationEntry *self)
+{
+ gchar *tmp = NULL;
+ ICalDuration *value;
+
+ value = e_estimated_duration_entry_get_value (self);
+
+ if (value != NULL) {
+ gint64 seconds = i_cal_duration_as_int (value);
+
+ if (seconds > 0)
+ tmp = e_cal_util_seconds_to_string (seconds);
+ }
+
+ gtk_entry_set_text (GTK_ENTRY (self->priv->entry), tmp ? tmp : C_("estimated-duration", "None"));
+
+ g_free (tmp);
+}
+
+static void
+estimated_duration_entry_add_relation (EEstimatedDurationEntry *self)
+{
+ AtkObject *a11y_estimated_duration_entry;
+ AtkObject *a11y_widget;
+ AtkRelationSet *set;
+ AtkRelation *relation;
+ GtkWidget *widget;
+ GPtrArray *target;
+ gpointer target_object;
+
+ /* add a labelled_by relation for widget for accessibility */
+
+ widget = GTK_WIDGET (self);
+ a11y_estimated_duration_entry = gtk_widget_get_accessible (widget);
+
+ widget = self->priv->entry;
+ a11y_widget = gtk_widget_get_accessible (widget);
+
+ set = atk_object_ref_relation_set (a11y_widget);
+ if (set != NULL) {
+ relation = atk_relation_set_get_relation_by_type (set, ATK_RELATION_LABELLED_BY);
+ /* check whether has a labelled_by relation already */
+ if (relation != NULL) {
+ g_object_unref (set);
+ return;
+ }
+ }
+
+ g_clear_object (&set);
+
+ set = atk_object_ref_relation_set (a11y_estimated_duration_entry);
+ if (!set)
+ return;
+
+ relation = atk_relation_set_get_relation_by_type (set, ATK_RELATION_LABELLED_BY);
+ if (relation != NULL) {
+ target = atk_relation_get_target (relation);
+ target_object = g_ptr_array_index (target, 0);
+ if (ATK_IS_OBJECT (target_object)) {
+ atk_object_add_relationship (
+ a11y_widget,
+ ATK_RELATION_LABELLED_BY,
+ ATK_OBJECT (target_object));
+ }
+ }
+
+ g_clear_object (&set);
+}
+
+static void
+estimated_duration_set_button_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ EEstimatedDurationEntry *self = user_data;
+ ICalDuration *duration;
+ gint new_minutes;
+
+ g_return_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (self));
+
+ new_minutes =
+ gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->priv->minutes_spin)) +
+ (60 * gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->priv->hours_spin))) +
+ (24 * 60 * gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->priv->days_spin)));
+ g_return_if_fail (new_minutes > 0);
+
+ gtk_widget_hide (self->priv->popover);
+
+ duration = i_cal_duration_new_from_int (60 * new_minutes);
+ e_estimated_duration_entry_set_value (self, duration);
+ g_clear_object (&duration);
+}
+
+static void
+estimated_duration_unset_button_clicked_cb (GtkButton *button,
+ gpointer user_data)
+{
+ EEstimatedDurationEntry *self = user_data;
+
+ g_return_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (self));
+
+ gtk_widget_hide (self->priv->popover);
+
+ e_estimated_duration_entry_set_value (self, NULL);
+}
+
+static void
+estimated_duration_update_sensitize_cb (GtkSpinButton *spin,
+ gpointer user_data)
+{
+ EEstimatedDurationEntry *self = user_data;
+
+ g_return_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (self));
+
+ gtk_widget_set_sensitive (self->priv->set_button,
+ gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->priv->minutes_spin)) +
+ gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->priv->hours_spin)) +
+ gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (self->priv->days_spin)) > 0);
+}
+
+static void
+estimated_duration_entry_button_clicked_cb (EEstimatedDurationEntry *self)
+{
+ gint value;
+
+ if (!self->priv->popover) {
+ GtkWidget *widget;
+ GtkBox *vbox, *box;
+
+ self->priv->days_spin = gtk_spin_button_new_with_range (0.0, 366.0, 1.0);
+ self->priv->hours_spin = gtk_spin_button_new_with_range (0.0, 23.0, 1.0);
+ self->priv->minutes_spin = gtk_spin_button_new_with_range (0.0, 59.0, 1.0);
+
+ g_object_set (G_OBJECT (self->priv->days_spin),
+ "digits", 0,
+ "numeric", TRUE,
+ "snap-to-ticks", TRUE,
+ NULL);
+
+ g_object_set (G_OBJECT (self->priv->hours_spin),
+ "digits", 0,
+ "numeric", TRUE,
+ "snap-to-ticks", TRUE,
+ NULL);
+
+ g_object_set (G_OBJECT (self->priv->minutes_spin),
+ "digits", 0,
+ "numeric", TRUE,
+ "snap-to-ticks", TRUE,
+ NULL);
+
+ vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 2));
+
+ widget = gtk_label_new (_("Set an estimated duration for"));
+ gtk_box_pack_start (vbox, widget, FALSE, FALSE, 0);
+
+ box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
+ g_object_set (G_OBJECT (box),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_CENTER,
+ "vexpand", FALSE,
+ NULL);
+
+ gtk_box_pack_start (box, self->priv->days_spin, FALSE, FALSE, 4);
+ /* Translators: this is part of: "Set an estimated duration for [nnn] days [nnn] hours [nnn]
minutes", where the text in "[]" means a separate widget */
+ widget = gtk_label_new_with_mnemonic (C_("estimated-duration", "_days"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), self->priv->days_spin);
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 4);
+
+ gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
+
+ box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
+ g_object_set (G_OBJECT (box),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_CENTER,
+ "vexpand", FALSE,
+ NULL);
+
+ gtk_box_pack_start (box, self->priv->hours_spin, FALSE, FALSE, 4);
+ /* Translators: this is part of: "Set an estimated duration for [nnn] days [nnn] hours [nnn]
minutes", where the text in "[]" means a separate widget */
+ widget = gtk_label_new_with_mnemonic (C_("estimated-duration", "_hours"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), self->priv->hours_spin);
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 4);
+
+ gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
+
+ box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
+ g_object_set (G_OBJECT (box),
+ "halign", GTK_ALIGN_START,
+ "hexpand", FALSE,
+ "valign", GTK_ALIGN_CENTER,
+ "vexpand", FALSE,
+ NULL);
+
+ gtk_box_pack_start (box, self->priv->minutes_spin, FALSE, FALSE, 4);
+ /* Translators: this is part of: "Set an estimated duration for [nnn] days [nnn] hours [nnn]
minutes", where the text in "[]" means a separate widget */
+ widget = gtk_label_new_with_mnemonic (C_("estimated-duration", "_minutes"));
+ gtk_label_set_mnemonic_widget (GTK_LABEL (widget), self->priv->minutes_spin);
+ gtk_box_pack_start (box, widget, FALSE, FALSE, 4);
+
+ gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
+
+ box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2));
+ g_object_set (G_OBJECT (box),
+ "halign", GTK_ALIGN_CENTER,
+ "hexpand", TRUE,
+ "valign", GTK_ALIGN_CENTER,
+ "vexpand", FALSE,
+ NULL);
+
+ self->priv->unset_button = gtk_button_new_with_mnemonic (_("_Unset"));
+ g_object_set (G_OBJECT (self->priv->unset_button),
+ "halign", GTK_ALIGN_CENTER,
+ NULL);
+
+ gtk_box_pack_start (box, self->priv->unset_button, FALSE, FALSE, 1);
+
+ self->priv->set_button = gtk_button_new_with_mnemonic (_("_Set"));
+ g_object_set (G_OBJECT (self->priv->set_button),
+ "halign", GTK_ALIGN_CENTER,
+ NULL);
+
+ gtk_box_pack_start (box, self->priv->set_button, FALSE, FALSE, 1);
+
+ gtk_box_pack_start (vbox, GTK_WIDGET (box), FALSE, FALSE, 0);
+
+ self->priv->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+ gtk_size_group_add_widget (self->priv->size_group, self->priv->unset_button);
+ gtk_size_group_add_widget (self->priv->size_group, self->priv->set_button);
+
+ gtk_widget_show_all (GTK_WIDGET (vbox));
+
+ self->priv->popover = gtk_popover_new (GTK_WIDGET (self));
+ gtk_popover_set_position (GTK_POPOVER (self->priv->popover), GTK_POS_BOTTOM);
+ gtk_container_add (GTK_CONTAINER (self->priv->popover), GTK_WIDGET (vbox));
+ gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
+
+ g_signal_connect (self->priv->set_button, "clicked",
+ G_CALLBACK (estimated_duration_set_button_clicked_cb), self);
+
+ g_signal_connect (self->priv->unset_button, "clicked",
+ G_CALLBACK (estimated_duration_unset_button_clicked_cb), self);
+
+ g_signal_connect (self->priv->days_spin, "value-changed",
+ G_CALLBACK (estimated_duration_update_sensitize_cb), self);
+
+ g_signal_connect (self->priv->hours_spin, "value-changed",
+ G_CALLBACK (estimated_duration_update_sensitize_cb), self);
+
+ g_signal_connect (self->priv->minutes_spin, "value-changed",
+ G_CALLBACK (estimated_duration_update_sensitize_cb), self);
+ }
+
+ value = self->priv->value ? i_cal_duration_as_int (self->priv->value) : 0;
+
+ /* seconds are ignored */
+ value = value / 60;
+
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->priv->minutes_spin), value % 60);
+
+ value = value / 60;
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->priv->hours_spin), value % 24);
+
+ value = value / 24;
+ gtk_spin_button_set_value (GTK_SPIN_BUTTON (self->priv->days_spin), value);
+
+ gtk_widget_hide (self->priv->popover);
+ gtk_popover_set_relative_to (GTK_POPOVER (self->priv->popover), self->priv->entry);
+ gtk_widget_show (self->priv->popover);
+
+ gtk_widget_grab_focus (self->priv->days_spin);
+
+ estimated_duration_update_sensitize_cb (NULL, self);
+}
+
+static void
+estimated_duration_entry_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_VALUE:
+ e_estimated_duration_entry_set_value (
+ E_ESTIMATED_DURATION_ENTRY (object),
+ g_value_get_object (value));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+estimated_duration_entry_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_VALUE:
+ g_value_set_object (
+ value, e_estimated_duration_entry_get_value (
+ E_ESTIMATED_DURATION_ENTRY (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+estimated_duration_entry_get_finalize (GObject *object)
+{
+ EEstimatedDurationEntry *self = E_ESTIMATED_DURATION_ENTRY (object);
+
+ g_clear_object (&self->priv->value);
+ g_clear_object (&self->priv->size_group);
+
+ /* Chain up to parent's method. */
+ G_OBJECT_CLASS (e_estimated_duration_entry_parent_class)->finalize (object);
+}
+
+static gboolean
+estimated_duration_entry_mnemonic_activate (GtkWidget *widget,
+ gboolean group_cycling)
+{
+ EEstimatedDurationEntry *self = E_ESTIMATED_DURATION_ENTRY (widget);
+
+ if (gtk_widget_get_can_focus (widget)) {
+ if (self->priv->button != NULL)
+ gtk_widget_grab_focus (self->priv->button);
+ }
+
+ return TRUE;
+}
+
+static gboolean
+estimated_duration_entry_focus (GtkWidget *widget,
+ GtkDirectionType direction)
+{
+ EEstimatedDurationEntry *self = E_ESTIMATED_DURATION_ENTRY (widget);
+
+ if (direction == GTK_DIR_TAB_FORWARD) {
+ if (gtk_widget_has_focus (self->priv->entry))
+ gtk_widget_grab_focus (self->priv->button);
+ else if (gtk_widget_has_focus (self->priv->button))
+ return FALSE;
+ else if (gtk_widget_get_visible (self->priv->entry))
+ gtk_widget_grab_focus (self->priv->entry);
+ else
+ gtk_widget_grab_focus (self->priv->button);
+
+ } else if (direction == GTK_DIR_TAB_BACKWARD) {
+ if (gtk_widget_has_focus (self->priv->entry))
+ return FALSE;
+ else if (gtk_widget_has_focus (self->priv->button)) {
+ if (gtk_widget_get_visible (self->priv->entry))
+ gtk_widget_grab_focus (self->priv->entry);
+ else
+ return FALSE;
+ } else
+ gtk_widget_grab_focus (self->priv->button);
+ } else
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+e_estimated_duration_entry_class_init (EEstimatedDurationEntryClass *klass)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->set_property = estimated_duration_entry_set_property;
+ object_class->get_property = estimated_duration_entry_get_property;
+ object_class->finalize = estimated_duration_entry_get_finalize;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ widget_class->mnemonic_activate = estimated_duration_entry_mnemonic_activate;
+ widget_class->focus = estimated_duration_entry_focus;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_VALUE,
+ g_param_spec_object (
+ "value",
+ "Value",
+ NULL,
+ I_CAL_TYPE_DURATION,
+ G_PARAM_READWRITE));
+
+ signals[CHANGED] = g_signal_new (
+ "changed",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EEstimatedDurationEntryClass, changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+e_estimated_duration_entry_init (EEstimatedDurationEntry *self)
+{
+ GtkWidget *widget;
+
+ self->priv = e_estimated_duration_entry_get_instance_private (self);
+
+ gtk_widget_set_can_focus (GTK_WIDGET (self), TRUE);
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self), GTK_ORIENTATION_HORIZONTAL);
+
+ widget = gtk_entry_new ();
+ gtk_editable_set_editable (GTK_EDITABLE (widget), FALSE);
+ gtk_box_pack_start (GTK_BOX (self), widget, TRUE, TRUE, 0);
+ self->priv->entry = widget;
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "changed",
+ G_CALLBACK (estimated_duration_entry_emit_changed), self);
+
+ widget = gtk_button_new_with_mnemonic (_("Chan_ge…"));
+ gtk_box_pack_start (GTK_BOX (self), widget, FALSE, FALSE, 6);
+ self->priv->button = widget;
+ gtk_widget_show (widget);
+
+ g_signal_connect_swapped (
+ widget, "clicked",
+ G_CALLBACK (estimated_duration_entry_button_clicked_cb), self);
+
+ estimated_duration_entry_update_entry (self);
+}
+
+GtkWidget *
+e_estimated_duration_entry_new (void)
+{
+ return g_object_new (E_TYPE_ESTIMATED_DURATION_ENTRY, NULL);
+}
+
+ICalDuration *
+e_estimated_duration_entry_get_value (EEstimatedDurationEntry *self)
+{
+ g_return_val_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (self), NULL);
+
+ return self->priv->value;
+}
+
+void
+e_estimated_duration_entry_set_value (EEstimatedDurationEntry *self,
+ const ICalDuration *value)
+{
+ g_return_if_fail (E_IS_ESTIMATED_DURATION_ENTRY (self));
+
+ if (value && !i_cal_duration_as_int ((ICalDuration *) value))
+ value = NULL;
+
+ if (self->priv->value == value)
+ return;
+
+ if (self->priv->value && value && i_cal_duration_as_int (self->priv->value) == i_cal_duration_as_int
((ICalDuration *) value))
+ return;
+
+ g_clear_object (&self->priv->value);
+ if (value)
+ self->priv->value = i_cal_duration_new_from_int (i_cal_duration_as_int ((ICalDuration *)
value));
+
+ estimated_duration_entry_update_entry (self);
+ estimated_duration_entry_add_relation (self);
+
+ g_object_notify (G_OBJECT (self), "value");
+}
diff --git a/src/calendar/gui/e-estimated-duration-entry.h b/src/calendar/gui/e-estimated-duration-entry.h
new file mode 100644
index 0000000000..a78e655b2a
--- /dev/null
+++ b/src/calendar/gui/e-estimated-duration-entry.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * SPDX-FileCopyrightText: (C) 2021 Red Hat (www.redhat.com)
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef E_ESTIMATED_DURATION_ENTRY_H
+#define E_ESTIMATED_DURATION_ENTRY_H
+
+#include <gtk/gtk.h>
+#include <libecal/libecal.h>
+
+/* Standard GObject macros */
+#define E_TYPE_ESTIMATED_DURATION_ENTRY \
+ (e_estimated_duration_entry_get_type ())
+#define E_ESTIMATED_DURATION_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST \
+ ((obj), E_TYPE_ESTIMATED_DURATION_ENTRY, EEstimatedDurationEntry))
+#define E_ESTIMATED_DURATION_ENTRY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_CAST \
+ ((cls), E_TYPE_ESTIMATED_DURATION_ENTRY, EEstimatedDurationEntryClass))
+#define E_IS_ESTIMATED_DURATION_ENTRY(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE \
+ ((obj), E_TYPE_ESTIMATED_DURATION_ENTRY))
+#define E_IS_ESTIMATED_DURATION_ENTRY_CLASS(cls) \
+ (G_TYPE_CHECK_CLASS_TYPE \
+ ((cls), E_TYPE_ESTIMATED_DURATION_ENTRY))
+#define E_IS_ESTIMATED_DURATION_ENTRY_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS \
+ ((obj), E_TYPE_ESTIMATED_DURATION_ENTRY, EEstimatedDurationEntryClass))
+
+G_BEGIN_DECLS
+
+typedef struct _EEstimatedDurationEntry EEstimatedDurationEntry;
+typedef struct _EEstimatedDurationEntryClass EEstimatedDurationEntryClass;
+typedef struct _EEstimatedDurationEntryPrivate EEstimatedDurationEntryPrivate;
+
+struct _EEstimatedDurationEntry {
+ GtkBox parent;
+ EEstimatedDurationEntryPrivate *priv;
+};
+
+struct _EEstimatedDurationEntryClass {
+ GtkBoxClass parent_class;
+
+ void (*changed) (EEstimatedDurationEntry *estimated_duration_entry);
+};
+
+GType e_estimated_duration_entry_get_type (void);
+GtkWidget * e_estimated_duration_entry_new (void);
+ICalDuration * e_estimated_duration_entry_get_value (EEstimatedDurationEntry *self);
+void e_estimated_duration_entry_set_value (EEstimatedDurationEntry *self,
+ const ICalDuration *value);
+
+G_END_DECLS
+
+#endif /* E_ESTIMATED_DURATION_ENTRY_H */
diff --git a/src/calendar/gui/print.c b/src/calendar/gui/print.c
index 1c3bec5bc8..43ef9e2fbe 100644
--- a/src/calendar/gui/print.c
+++ b/src/calendar/gui/print.c
@@ -3660,12 +3660,43 @@ print_comp_draw_real (GtkPrintOperation *operation,
/* For a VTODO we print the Status, Priority, % Complete and URL. */
if (vtype == E_CAL_COMPONENT_TODO) {
+ ICalComponent *icomp;
+ ICalProperty *prop;
ICalPropertyStatus status;
const gchar *status_string = NULL;
gint percent;
gint priority;
gchar *url;
+ icomp = e_cal_component_get_icalcomponent (comp);
+
+ /* Estimated duration */
+ prop = i_cal_component_get_first_property (icomp, I_CAL_ESTIMATEDDURATION_PROPERTY);
+ if (prop) {
+ ICalDuration *duration;
+
+ duration = i_cal_property_get_estimatedduration (prop);
+
+ if (duration) {
+ gint seconds;
+
+ seconds = i_cal_duration_as_int (duration);
+ if (seconds > 0) {
+ gchar *tmp = e_cal_util_seconds_to_string (seconds);
+ gchar *estimated_duration = g_strdup_printf (_("Estimated duration:
%s"), tmp);
+ top = bound_text (
+ context, font, estimated_duration, -1,
+ 0.0, top, width, height, FALSE, NULL, &page_start, &pages);
+ top += get_font_size (font) - 6;
+ g_free (estimated_duration);
+ g_free (tmp);
+ }
+ }
+
+ g_clear_object (&duration);
+ g_object_unref (prop);
+ }
+
/* Status */
status = e_cal_component_get_status (comp);
if (status != I_CAL_STATUS_NONE) {
diff --git a/src/modules/itip-formatter/itip-view-elements-defines.h
b/src/modules/itip-formatter/itip-view-elements-defines.h
index 23c4b1912a..c8ba032abc 100644
--- a/src/modules/itip-formatter/itip-view-elements-defines.h
+++ b/src/modules/itip-formatter/itip-view-elements-defines.h
@@ -26,6 +26,7 @@
#define TABLE_ROW_START_DATE "table_row_start_time"
#define TABLE_ROW_END_DATE "table_row_end_time"
#define TABLE_ROW_DUE_DATE "table_row_due_date"
+#define TABLE_ROW_ESTIMATED_DURATION "table_row_estimated_duration"
#define TABLE_ROW_STATUS "table_row_status"
#define TABLE_ROW_COMMENT "table_row_comment"
#define TABLE_ROW_CATEGORIES "table_row_categories"
diff --git a/src/modules/itip-formatter/itip-view.c b/src/modules/itip-formatter/itip-view.c
index 6c611716bc..e4da17c9c0 100644
--- a/src/modules/itip-formatter/itip-view.c
+++ b/src/modules/itip-formatter/itip-view.c
@@ -97,6 +97,7 @@ struct _ItipViewPrivate {
gchar *categories;
gchar *due_date_label;
+ gchar *estimated_duration;
GSList *upper_info_items;
GSList *lower_info_items;
@@ -854,6 +855,7 @@ update_start_end_times (ItipView *view)
g_free (priv->end_label);
g_free (priv->categories);
g_free (priv->due_date_label);
+ g_free (priv->estimated_duration);
#define is_same(_member) (priv->start_tm->_member == priv->end_tm->_member)
if (priv->start_tm && priv->end_tm && priv->start_tm_is_date && priv->end_tm_is_date
@@ -1712,6 +1714,7 @@ itip_view_write (gpointer itip_part_ptr,
append_text_table_row (buffer, TABLE_ROW_START_DATE, _("Start time:"), NULL);
append_text_table_row (buffer, TABLE_ROW_END_DATE, _("End time:"), NULL);
append_text_table_row (buffer, TABLE_ROW_DUE_DATE, _("Due date:"), NULL);
+ append_text_table_row (buffer, TABLE_ROW_ESTIMATED_DURATION, _("Estimated duration:"), NULL);
append_text_table_row (buffer, TABLE_ROW_STATUS, _("Status:"), NULL);
append_text_table_row (buffer, TABLE_ROW_COMMENT, _("Comment:"), NULL);
append_text_table_row (buffer, TABLE_ROW_CATEGORIES, _("Categories:"), NULL);
@@ -1835,6 +1838,9 @@ itip_view_write_for_printing (ItipView *view,
append_text_table_row_nonempty (
buffer, TABLE_ROW_DUE_DATE,
_("Due date:"), view->priv->due_date_label);
+ append_text_table_row_nonempty (
+ buffer, TABLE_ROW_ESTIMATED_DURATION,
+ _("Estimated duration:"), view->priv->estimated_duration);
append_text_table_row_nonempty (
buffer, TABLE_ROW_STATUS,
_("Status:"), view->priv->status);
@@ -6816,6 +6822,28 @@ itip_view_init_view (ItipView *view)
set_area_text (view, TABLE_ROW_CATEGORIES, view->priv->categories, FALSE);
}
+ g_clear_pointer (&view->priv->estimated_duration, g_free);
+
+ prop = i_cal_component_get_first_property (icomp, I_CAL_ESTIMATEDDURATION_PROPERTY);
+ if (prop) {
+ ICalDuration *duration;
+
+ duration = i_cal_property_get_estimatedduration (prop);
+
+ if (duration) {
+ gint seconds;
+
+ seconds = i_cal_duration_as_int (duration);
+ if (seconds > 0) {
+ view->priv->estimated_duration = e_cal_util_seconds_to_string (seconds);
+ set_area_text (view, TABLE_ROW_ESTIMATED_DURATION,
view->priv->estimated_duration, FALSE);
+ }
+ }
+
+ g_clear_object (&duration);
+ g_object_unref (prop);
+ }
+
/* Recurrence info */
itip_view_add_recurring_info (view);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]