Files
evolution/debian/patches/3001-command_key.patch

16117 lines
447 KiB
Diff
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Description: Use Command as primary modifier key
Origin: Callisto Desktop
Author: Maia <maia@tsundoku.ne.jp>
Last-Update: 2025-06-07 evolution-3.46.4
diff -urN a/src/addressbook/gui/contact-editor/e-contact-editor.c b/src/addressbook/gui/contact-editor/e-contact-editor.c
--- a/src/addressbook/gui/contact-editor/e-contact-editor.c 2025-06-07 14:20:57.255396309 -0700
+++ b/src/addressbook/gui/contact-editor/e-contact-editor.c 2025-06-07 14:27:24.031672803 -0700
@@ -262,14 +262,14 @@
{ "undo",
"edit-undo",
N_("_Undo"),
- "<Control>z",
+ "<Super>z",
N_("Undo"),
NULL }, /* Handled by EFocusTracker */
{ "redo",
"edit-redo",
N_("_Redo"),
- "<Control>y",
+ "<Super>y",
N_("Redo"),
NULL } /* Handled by EFocusTracker */
diff -urN a/src/addressbook/gui/widgets/eab-contact-display.c b/src/addressbook/gui/widgets/eab-contact-display.c
--- a/src/addressbook/gui/widgets/eab-contact-display.c 2025-06-07 14:20:57.263396404 -0700
+++ b/src/addressbook/gui/widgets/eab-contact-display.c 2025-06-07 14:27:24.031672803 -0700
@@ -184,7 +184,7 @@
{ "contact-mailto-copy",
"edit-copy",
N_("Copy _Email Address"),
- "<Control>c",
+ "<Super>c",
N_("Copy the email address to the clipboard"),
G_CALLBACK (action_contact_mailto_copy_cb) },
diff -urN a/src/calendar/gui/e-comp-editor-event.c b/src/calendar/gui/e-comp-editor-event.c
--- a/src/calendar/gui/e-comp-editor-event.c 2025-06-07 14:20:57.267396453 -0700
+++ b/src/calendar/gui/e-comp-editor-event.c 2025-06-07 14:27:24.031672803 -0700
@@ -781,7 +781,7 @@
{ "all-day-event",
"stock_new-24h-appointment",
N_("All _Day Event"),
- "<Control>Y",
+ "<Super>Y",
N_("Toggles whether to have All Day Event"),
NULL,
FALSE },
diff -urN a/src/calendar/gui/e-comp-editor-event.c.orig b/src/calendar/gui/e-comp-editor-event.c.orig
--- a/src/calendar/gui/e-comp-editor-event.c.orig 1969-12-31 16:00:00.000000000 -0800
+++ b/src/calendar/gui/e-comp-editor-event.c.orig 2025-06-07 14:20:57.267396453 -0700
@@ -0,0 +1,1079 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc. (www.redhat.com)
+ *
+ * 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.
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-config.h"
+
+#include <glib/gi18n-lib.h>
+#include <gtk/gtk.h>
+
+#include <e-util/e-util.h>
+
+#include "calendar-config.h"
+#include "comp-util.h"
+#include "e-comp-editor.h"
+#include "e-comp-editor-page.h"
+#include "e-comp-editor-page-attachments.h"
+#include "e-comp-editor-page-general.h"
+#include "e-comp-editor-page-recurrence.h"
+#include "e-comp-editor-page-reminders.h"
+#include "e-comp-editor-page-schedule.h"
+#include "e-comp-editor-property-parts.h"
+#include "e-timezone-entry.h"
+
+#include "e-comp-editor-event.h"
+
+struct _ECompEditorEventPrivate {
+ ECompEditorPage *page_general;
+ ECompEditorPropertyPart *dtstart;
+ ECompEditorPropertyPart *dtend;
+ ECompEditorPropertyPart *categories;
+ ECompEditorPropertyPart *timezone;
+ ECompEditorPropertyPart *transparency;
+ ECompEditorPropertyPart *description;
+ GtkWidget *all_day_check;
+
+ gpointer in_the_past_alert;
+ gpointer insensitive_info_alert;
+};
+
+G_DEFINE_TYPE (ECompEditorEvent, e_comp_editor_event, E_TYPE_COMP_EDITOR)
+
+static void
+ece_event_action_classification_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ ECompEditorEvent *event_editor)
+{
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+
+ e_comp_editor_set_changed (E_COMP_EDITOR (event_editor), TRUE);
+}
+
+static void
+ece_event_update_times (ECompEditorEvent *event_editor,
+ EDateEdit *date_edit,
+ gboolean change_end_datetime)
+{
+ guint flags;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+ g_return_if_fail (E_IS_DATE_EDIT (date_edit));
+
+ if (e_date_edit_has_focus (date_edit) ||
+ !e_date_edit_date_is_valid (date_edit) ||
+ !e_date_edit_time_is_valid (date_edit))
+ return;
+
+ if (!e_comp_editor_get_updating (E_COMP_EDITOR (event_editor))) {
+ e_comp_editor_ensure_start_before_end (E_COMP_EDITOR (event_editor),
+ event_editor->priv->dtstart,
+ event_editor->priv->dtend,
+ change_end_datetime);
+ e_comp_editor_ensure_same_value_type (E_COMP_EDITOR (event_editor),
+ change_end_datetime ? event_editor->priv->dtstart : event_editor->priv->dtend,
+ change_end_datetime ? event_editor->priv->dtend : event_editor->priv->dtstart);
+ }
+
+ flags = e_comp_editor_get_flags (E_COMP_EDITOR (event_editor));
+
+ if ((flags & E_COMP_EDITOR_FLAG_IS_NEW) != 0) {
+ ICalTime *start_tt;
+
+ start_tt = e_comp_editor_property_part_datetime_get_value (
+ E_COMP_EDITOR_PROPERTY_PART_DATETIME (event_editor->priv->dtstart));
+
+ if (cal_comp_util_compare_time_with_today (start_tt) < 0) {
+ if (!event_editor->priv->in_the_past_alert) {
+ EAlert *alert;
+
+ alert = e_comp_editor_add_warning (E_COMP_EDITOR (event_editor),
+ _("Events time is in the past"), NULL);
+
+ event_editor->priv->in_the_past_alert = alert;
+
+ if (alert)
+ g_object_add_weak_pointer (G_OBJECT (alert), &event_editor->priv->in_the_past_alert);
+
+ g_clear_object (&alert);
+ }
+ } else if (event_editor->priv->in_the_past_alert) {
+ e_alert_response (event_editor->priv->in_the_past_alert, GTK_RESPONSE_OK);
+ }
+
+ g_clear_object (&start_tt);
+ }
+}
+
+static void
+ece_event_dtstart_changed_cb (EDateEdit *date_edit,
+ ECompEditorEvent *event_editor)
+{
+ g_return_if_fail (E_IS_DATE_EDIT (date_edit));
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+
+ if (!e_date_edit_has_focus (date_edit))
+ ece_event_update_times (event_editor, date_edit, TRUE);
+}
+
+static void
+ece_event_dtend_changed_cb (EDateEdit *date_edit,
+ ECompEditorEvent *event_editor)
+{
+ g_return_if_fail (E_IS_DATE_EDIT (date_edit));
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+
+ if (!e_date_edit_has_focus (date_edit))
+ ece_event_update_times (event_editor, date_edit, FALSE);
+}
+
+static void
+ece_event_all_day_toggled_cb (ECompEditorEvent *event_editor)
+{
+ GtkWidget *edit_widget;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+
+ edit_widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->dtstart);
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (event_editor->priv->all_day_check))) {
+ gint hour, minute;
+
+ if (!e_date_edit_get_time_of_day (E_DATE_EDIT (edit_widget), &hour, &minute))
+ e_date_edit_set_time_of_day (E_DATE_EDIT (edit_widget), 0, 0);
+ }
+
+ ece_event_update_times (event_editor, E_DATE_EDIT (edit_widget), TRUE);
+
+ e_comp_editor_ensure_changed (E_COMP_EDITOR (event_editor));
+}
+
+static void
+ece_event_sensitize_widgets (ECompEditor *comp_editor,
+ gboolean force_insensitive)
+{
+ ECompEditorEvent *event_editor;
+ gboolean is_organizer;
+ GtkAction *action;
+ GtkWidget *widget;
+ guint32 flags;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (comp_editor));
+
+ E_COMP_EDITOR_CLASS (e_comp_editor_event_parent_class)->sensitize_widgets (comp_editor, force_insensitive);
+
+ flags = e_comp_editor_get_flags (comp_editor);
+ is_organizer = (flags & (E_COMP_EDITOR_FLAG_IS_NEW | E_COMP_EDITOR_FLAG_ORGANIZER_IS_USER)) != 0;
+ event_editor = E_COMP_EDITOR_EVENT (comp_editor);
+
+ gtk_widget_set_sensitive (event_editor->priv->all_day_check, !force_insensitive);
+
+ #define sensitize_part(x) G_STMT_START { \
+ widget = e_comp_editor_property_part_get_label_widget (x); \
+ if (widget) \
+ gtk_widget_set_sensitive (widget, !force_insensitive); \
+ \
+ widget = e_comp_editor_property_part_get_edit_widget (x); \
+ if (widget) \
+ gtk_widget_set_sensitive (widget, !force_insensitive); \
+ } G_STMT_END
+
+ sensitize_part (event_editor->priv->dtstart);
+ sensitize_part (event_editor->priv->dtend);
+ sensitize_part (event_editor->priv->timezone);
+
+ #undef sensitize_part
+
+ action = e_comp_editor_get_action (comp_editor, "all-day-event");
+ gtk_action_set_sensitive (action, !force_insensitive);
+
+ action = e_comp_editor_get_action (comp_editor, "classification-menu");
+ gtk_action_set_sensitive (action, !force_insensitive);
+
+ if (event_editor->priv->insensitive_info_alert)
+ e_alert_response (event_editor->priv->insensitive_info_alert, GTK_RESPONSE_OK);
+
+ if (force_insensitive || !is_organizer) {
+ ECalClient *client;
+ const gchar *message = NULL;
+
+ client = e_comp_editor_get_target_client (comp_editor);
+ if (!client)
+ message = _("Event cannot be edited, because the selected calendar could not be opened");
+ else if (e_client_is_readonly (E_CLIENT (client)))
+ message = _("Event cannot be edited, because the selected calendar is read only");
+ else if (!is_organizer)
+ message = _("Changes made to the event will not be sent to the attendees, because you are not the organizer");
+
+ if (message) {
+ EAlert *alert;
+
+ alert = e_comp_editor_add_information (comp_editor, message, NULL);
+
+ event_editor->priv->insensitive_info_alert = alert;
+
+ if (alert)
+ g_object_add_weak_pointer (G_OBJECT (alert), &event_editor->priv->insensitive_info_alert);
+
+ g_clear_object (&alert);
+ }
+ }
+}
+
+static ICalTimezone *
+ece_event_get_timezone_from_property (ECompEditor *comp_editor,
+ ICalProperty *property)
+{
+ ECalClient *client;
+ ICalParameter *param;
+ ICalTimezone *zone = NULL;
+ const gchar *tzid;
+
+ g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), NULL);
+
+ if (!property)
+ return NULL;
+
+ param = i_cal_property_get_first_parameter (property, I_CAL_TZID_PARAMETER);
+ if (!param)
+ return NULL;
+
+ tzid = i_cal_parameter_get_tzid (param);
+ if (!tzid || !*tzid) {
+ g_object_unref (param);
+ return NULL;
+ }
+
+ if (g_ascii_strcasecmp (tzid, "UTC") == 0) {
+ g_object_unref (param);
+ return i_cal_timezone_get_utc_timezone ();
+ }
+
+ client = e_comp_editor_get_source_client (comp_editor);
+ /* It should be already fetched for the UI, thus this should be non-blocking. */
+ if (client && e_cal_client_get_timezone_sync (client, tzid, &zone, NULL, NULL) && zone) {
+ g_object_unref (param);
+ return zone;
+ }
+
+ zone = i_cal_timezone_get_builtin_timezone_from_tzid (tzid);
+ if (!zone)
+ zone = i_cal_timezone_get_builtin_timezone (tzid);
+
+ g_object_unref (param);
+
+ return zone;
+}
+
+static void
+ece_event_update_timezone (ECompEditorEvent *event_editor,
+ ICalTime **out_dtstart,
+ ICalTime **out_dtend)
+{
+ ECompEditor *comp_editor;
+ ICalTime *dtstart = NULL, *dtend = NULL;
+ ICalComponent *component;
+ ICalProperty *prop;
+ ICalTimezone *zone = NULL;
+ gboolean has_property = FALSE, is_date_value = FALSE;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+
+ comp_editor = E_COMP_EDITOR (event_editor);
+
+ component = e_comp_editor_get_component (comp_editor);
+ if (!component) {
+ if (out_dtstart)
+ *out_dtstart = NULL;
+
+ if (out_dtend)
+ *out_dtend = NULL;
+
+ return;
+ }
+
+ if (e_cal_util_component_has_property (component, I_CAL_DTSTART_PROPERTY)) {
+ has_property = TRUE;
+
+ dtstart = i_cal_component_get_dtstart (component);
+ if (dtstart && i_cal_time_is_valid_time (dtstart)) {
+ if (i_cal_time_is_date (dtstart)) {
+ zone = NULL;
+ is_date_value = TRUE;
+ } else if (i_cal_time_is_utc (dtstart)) {
+ zone = i_cal_timezone_get_utc_timezone ();
+ } else {
+ prop = i_cal_component_get_first_property (component, I_CAL_DTSTART_PROPERTY);
+ zone = ece_event_get_timezone_from_property (comp_editor, prop);
+ g_clear_object (&prop);
+ }
+ }
+ }
+
+ if (e_cal_util_component_has_property (component, I_CAL_DTEND_PROPERTY)) {
+ has_property = TRUE;
+
+ dtend = i_cal_component_get_dtend (component);
+ if (!zone && i_cal_time_is_valid_time (dtend)) {
+ if (i_cal_time_is_date (dtend)) {
+ zone = NULL;
+ is_date_value = TRUE;
+ } else if (i_cal_time_is_utc (dtend)) {
+ zone = i_cal_timezone_get_utc_timezone ();
+ } else {
+ prop = i_cal_component_get_first_property (component, I_CAL_DTEND_PROPERTY);
+ zone = ece_event_get_timezone_from_property (comp_editor, prop);
+ g_clear_object (&prop);
+ }
+ }
+ }
+
+ if (!zone && e_cal_util_component_has_property (component, I_CAL_DUE_PROPERTY)) {
+ ICalTime *itt;
+
+ has_property = TRUE;
+
+ itt = i_cal_component_get_due (component);
+ if (itt && i_cal_time_is_valid_time (itt)) {
+ if (i_cal_time_is_date (itt)) {
+ zone = NULL;
+ is_date_value = TRUE;
+ } else if (i_cal_time_is_utc (itt)) {
+ zone = i_cal_timezone_get_utc_timezone ();
+ } else {
+ prop = i_cal_component_get_first_property (component, I_CAL_DUE_PROPERTY);
+ zone = ece_event_get_timezone_from_property (comp_editor, prop);
+ g_clear_object (&prop);
+ }
+ }
+
+ g_clear_object (&itt);
+ }
+
+ if (has_property) {
+ GtkWidget *edit_widget;
+ ICalTimezone *cfg_zone;
+
+ edit_widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->timezone);
+
+ if (!zone && is_date_value)
+ e_timezone_entry_set_timezone (E_TIMEZONE_ENTRY (edit_widget), calendar_config_get_icaltimezone ());
+ else
+ e_timezone_entry_set_timezone (E_TIMEZONE_ENTRY (edit_widget), zone);
+
+ cfg_zone = calendar_config_get_icaltimezone ();
+
+ if (zone && cfg_zone && zone != cfg_zone &&
+ (g_strcmp0 (i_cal_timezone_get_location (zone), i_cal_timezone_get_location (cfg_zone)) != 0 ||
+ g_strcmp0 (i_cal_timezone_get_tzid (zone), i_cal_timezone_get_tzid (cfg_zone)) != 0)) {
+ /* Show timezone part */
+ GtkAction *action;
+
+ action = e_comp_editor_get_action (comp_editor, "view-timezone");
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+ }
+ }
+
+ if (out_dtstart)
+ *out_dtstart = dtstart;
+ else
+ g_clear_object (&dtstart);
+
+ if (out_dtend)
+ *out_dtend = dtend;
+ else
+ g_clear_object (&dtend);
+}
+
+static void
+ece_event_fill_widgets (ECompEditor *comp_editor,
+ ICalComponent *component)
+{
+ ECompEditorEvent *event_editor;
+ ICalTime *dtstart, *dtend;
+ ICalProperty *prop;
+ gboolean all_day_event = FALSE;
+ GtkAction *action;
+ guint32 flags;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (comp_editor));
+ g_return_if_fail (component != NULL);
+
+ event_editor = E_COMP_EDITOR_EVENT (comp_editor);
+
+ flags = e_comp_editor_get_flags (comp_editor);
+ dtstart = NULL;
+ dtend = NULL;
+
+ /* Set timezone before the times, because they are converted into this timezone */
+ ece_event_update_timezone (event_editor, &dtstart, &dtend);
+
+ E_COMP_EDITOR_CLASS (e_comp_editor_event_parent_class)->fill_widgets (comp_editor, component);
+
+ if (dtstart && i_cal_time_is_valid_time (dtstart) && !i_cal_time_is_null_time (dtstart) &&
+ (!dtend || !i_cal_time_is_valid_time (dtend) || i_cal_time_is_null_time (dtend))) {
+ gboolean dtend_set = FALSE;
+ g_clear_object (&dtend);
+ dtend = i_cal_time_clone (dtstart);
+
+ if (e_cal_util_component_has_property (component, I_CAL_DURATION_PROPERTY)) {
+ ICalProperty *prop;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_DURATION_PROPERTY);
+ if (prop) {
+ ICalDuration *duration;
+
+ g_clear_object (&prop);
+
+ duration = i_cal_component_get_duration (component);
+ if (!duration || i_cal_duration_is_null_duration (duration) || i_cal_duration_is_bad_duration (duration)) {
+ g_clear_object (&duration);
+ /* The DURATION shouldn't be negative, but just return DTSTART if it
+ * is, i.e. assume it is 0. */
+ } else if (!i_cal_duration_is_neg (duration)) {
+ guint dur_days, dur_hours, dur_minutes, dur_seconds;
+
+ /* If DTSTART is a DATE value, then we need to check if the DURATION
+ * includes any hours, minutes or seconds. If it does, we need to
+ * make the DTEND/DUE a DATE-TIME value. */
+ dur_days = i_cal_duration_get_days (duration) + (7 * i_cal_duration_get_weeks (duration));
+ dur_hours = i_cal_duration_get_hours (duration);
+ dur_minutes = i_cal_duration_get_minutes (duration);
+ dur_seconds = i_cal_duration_get_seconds (duration);
+
+ if (i_cal_time_is_date (dtend) && (
+ dur_hours != 0 || dur_minutes != 0 || dur_seconds != 0)) {
+ i_cal_time_set_is_date (dtend, FALSE);
+ }
+
+ /* Add on the DURATION. */
+ i_cal_time_adjust (dtend, dur_days, dur_hours, dur_minutes, dur_seconds);
+
+ dtend_set = TRUE;
+ }
+
+ g_clear_object (&duration);
+ }
+ }
+
+ if (!dtend_set && i_cal_time_is_date (dtstart))
+ i_cal_time_adjust (dtend, 1, 0, 0, 0);
+ }
+
+ if (dtend && i_cal_time_is_valid_time (dtend) && !i_cal_time_is_null_time (dtend)) {
+ if (i_cal_time_is_date (dtstart) && i_cal_time_is_date (dtend)) {
+ all_day_event = TRUE;
+ if (i_cal_time_compare_date_only (dtend, dtstart) > 0) {
+ i_cal_time_adjust (dtend, -1, 0, 0, 0);
+ }
+ }
+
+ e_comp_editor_property_part_datetime_set_value (
+ E_COMP_EDITOR_PROPERTY_PART_DATETIME (event_editor->priv->dtend), dtend);
+ }
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (event_editor->priv->all_day_check), all_day_event);
+
+ prop = i_cal_component_get_first_property (component, I_CAL_CLASS_PROPERTY);
+ if (prop && i_cal_property_get_class (prop) == I_CAL_CLASS_PRIVATE)
+ action = e_comp_editor_get_action (comp_editor, "classify-private");
+ else if (prop && i_cal_property_get_class (prop) == I_CAL_CLASS_CONFIDENTIAL)
+ action = e_comp_editor_get_action (comp_editor, "classify-confidential");
+ else if (!(flags & E_COMP_EDITOR_FLAG_IS_NEW))
+ action = e_comp_editor_get_action (comp_editor, "classify-public");
+ else {
+ GSettings *settings;
+
+ settings = e_util_ref_settings ("org.gnome.evolution.calendar");
+
+ if (g_settings_get_boolean (settings, "classify-private")) {
+ action = e_comp_editor_get_action (comp_editor, "classify-private");
+ } else {
+ action = e_comp_editor_get_action (comp_editor, "classify-public");
+ }
+
+ g_object_unref (settings);
+ }
+
+ gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (action), TRUE);
+
+ g_clear_object (&dtstart);
+ g_clear_object (&dtend);
+ g_clear_object (&prop);
+}
+
+static gboolean
+ece_event_client_needs_all_day_as_time (ECompEditor *comp_editor)
+{
+ ECalClient *client;
+
+ g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
+
+ client = e_comp_editor_get_target_client (comp_editor);
+
+ return client && e_client_check_capability (E_CLIENT (client), E_CAL_STATIC_CAPABILITY_ALL_DAY_EVENT_AS_TIME);
+}
+
+static gboolean
+ece_event_fill_component (ECompEditor *comp_editor,
+ ICalComponent *component)
+{
+ ECompEditorEvent *event_editor;
+ gboolean date_valid, time_valid;
+ ICalProperty *dtstart_prop, *dtend_prop;
+ ICalProperty *prop;
+ ICalProperty_Class class_value;
+
+ g_return_val_if_fail (E_IS_COMP_EDITOR (comp_editor), FALSE);
+ g_return_val_if_fail (I_CAL_IS_COMPONENT (component), FALSE);
+
+ if (!E_COMP_EDITOR_CLASS (e_comp_editor_event_parent_class)->fill_component (comp_editor, component))
+ return FALSE;
+
+ event_editor = E_COMP_EDITOR_EVENT (comp_editor);
+
+ if (!e_comp_editor_property_part_datetime_check_validity (
+ E_COMP_EDITOR_PROPERTY_PART_DATETIME (event_editor->priv->dtstart), &date_valid, &time_valid)) {
+ const gchar *error_message = NULL;
+
+ if (!date_valid)
+ error_message = g_strdup (_("Start date is not a valid date"));
+ else if (!time_valid)
+ error_message = g_strdup (_("Start time is not a valid time"));
+
+ e_comp_editor_set_validation_error (comp_editor, event_editor->priv->page_general,
+ e_comp_editor_property_part_get_edit_widget (event_editor->priv->dtstart),
+ error_message ? error_message : _("Unknown error"));
+
+ return FALSE;
+ }
+
+ if (!e_comp_editor_property_part_datetime_check_validity (
+ E_COMP_EDITOR_PROPERTY_PART_DATETIME (event_editor->priv->dtend), &date_valid, &time_valid)) {
+ const gchar *error_message = NULL;
+
+ if (!date_valid)
+ error_message = g_strdup (_("End date is not a valid date"));
+ else if (!time_valid)
+ error_message = g_strdup (_("End time is not a valid time"));
+
+ e_comp_editor_set_validation_error (comp_editor, event_editor->priv->page_general,
+ e_comp_editor_property_part_get_edit_widget (event_editor->priv->dtend),
+ error_message ? error_message : _("Unknown error"));
+
+ return FALSE;
+ }
+
+ dtstart_prop = i_cal_component_get_first_property (component, I_CAL_DTSTART_PROPERTY);
+ dtend_prop = i_cal_component_get_first_property (component, I_CAL_DTEND_PROPERTY);
+
+ if (dtstart_prop && dtend_prop) {
+ ICalTime *dtstart, *dtend;
+ gboolean set_dtstart = FALSE, set_dtend = FALSE;
+
+ dtstart = i_cal_property_get_dtstart (dtstart_prop);
+ dtend = i_cal_property_get_dtend (dtend_prop);
+
+ if (dtstart && i_cal_time_is_date (dtstart) &&
+ dtend && i_cal_time_is_date (dtend)) {
+ /* Add 1 day to DTEND, as it is not inclusive. */
+ i_cal_time_adjust (dtend, 1, 0, 0, 0);
+ set_dtend = TRUE;
+
+ if (ece_event_client_needs_all_day_as_time (comp_editor)) {
+ ECompEditorEvent *event_editor = E_COMP_EDITOR_EVENT (comp_editor);
+ GtkWidget *timezone_entry;
+ ICalTimezone *zone;
+
+ timezone_entry = e_comp_editor_property_part_get_edit_widget (event_editor->priv->timezone);
+ zone = e_timezone_entry_get_timezone (E_TIMEZONE_ENTRY (timezone_entry));
+
+ cal_comp_util_ensure_allday_timezone (dtstart, zone);
+ cal_comp_util_ensure_allday_timezone (dtend, zone);
+
+ set_dtstart = TRUE;
+ }
+ }
+
+ if (set_dtstart) {
+ /* Remove the VALUE parameter, to correspond to the actual value being set */
+ i_cal_property_remove_parameter_by_kind (dtstart_prop, I_CAL_VALUE_PARAMETER);
+
+ i_cal_property_set_dtstart (dtstart_prop, dtstart);
+ cal_comp_util_update_tzid_parameter (dtstart_prop, dtstart);
+ }
+
+ if (set_dtend) {
+ /* Remove the VALUE parameter, to correspond to the actual value being set */
+ i_cal_property_remove_parameter_by_kind (dtend_prop, I_CAL_VALUE_PARAMETER);
+
+ i_cal_property_set_dtend (dtend_prop, dtend);
+ cal_comp_util_update_tzid_parameter (dtend_prop, dtend);
+
+ e_cal_util_component_remove_property_by_kind (component, I_CAL_DURATION_PROPERTY, TRUE);
+ }
+
+ g_clear_object (&dtstart);
+ g_clear_object (&dtend);
+ }
+
+ g_clear_object (&dtstart_prop);
+ g_clear_object (&dtend_prop);
+
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (
+ e_comp_editor_get_action (comp_editor, "classify-private"))))
+ class_value = I_CAL_CLASS_PRIVATE;
+ else if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (
+ e_comp_editor_get_action (comp_editor, "classify-confidential"))))
+ class_value = I_CAL_CLASS_CONFIDENTIAL;
+ else
+ class_value = I_CAL_CLASS_PUBLIC;
+
+ prop = i_cal_component_get_first_property (component, I_CAL_CLASS_PROPERTY);
+ if (prop) {
+ i_cal_property_set_class (prop, class_value);
+ g_object_unref (prop);
+ } else {
+ prop = i_cal_property_new_class (class_value);
+ i_cal_component_take_property (component, prop);
+ }
+
+ return TRUE;
+}
+
+static void
+ece_event_notify_source_client_cb (GObject *object,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (object));
+
+ ece_event_update_timezone (E_COMP_EDITOR_EVENT (object), NULL, NULL);
+}
+
+static void
+ece_event_notify_target_client_cb (GObject *object,
+ GParamSpec *param,
+ gpointer user_data)
+{
+ ECompEditorEvent *event_editor;
+ GtkAction *action;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (object));
+
+ event_editor = E_COMP_EDITOR_EVENT (object);
+ action = e_comp_editor_get_action (E_COMP_EDITOR (event_editor), "view-timezone");
+
+ /* These influence whether the timezone part is visible and they
+ depend on the target's client ece_event_client_needs_all_day_as_time() */
+ g_object_notify (G_OBJECT (action), "active");
+ g_object_notify (G_OBJECT (event_editor->priv->all_day_check), "active");
+}
+
+static gboolean
+transform_action_to_timezone_visible_cb (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ ECompEditorEvent *event_editor = user_data;
+ GtkToggleButton *all_day_check = GTK_TOGGLE_BUTTON (event_editor->priv->all_day_check);
+
+ g_value_set_boolean (to_value,
+ g_value_get_boolean (from_value) &&
+ (!gtk_toggle_button_get_active (all_day_check) || ece_event_client_needs_all_day_as_time (E_COMP_EDITOR (event_editor))));
+
+ return TRUE;
+}
+
+static gboolean
+transform_toggle_to_timezone_visible_cb (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ ECompEditor *comp_editor = user_data;
+ GtkToggleAction *action = GTK_TOGGLE_ACTION (e_comp_editor_get_action (comp_editor, "view-timezone"));
+
+ g_value_set_boolean (to_value,
+ gtk_toggle_action_get_active (action) &&
+ (!g_value_get_boolean (from_value) || ece_event_client_needs_all_day_as_time (comp_editor)));
+
+ return TRUE;
+}
+
+static gboolean
+transform_all_day_check_to_action_sensitive_cb (GBinding *binding,
+ const GValue *from_value,
+ GValue *to_value,
+ gpointer user_data)
+{
+ ECompEditor *comp_editor = user_data;
+
+ g_value_set_boolean (to_value,
+ !g_value_get_boolean (from_value) ||
+ ece_event_client_needs_all_day_as_time (comp_editor));
+
+ return TRUE;
+}
+
+static void
+ece_event_setup_ui (ECompEditorEvent *event_editor)
+{
+ const gchar *ui =
+ "<ui>"
+ " <menubar action='main-menu'>"
+ " <menu action='view-menu'>"
+ " <placeholder name='parts'>"
+ " <menuitem action='view-timezone'/>"
+ " <menuitem action='view-categories'/>"
+ " </placeholder>"
+ " </menu>"
+ " <menu action='options-menu'>"
+ " <placeholder name='toggles'>"
+ " <menuitem action='all-day-event'/>"
+ " <menuitem action='show-time-busy'/>"
+ " <menu action='classification-menu'>"
+ " <menuitem action='classify-public'/>"
+ " <menuitem action='classify-private'/>"
+ " <menuitem action='classify-confidential'/>"
+ " </menu>"
+ " </placeholder>"
+ " </menu>"
+ " </menubar>"
+ " <toolbar name='main-toolbar'>"
+ " <placeholder name='content'>\n"
+ " <toolitem action='all-day-event'/>\n"
+ " <toolitem action='show-time-busy'/>\n"
+ " </placeholder>"
+ " </toolbar>"
+ "</ui>";
+
+ const GtkToggleActionEntry view_actions[] = {
+ { "view-categories",
+ NULL,
+ N_("_Categories"),
+ NULL,
+ N_("Toggles whether to display categories"),
+ NULL,
+ FALSE },
+
+ { "view-timezone",
+ "stock_timezone",
+ N_("Time _Zone"),
+ NULL,
+ N_("Toggles whether the time zone is displayed"),
+ NULL,
+ FALSE },
+
+ { "all-day-event",
+ "stock_new-24h-appointment",
+ N_("All _Day Event"),
+ "<Control>Y",
+ N_("Toggles whether to have All Day Event"),
+ NULL,
+ FALSE },
+
+ { "show-time-busy",
+ "dialog-error",
+ N_("Show Time as _Busy"),
+ NULL,
+ N_("Toggles whether to show time as busy"),
+ NULL,
+ FALSE }
+ };
+
+ const GtkRadioActionEntry classification_radio_entries[] = {
+
+ { "classify-public",
+ NULL,
+ N_("Pu_blic"),
+ NULL,
+ N_("Classify as public"),
+ I_CAL_CLASS_PUBLIC },
+
+ { "classify-private",
+ NULL,
+ N_("_Private"),
+ NULL,
+ N_("Classify as private"),
+ I_CAL_CLASS_PRIVATE },
+
+ { "classify-confidential",
+ NULL,
+ N_("_Confidential"),
+ NULL,
+ N_("Classify as confidential"),
+ I_CAL_CLASS_CONFIDENTIAL }
+ };
+
+ ECompEditor *comp_editor;
+ GSettings *settings;
+ GtkUIManager *ui_manager;
+ GtkAction *action;
+ GtkActionGroup *action_group;
+ GtkWidget *widget;
+ GError *error = NULL;
+
+ g_return_if_fail (E_IS_COMP_EDITOR_EVENT (event_editor));
+
+ comp_editor = E_COMP_EDITOR (event_editor);
+ settings = e_comp_editor_get_settings (comp_editor);
+ ui_manager = e_comp_editor_get_ui_manager (comp_editor);
+ action_group = e_comp_editor_get_action_group (comp_editor, "individual");
+
+ gtk_action_group_add_toggle_actions (action_group,
+ view_actions, G_N_ELEMENTS (view_actions), event_editor);
+
+ gtk_action_group_add_radio_actions (
+ action_group, classification_radio_entries,
+ G_N_ELEMENTS (classification_radio_entries),
+ I_CAL_CLASS_PUBLIC,
+ G_CALLBACK (ece_event_action_classification_cb), event_editor);
+
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+
+ e_plugin_ui_register_manager (ui_manager, "org.gnome.evolution.event-editor", event_editor);
+ e_plugin_ui_enable_manager (ui_manager, "org.gnome.evolution.event-editor");
+
+ if (error) {
+ g_critical ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ action = e_comp_editor_get_action (comp_editor, "view-categories");
+ e_binding_bind_property (
+ event_editor->priv->categories, "visible",
+ action, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+ g_settings_bind (
+ settings, "editor-show-categories",
+ action, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ action = e_comp_editor_get_action (comp_editor, "view-timezone");
+ e_binding_bind_property_full (
+ action, "active",
+ event_editor->priv->timezone, "visible",
+ G_BINDING_DEFAULT,
+ transform_action_to_timezone_visible_cb,
+ NULL, /* not used */
+ event_editor, NULL);
+ e_binding_bind_property_full (
+ event_editor->priv->all_day_check, "active",
+ event_editor->priv->timezone, "visible",
+ G_BINDING_DEFAULT,
+ transform_toggle_to_timezone_visible_cb,
+ NULL, /* not used */
+ event_editor, NULL);
+ e_binding_bind_property_full (
+ event_editor->priv->all_day_check, "active",
+ action, "sensitive",
+ G_BINDING_SYNC_CREATE,
+ transform_all_day_check_to_action_sensitive_cb,
+ NULL, /* not used */
+ event_editor, NULL);
+ g_settings_bind (
+ settings, "editor-show-timezone",
+ action, "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ action = e_comp_editor_get_action (comp_editor, "all-day-event");
+ e_binding_bind_property (
+ event_editor->priv->all_day_check, "active",
+ action, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+
+ widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->transparency);
+ action = e_comp_editor_get_action (comp_editor, "show-time-busy");
+ e_binding_bind_property (
+ widget, "active",
+ action, "active",
+ G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
+}
+
+static void
+e_comp_editor_event_constructed (GObject *object)
+{
+ ECompEditor *comp_editor;
+ ECompEditorEvent *event_editor;
+ ECompEditorPage *page;
+ ECompEditorPropertyPart *part;
+ ECompEditorPropertyPart *summary;
+ EFocusTracker *focus_tracker;
+ GtkWidget *widget;
+
+ G_OBJECT_CLASS (e_comp_editor_event_parent_class)->constructed (object);
+
+ event_editor = E_COMP_EDITOR_EVENT (object);
+ comp_editor = E_COMP_EDITOR (event_editor);
+ focus_tracker = e_comp_editor_get_focus_tracker (comp_editor);
+
+ page = e_comp_editor_page_general_new (comp_editor,
+ _("_Calendar:"), E_SOURCE_EXTENSION_CALENDAR,
+ NULL, FALSE, 2);
+ event_editor->priv->page_general = page;
+
+ part = e_comp_editor_property_part_summary_new (focus_tracker);
+ e_comp_editor_page_add_property_part (page, part, 0, 2, 3, 1);
+ summary = part;
+
+ part = e_comp_editor_property_part_location_new (focus_tracker);
+ e_comp_editor_page_add_property_part (page, part, 0, 3, 3, 1);
+
+ part = e_comp_editor_property_part_dtstart_new (C_("ECompEditor", "_Start time:"), FALSE, FALSE, TRUE);
+ e_comp_editor_page_add_property_part (page, part, 0, 4, 2, 1);
+ e_comp_editor_property_part_set_sensitize_handled (part, TRUE);
+ event_editor->priv->dtstart = part;
+
+ part = e_comp_editor_property_part_dtend_new (C_("ECompEditor", "_End time:"), FALSE, FALSE);
+ e_comp_editor_page_add_property_part (page, part, 0, 5, 2, 1);
+ e_comp_editor_property_part_set_sensitize_handled (part, TRUE);
+ event_editor->priv->dtend = part;
+
+ part = e_comp_editor_property_part_timezone_new ();
+ e_comp_editor_page_add_property_part (page, part, 0, 6, 3, 1);
+ e_comp_editor_property_part_set_sensitize_handled (part, TRUE);
+ event_editor->priv->timezone = part;
+
+ widget = gtk_check_button_new_with_mnemonic (C_("ECompEditor", "All da_y event"));
+ g_object_set (G_OBJECT (widget),
+ "hexpand", TRUE,
+ "halign", GTK_ALIGN_FILL,
+ "vexpand", FALSE,
+ "valign", GTK_ALIGN_START,
+ NULL);
+ gtk_grid_attach (GTK_GRID (page), widget, 2, 4, 1, 1);
+ gtk_widget_show (widget);
+ event_editor->priv->all_day_check = widget;
+
+ part = e_comp_editor_property_part_transparency_new ();
+ e_comp_editor_page_add_property_part (page, part, 2, 5, 1, 1);
+ event_editor->priv->transparency = part;
+
+ widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->transparency);
+ /* Transparency checkbox is not shown in the page, even it's packed there */
+ gtk_widget_hide (widget);
+
+ part = e_comp_editor_property_part_status_new (I_CAL_VEVENT_COMPONENT);
+ e_comp_editor_page_add_property_part (page, part, 0, 7, 3, 1);
+
+ widget = e_comp_editor_property_part_get_edit_widget (part);
+ gtk_widget_set_halign (widget, GTK_ALIGN_START);
+ gtk_widget_set_hexpand (widget, FALSE);
+
+ part = e_comp_editor_property_part_url_new (focus_tracker);
+ e_comp_editor_page_add_property_part (page, part, 0, 8, 3, 1);
+
+ part = e_comp_editor_property_part_categories_new (focus_tracker);
+ e_comp_editor_page_add_property_part (page, part, 0, 9, 3, 1);
+ event_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, 3, 1);
+ event_editor->priv->description = part;
+
+ widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->timezone);
+ e_comp_editor_property_part_datetime_attach_timezone_entry (
+ E_COMP_EDITOR_PROPERTY_PART_DATETIME (event_editor->priv->dtstart),
+ E_TIMEZONE_ENTRY (widget));
+ g_signal_connect_swapped (event_editor->priv->dtstart, "lookup-timezone",
+ G_CALLBACK (e_comp_editor_lookup_timezone), event_editor);
+ e_comp_editor_property_part_datetime_attach_timezone_entry (
+ E_COMP_EDITOR_PROPERTY_PART_DATETIME (event_editor->priv->dtend),
+ E_TIMEZONE_ENTRY (widget));
+ g_signal_connect_swapped (event_editor->priv->dtend, "lookup-timezone",
+ G_CALLBACK (e_comp_editor_lookup_timezone), event_editor);
+
+ e_comp_editor_set_time_parts (comp_editor, event_editor->priv->dtstart, event_editor->priv->dtend);
+
+ widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->dtstart);
+ e_binding_bind_property (
+ event_editor->priv->all_day_check, "active",
+ widget, "show-time",
+ G_BINDING_INVERT_BOOLEAN | G_BINDING_BIDIRECTIONAL);
+ g_signal_connect (widget, "changed", G_CALLBACK (ece_event_dtstart_changed_cb), event_editor);
+
+ widget = e_comp_editor_property_part_get_edit_widget (event_editor->priv->dtend);
+ e_binding_bind_property (
+ event_editor->priv->all_day_check, "active",
+ widget, "show-time",
+ G_BINDING_INVERT_BOOLEAN | G_BINDING_BIDIRECTIONAL);
+ g_signal_connect (widget, "changed", G_CALLBACK (ece_event_dtend_changed_cb), event_editor);
+
+ e_signal_connect_notify_swapped (event_editor->priv->all_day_check, "notify::active",
+ G_CALLBACK (ece_event_all_day_toggled_cb), event_editor);
+
+ e_comp_editor_add_page (comp_editor, C_("ECompEditorPage", "General"), page);
+
+ page = e_comp_editor_page_reminders_new (comp_editor);
+ e_comp_editor_add_page (comp_editor, C_("ECompEditorPage", "Reminders"), page);
+
+ page = e_comp_editor_page_recurrence_new (comp_editor);
+ e_comp_editor_add_page (comp_editor, C_("ECompEditorPage", "Recurrence"), page);
+
+ page = e_comp_editor_page_attachments_new (comp_editor);
+ e_comp_editor_add_page (comp_editor, C_("ECompEditorPage", "Attachments"), page);
+
+ page = e_comp_editor_page_schedule_new (comp_editor,
+ e_comp_editor_page_general_get_meeting_store (
+ E_COMP_EDITOR_PAGE_GENERAL (event_editor->priv->page_general)));
+ e_binding_bind_property (
+ event_editor->priv->page_general, "show-attendees",
+ page, "visible",
+ G_BINDING_SYNC_CREATE);
+
+ e_comp_editor_add_page (comp_editor, C_("ECompEditorPage", "Schedule"), page);
+
+ ece_event_setup_ui (event_editor);
+
+ widget = e_comp_editor_property_part_get_edit_widget (summary);
+ e_binding_bind_property (widget, "text", comp_editor, "title-suffix", 0);
+ /* Do this as the last thing, because some widgets can call the function as well */
+ gtk_widget_grab_focus (widget);
+
+ g_signal_connect (comp_editor, "notify::source-client",
+ G_CALLBACK (ece_event_notify_source_client_cb), NULL);
+
+ g_signal_connect (comp_editor, "notify::target-client",
+ G_CALLBACK (ece_event_notify_target_client_cb), NULL);
+}
+
+static void
+e_comp_editor_event_init (ECompEditorEvent *event_editor)
+{
+ event_editor->priv = G_TYPE_INSTANCE_GET_PRIVATE (event_editor, E_TYPE_COMP_EDITOR_EVENT, ECompEditorEventPrivate);
+}
+
+static void
+e_comp_editor_event_class_init (ECompEditorEventClass *klass)
+{
+ GObjectClass *object_class;
+ ECompEditorClass *comp_editor_class;
+
+ g_type_class_add_private (klass, sizeof (ECompEditorEventPrivate));
+
+ object_class = G_OBJECT_CLASS (klass);
+ object_class->constructed = e_comp_editor_event_constructed;
+
+ comp_editor_class = E_COMP_EDITOR_CLASS (klass);
+ comp_editor_class->help_section = "calendar-usage-add-appointment";
+ comp_editor_class->title_format_with_attendees = _("Meeting — %s");
+ comp_editor_class->title_format_without_attendees = _("Appointment — %s");
+ comp_editor_class->icon_name = "appointment-new";
+ comp_editor_class->sensitize_widgets = ece_event_sensitize_widgets;
+ comp_editor_class->fill_widgets = ece_event_fill_widgets;
+ comp_editor_class->fill_component = ece_event_fill_component;
+}
diff -urN a/src/calendar/gui/e-comp-editor-page-attachments.c b/src/calendar/gui/e-comp-editor-page-attachments.c
--- a/src/calendar/gui/e-comp-editor-page-attachments.c 2025-06-07 14:20:57.267396453 -0700
+++ b/src/calendar/gui/e-comp-editor-page-attachments.c 2025-06-07 14:27:24.035672846 -0700
@@ -621,7 +621,7 @@
{ "attachments-attach",
"mail-attachment",
N_("_Attachment…"),
- "<Control>m",
+ "<Super>m",
N_("Attach a file"),
G_CALLBACK (ecep_attachments_action_attach_cb) }
};
diff -urN a/src/calendar/gui/e-comp-editor-task.c b/src/calendar/gui/e-comp-editor-task.c
--- a/src/calendar/gui/e-comp-editor-task.c 2025-06-07 14:20:57.271396501 -0700
+++ b/src/calendar/gui/e-comp-editor-task.c 2025-06-07 14:27:24.035672846 -0700
@@ -816,7 +816,7 @@
{ "all-day-task",
"stock_new-24h-appointment",
N_("All _Day Task"),
- "<Control>Y",
+ "<Super>Y",
N_("Toggles whether to have All Day Task"),
NULL,
FALSE }
diff -urN a/src/calendar/gui/e-comp-editor.c b/src/calendar/gui/e-comp-editor.c
--- a/src/calendar/gui/e-comp-editor.c 2025-06-07 14:20:57.271396501 -0700
+++ b/src/calendar/gui/e-comp-editor.c 2025-06-07 14:27:24.039672886 -0700
@@ -2171,21 +2171,21 @@
{ "close",
"window-close",
N_("_Close"),
- "<Control>w",
+ "<Super>w",
N_("Close the current window"),
G_CALLBACK (action_close_cb) },
{ "copy-clipboard",
"edit-copy",
N_("_Copy"),
- "<Control>c",
+ "<Super>c",
N_("Copy the selection"),
NULL }, /* Handled by EFocusTracker */
{ "cut-clipboard",
"edit-cut",
N_("Cu_t"),
- "<Control>x",
+ "<Super>x",
N_("Cut the selection"),
NULL }, /* Handled by EFocusTracker */
@@ -2206,14 +2206,14 @@
{ "paste-clipboard",
"edit-paste",
N_("_Paste"),
- "<Control>v",
+ "<Super>v",
N_("Paste the clipboard"),
NULL }, /* Handled by EFocusTracker */
{ "print",
"document-print",
N_("_Print…"),
- "<Control>p",
+ "<Super>p",
NULL,
G_CALLBACK (action_print_cb) },
@@ -2227,21 +2227,21 @@
{ "select-all",
"edit-select-all",
N_("Select _All"),
- "<Control>a",
+ "<Super>a",
N_("Select all text"),
NULL }, /* Handled by EFocusTracker */
{ "undo",
"edit-undo",
N_("_Undo"),
- "<Control>z",
+ "<Super>z",
N_("Undo"),
NULL }, /* Handled by EFocusTracker */
{ "redo",
"edit-redo",
N_("_Redo"),
- "<Control>y",
+ "<Super>y",
N_("Redo"),
NULL }, /* Handled by EFocusTracker */
@@ -2302,14 +2302,14 @@
{ "save",
"document-save",
N_("_Save"),
- "<Control>s",
+ "<Super>s",
N_("Save current changes"),
G_CALLBACK (action_save_cb) },
{ "save-and-close",
NULL,
N_("Save and Close"),
- "<Control>Return",
+ "<Alt><Super>S",
N_("Save current changes and close editor"),
G_CALLBACK (action_save_and_close_cb) }
};
diff -urN a/src/calendar/gui/ea-cal-view.c b/src/calendar/gui/ea-cal-view.c
--- a/src/calendar/gui/ea-cal-view.c 2025-06-07 14:20:30.163068622 -0700
+++ b/src/calendar/gui/ea-cal-view.c 2025-06-07 14:27:24.039672886 -0700
@@ -371,19 +371,19 @@
switch (index) {
case 0:
/* New Appointment */
- return "<Alt>fna;<Control>n";
+ return "<Alt>fna;<Super>n";
case 1:
/* New Event */
- return "<Alt>fnd;<Shift><Control>d";
+ return "<Alt>fnd;<Shift><Super>d";
case 2:
/* New Meeting */
- return "<Alt>fne;<Shift><Control>e";
+ return "<Alt>fne;<Shift><Super>e";
case 3:
/* Go to today */
- return "<Alt>vt;<Alt><Control>t";
+ return "<Alt>vt;<Alt><Super>t";
case 4:
/* Go to date */
- return "<Alt>vd;<Alt><Control>g";
+ return "<Alt>vd;<Alt><Super>g";
default:
break;
}
diff -urN a/src/composer/e-composer-actions.c b/src/composer/e-composer-actions.c
--- a/src/composer/e-composer-actions.c 2025-06-07 14:20:57.283396645 -0700
+++ b/src/composer/e-composer-actions.c 2025-06-07 14:27:24.043672927 -0700
@@ -324,21 +324,21 @@
{ "attach",
"mail-attachment",
N_("_Attachment…"),
- "<Control>m",
+ "<Super>m",
N_("Attach a file"),
G_CALLBACK (action_attach_cb) },
{ "close",
"window-close",
N_("_Close"),
- "<Control>w",
+ "<Super>w",
N_("Close the current file"),
G_CALLBACK (action_close_cb) },
{ "new-message",
"mail-message-new",
N_("New _Message"),
- "<Control>n",
+ "<Super>n",
N_("Open New Message window"),
G_CALLBACK (action_new_message_cb) },
@@ -352,7 +352,7 @@
{ "save",
"document-save",
N_("_Save"),
- "<Shift><Control>s",
+ "<Shift><Super>s",
N_("Save the current file"),
G_CALLBACK (action_save_cb) },
@@ -385,28 +385,28 @@
{ "print",
"document-print",
N_("_Print…"),
- "<Control>p",
+ "<Super>p",
NULL,
G_CALLBACK (action_print_cb) },
{ "print-preview",
"document-print-preview",
N_("Print Pre_view"),
- "<Shift><Control>p",
+ "<Shift><Super>p",
NULL,
G_CALLBACK (action_print_preview_cb) },
{ "save-draft",
"document-save",
N_("Save as _Draft"),
- "<Control>s",
+ "<Super>s",
N_("Save as draft"),
G_CALLBACK (action_save_draft_cb) },
{ "send",
"mail-send",
N_("S_end"),
- "<Control>Return",
+ "<Alt><Super>s",
N_("Send this message"),
G_CALLBACK (action_send_cb) },
};
diff -urN a/src/e-util/e-html-editor-actions.c b/src/e-util/e-html-editor-actions.c
--- a/src/e-util/e-html-editor-actions.c 2025-06-07 14:20:57.303396885 -0700
+++ b/src/e-util/e-html-editor-actions.c 2025-06-07 14:27:24.047672970 -0700
@@ -1124,42 +1124,42 @@
{ "copy",
"edit-copy",
N_("_Copy"),
- "<Control>c",
+ "<Super>c",
N_("Copy selected text to the clipboard"),
NULL }, /* Handled by focus tracker */
{ "cut",
"edit-cut",
N_("Cu_t"),
- "<Control>x",
+ "<Super>x",
N_("Cut selected text to the clipboard"),
NULL }, /* Handled by focus tracker */
{ "paste",
"edit-paste",
N_("_Paste"),
- "<Control>v",
+ "<Super>v",
N_("Paste text from the clipboard"),
NULL }, /* Handled by focus tracker */
{ "redo",
"edit-redo",
N_("_Redo"),
- "<Shift><Control>z",
+ "<Shift><Super>z",
N_("Redo the last undone action"),
G_CALLBACK (action_redo_cb) },
{ "select-all",
"edit-select-all",
N_("Select _All"),
- "<Control>a",
+ "<Super>a",
NULL,
NULL }, /* Handled by focus tracker */
{ "undo",
"edit-undo",
N_("_Undo"),
- "<Control>z",
+ "<Super>z",
N_("Undo the last action"),
G_CALLBACK (action_undo_cb) },
@@ -1227,7 +1227,7 @@
{ "indent",
"format-indent-more",
N_("_Increase Indent"),
- "<Control>bracketright",
+ "<Super>bracketright",
N_("Increase Indent"),
G_CALLBACK (action_indent_cb) },
@@ -1262,28 +1262,28 @@
{ "paste-quote",
NULL,
N_("Paste _Quotation"),
- "<Control><Alt>v",
+ "<Super><Alt>v",
NULL,
G_CALLBACK (action_paste_quote_cb) },
{ "show-find",
"edit-find",
N_("_Find…"),
- "<Control>f",
+ "<Super>f",
N_("Search for text"),
G_CALLBACK (action_show_find_cb) },
{ "find-again",
NULL,
N_("Find A_gain"),
- "<Control>g",
+ "<Super>g",
NULL,
G_CALLBACK (action_find_again_cb) },
{ "show-replace",
"edit-find-replace",
N_("Re_place…"),
- "<Control>h",
+ "<Super>h",
N_("Search for and replace text"),
G_CALLBACK (action_show_replace_cb) },
@@ -1297,14 +1297,14 @@
{ "unindent",
"format-indent-less",
N_("_Decrease Indent"),
- "<Control>bracketleft",
+ "<Super>bracketleft",
N_("Decrease Indent"),
G_CALLBACK (action_unindent_cb) },
{ "wrap-lines",
NULL,
N_("_Wrap Lines"),
- "<Control><Shift>k",
+ "<Super><Shift>k",
NULL,
G_CALLBACK (action_wrap_lines_cb) }
};
@@ -1314,28 +1314,28 @@
{ "justify-center",
"format-justify-center",
N_("_Center"),
- "<Control>e",
+ "<Super>e",
N_("Center Alignment"),
E_CONTENT_EDITOR_ALIGNMENT_CENTER },
{ "justify-fill",
"format-justify-fill",
N_("_Justified"),
- "<Control>j",
+ "<Super>j",
N_("Align Justified"),
E_CONTENT_EDITOR_ALIGNMENT_JUSTIFY },
{ "justify-left",
"format-justify-left",
N_("_Left"),
- "<Control>l",
+ "<Super>l",
N_("Left Alignment"),
E_CONTENT_EDITOR_ALIGNMENT_LEFT },
{ "justify-right",
"format-justify-right",
N_("_Right"),
- "<Control>r",
+ "<Super>r",
N_("Right Alignment"),
E_CONTENT_EDITOR_ALIGNMENT_RIGHT }
};
@@ -1383,63 +1383,63 @@
{ "style-normal",
NULL,
N_("_Normal"),
- "<Control>0",
+ "<Super>0",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_PARAGRAPH },
{ "style-h1",
NULL,
N_("Heading _1"),
- "<Control>1",
+ "<Super>1",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_H1 },
{ "style-h2",
NULL,
N_("Heading _2"),
- "<Control>2",
+ "<Super>2",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_H2 },
{ "style-h3",
NULL,
N_("Heading _3"),
- "<Control>3",
+ "<Super>3",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_H3 },
{ "style-h4",
NULL,
N_("Heading _4"),
- "<Control>4",
+ "<Super>4",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_H4 },
{ "style-h5",
NULL,
N_("Heading _5"),
- "<Control>5",
+ "<Super>5",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_H5 },
{ "style-h6",
NULL,
N_("Heading _6"),
- "<Control>6",
+ "<Super>6",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_H6 },
{ "style-preformat",
NULL,
N_("_Preformatted"),
- "<Control>7",
+ "<Super>7",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_PRE },
{ "style-address",
NULL,
N_("A_ddress"),
- "<Control>8",
+ "<Super>8",
NULL,
E_CONTENT_EDITOR_BLOCK_FORMAT_ADDRESS },
@@ -1491,7 +1491,7 @@
{ "insert-link",
"insert-link",
N_("_Link…"),
- "<Control>k",
+ "<Super>k",
N_("Insert Link"),
G_CALLBACK (action_insert_link_cb) },
@@ -1573,7 +1573,7 @@
{ "paste-as-text",
NULL,
N_("Paste As _Text"),
- "<Shift><Control>v",
+ "<Shift><Super>v",
NULL,
G_CALLBACK (action_paste_as_text_cb) },
@@ -1584,7 +1584,7 @@
{ "bold",
"format-text-bold",
N_("_Bold"),
- "<Control>b",
+ "<Super>b",
N_("Bold"),
NULL,
FALSE },
@@ -1592,7 +1592,7 @@
{ "italic",
"format-text-italic",
N_("_Italic"),
- "<Control>i",
+ "<Super>i",
N_("Italic"),
NULL,
FALSE },
@@ -1608,7 +1608,7 @@
{ "subscript",
NULL,
N_("Subs_cript"),
- "<Control><Shift>b",
+ "<Super><Shift>b",
N_("Subscript"),
NULL,
FALSE },
@@ -1616,7 +1616,7 @@
{ "superscript",
NULL,
N_("Su_perscript"),
- "<Control><Shift>p",
+ "<Super><Shift>p",
N_("Superscript"),
NULL,
FALSE },
@@ -1624,7 +1624,7 @@
{ "underline",
"format-text-underline",
N_("_Underline"),
- "<Control>u",
+ "<Super>u",
N_("Underline"),
NULL,
FALSE }
diff -urN a/src/e-util/e-mail-signature-editor.c b/src/e-util/e-mail-signature-editor.c
--- a/src/e-util/e-mail-signature-editor.c 2025-06-07 14:20:57.307396934 -0700
+++ b/src/e-util/e-mail-signature-editor.c 2025-06-07 14:27:24.047672970 -0700
@@ -332,14 +332,14 @@
{ "close",
"window-close",
N_("_Close"),
- "<Control>w",
+ "<Super>w",
N_("Close"),
G_CALLBACK (action_close_cb) },
{ "save-and-close",
"document-save",
N_("_Save and Close"),
- "<Control>Return",
+ "<Alt><Super>s",
N_("Save and Close"),
G_CALLBACK (action_save_and_close_cb) },
diff -urN a/src/e-util/e-web-view.c b/src/e-util/e-web-view.c
--- a/src/e-util/e-web-view.c 2025-06-07 14:20:57.327397173 -0700
+++ b/src/e-util/e-web-view.c 2025-06-07 14:27:24.051673010 -0700
@@ -404,7 +404,7 @@
{ "uri-copy",
"edit-copy",
N_("_Copy Link Location"),
- "<Control>c",
+ "<Super>c",
N_("Copy the link to the clipboard"),
G_CALLBACK (action_uri_copy_cb) }
};
@@ -424,7 +424,7 @@
{ "mailto-copy",
"edit-copy",
N_("_Copy Email Address"),
- "<Control>c",
+ "<Super>c",
N_("Copy the email address to the clipboard"),
G_CALLBACK (action_mailto_copy_cb) },
@@ -448,14 +448,14 @@
{ "image-copy",
"edit-copy",
N_("_Copy Image"),
- "<Control>c",
+ "<Super>c",
N_("Copy the image to the clipboard"),
G_CALLBACK (action_image_copy_cb) },
{ "image-save",
"document-save",
N_("Save _Image…"),
- "<Control>s",
+ "<Super>s",
N_("Save the image to a file"),
G_CALLBACK (action_image_save_cb) }
};
@@ -465,7 +465,7 @@
{ "copy-clipboard",
"edit-copy",
N_("_Copy"),
- "<Control>c",
+ "<Super>c",
N_("Copy the selection"),
G_CALLBACK (action_copy_clipboard_cb) },
diff -urN a/src/e-util/e-web-view.c.orig b/src/e-util/e-web-view.c.orig
--- a/src/e-util/e-web-view.c.orig 1969-12-31 16:00:00.000000000 -0800
+++ b/src/e-util/e-web-view.c.orig 2025-06-07 14:20:57.327397173 -0700
@@ -0,0 +1,4439 @@
+/*
+ * e-web-view.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "evolution-config.h"
+
+#include <glib/gi18n-lib.h>
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pango/pango.h>
+
+#include <camel/camel.h>
+#include <libebackend/libebackend.h>
+
+#include <libsoup/soup.h>
+
+#include "e-alert-dialog.h"
+#include "e-alert-sink.h"
+#include "e-file-request.h"
+#include "e-misc-utils.h"
+#include "e-plugin-ui.h"
+#include "e-popup-action.h"
+#include "e-selectable.h"
+#include "e-stock-request.h"
+#include "e-web-view-jsc-utils.h"
+
+#include "e-web-view.h"
+
+#define E_WEB_VIEW_GET_PRIVATE(obj) \
+ (G_TYPE_INSTANCE_GET_PRIVATE \
+ ((obj), E_TYPE_WEB_VIEW, EWebViewPrivate))
+
+typedef struct _AsyncContext AsyncContext;
+
+typedef struct _ElementClickedData {
+ EWebViewElementClickedFunc callback;
+ gpointer user_data;
+} ElementClickedData;
+
+struct _EWebViewPrivate {
+ GtkUIManager *ui_manager;
+ gchar *selected_uri;
+ gchar *cursor_image_src;
+
+ GQueue highlights;
+ gboolean highlights_enabled;
+
+ GtkAction *open_proxy;
+ GtkAction *print_proxy;
+ GtkAction *save_as_proxy;
+
+ /* Lockdown Options */
+ gboolean disable_printing;
+ gboolean disable_save_to_disk;
+
+ gboolean caret_mode;
+
+ GSettings *font_settings;
+ gulong font_name_changed_handler_id;
+ gulong monospace_font_name_changed_handler_id;
+
+ GHashTable *scheme_handlers; /* gchar *scheme ~> EContentRequest */
+ GHashTable *old_settings;
+
+ WebKitFindController *find_controller;
+ gulong found_text_handler_id;
+ gulong failed_to_find_text_handler_id;
+
+ gboolean has_hover_link;
+
+ GHashTable *element_clicked_cbs; /* gchar *element_class ~> GPtrArray {ElementClickedData} */
+
+ gboolean has_selection;
+ gboolean need_input;
+
+ GCancellable *cancellable;
+
+ gchar *last_popup_iframe_src;
+ gchar *last_popup_iframe_id;
+ gchar *last_popup_element_id;
+ gchar *last_popup_link_uri;
+
+ gint minimum_font_size;
+};
+
+struct _AsyncContext {
+ EActivity *activity;
+ GFile *destination;
+ GInputStream *input_stream;
+ EContentRequest *content_request;
+ gchar *uri;
+};
+
+enum {
+ PROP_0,
+ PROP_CARET_MODE,
+ PROP_COPY_TARGET_LIST,
+ PROP_CURSOR_IMAGE_SRC,
+ PROP_DISABLE_PRINTING,
+ PROP_DISABLE_SAVE_TO_DISK,
+ PROP_HAS_SELECTION,
+ PROP_NEED_INPUT,
+ PROP_MINIMUM_FONT_SIZE,
+ PROP_OPEN_PROXY,
+ PROP_PASTE_TARGET_LIST,
+ PROP_PRINT_PROXY,
+ PROP_SAVE_AS_PROXY,
+ PROP_SELECTED_URI
+};
+
+enum {
+ NEW_ACTIVITY,
+ POPUP_EVENT,
+ STATUS_MESSAGE,
+ STOP_LOADING,
+ UPDATE_ACTIONS,
+ PROCESS_MAILTO,
+ URI_REQUESTED,
+ CONTENT_LOADED,
+ BEFORE_POPUP_EVENT,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+static const gchar *ui =
+"<ui>"
+" <popup name='context'>"
+" <menuitem action='copy-clipboard'/>"
+" <menuitem action='search-web'/>"
+" <separator/>"
+" <placeholder name='custom-actions-1'>"
+" <menuitem action='open'/>"
+" <menuitem action='save-as'/>"
+" <menuitem action='http-open'/>"
+" <menuitem action='send-message'/>"
+" <menuitem action='print'/>"
+" </placeholder>"
+" <placeholder name='custom-actions-2'>"
+" <menuitem action='uri-copy'/>"
+" <menuitem action='mailto-copy'/>"
+" <menuitem action='mailto-copy-raw'/>"
+" <menuitem action='image-copy'/>"
+" <menuitem action='image-save'/>"
+" </placeholder>"
+" <placeholder name='custom-actions-3'/>"
+" <separator/>"
+" <menuitem action='select-all'/>"
+" <placeholder name='inspect-menu' />"
+" </popup>"
+"</ui>";
+
+/* Forward Declarations */
+static void e_web_view_alert_sink_init (EAlertSinkInterface *iface);
+static void e_web_view_selectable_init (ESelectableInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (
+ EWebView,
+ e_web_view,
+ WEBKIT_TYPE_WEB_VIEW,
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_EXTENSIBLE, NULL)
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_ALERT_SINK,
+ e_web_view_alert_sink_init)
+ G_IMPLEMENT_INTERFACE (
+ E_TYPE_SELECTABLE,
+ e_web_view_selectable_init))
+
+static void
+async_context_free (gpointer ptr)
+{
+ AsyncContext *async_context = ptr;
+
+ if (!async_context)
+ return;
+
+ g_clear_object (&async_context->activity);
+ g_clear_object (&async_context->destination);
+ g_clear_object (&async_context->input_stream);
+ g_clear_object (&async_context->content_request);
+ g_free (async_context->uri);
+
+ g_slice_free (AsyncContext, async_context);
+}
+
+static void
+action_copy_clipboard_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ e_web_view_copy_clipboard (web_view);
+}
+
+static void
+e_web_view_search_web_get_selection_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSList *texts;
+ GError *local_error = NULL;
+
+ g_return_if_fail (E_IS_WEB_VIEW (source));
+
+ e_web_view_jsc_get_selection_finish (WEBKIT_WEB_VIEW (source), result, &texts, &local_error);
+
+ if (local_error &&
+ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ e_alert_submit (E_ALERT_SINK (source), "widgets:get-selected-text-failed", local_error->message, NULL);
+ } else if (texts) {
+ GSettings *settings;
+ gchar *text = texts->data;
+ gchar *uri_prefix;
+ gchar *escaped;
+ gchar *uri;
+
+ g_strstrip (text);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.shell");
+ uri_prefix = g_settings_get_string (settings, "search-web-uri-prefix");
+ g_object_unref (settings);
+
+ escaped = camel_url_encode (text, "& ?#:;,/\\");
+
+ uri = g_strconcat (uri_prefix, escaped, NULL);
+ if (uri && g_ascii_strncasecmp (uri, "https://", 8) == 0) {
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (source));
+
+ e_show_uri (GTK_IS_WINDOW (toplevel) ? GTK_WINDOW (toplevel) : NULL, uri);
+ } else {
+ g_printerr ("Incorrect URI provided, expects https:// prefix, but has got: '%s'\n", uri ? uri : "null");
+ }
+
+ g_free (uri_prefix);
+ g_free (escaped);
+ g_free (uri);
+ }
+
+ g_clear_error (&local_error);
+ g_slist_free_full (texts, g_free);
+}
+
+static void
+action_search_web_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ e_web_view_jsc_get_selection (WEBKIT_WEB_VIEW (web_view), E_TEXT_FORMAT_PLAIN, web_view->priv->cancellable,
+ e_web_view_search_web_get_selection_cb, NULL);
+}
+
+static void
+action_http_open_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ const gchar *uri;
+ gpointer parent;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ e_show_uri (parent, uri);
+}
+
+static void
+webview_mailto_copy (EWebView *web_view,
+ gboolean only_email_address)
+{
+ CamelURL *curl;
+ CamelInternetAddress *inet_addr;
+ GtkClipboard *clipboard;
+ const gchar *uri, *name = NULL, *email = NULL;
+ gchar *text;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ /* This should work because we checked it in update_actions(). */
+ curl = camel_url_new (uri, NULL);
+ g_return_if_fail (curl != NULL);
+
+ inet_addr = camel_internet_address_new ();
+ camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
+ if (only_email_address &&
+ camel_internet_address_get (inet_addr, 0, &name, &email) &&
+ email && *email) {
+ text = g_strdup (email);
+ } else {
+ text = camel_address_format (CAMEL_ADDRESS (inet_addr));
+ if (text == NULL || *text == '\0')
+ text = g_strdup (uri + strlen ("mailto:"));
+ }
+
+ g_object_unref (inet_addr);
+ camel_url_free (curl);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+ gtk_clipboard_set_text (clipboard, text, -1);
+ gtk_clipboard_store (clipboard);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text (clipboard, text, -1);
+ gtk_clipboard_store (clipboard);
+
+ g_free (text);
+}
+
+static void
+action_mailto_copy_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ webview_mailto_copy (web_view, FALSE);
+}
+
+static void
+action_mailto_copy_raw_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ webview_mailto_copy (web_view, TRUE);
+}
+
+static void
+action_select_all_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ e_web_view_select_all (web_view);
+}
+
+static void
+action_send_message_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ const gchar *uri;
+ gpointer parent;
+ gboolean handled;
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ handled = FALSE;
+ g_signal_emit (web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
+
+ if (!handled)
+ e_show_uri (parent, uri);
+}
+
+static void
+action_uri_copy_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ GtkClipboard *clipboard;
+ const gchar *uri;
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
+ gtk_clipboard_set_text (clipboard, uri, -1);
+ gtk_clipboard_store (clipboard);
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_text (clipboard, uri, -1);
+ gtk_clipboard_store (clipboard);
+}
+
+static void
+action_image_copy_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ e_web_view_cursor_image_copy (web_view);
+}
+
+static void
+action_image_save_cb (GtkAction *action,
+ EWebView *web_view)
+{
+ e_web_view_cursor_image_save (web_view);
+}
+
+static GtkActionEntry uri_entries[] = {
+
+ { "uri-copy",
+ "edit-copy",
+ N_("_Copy Link Location"),
+ "<Control>c",
+ N_("Copy the link to the clipboard"),
+ G_CALLBACK (action_uri_copy_cb) }
+};
+
+static GtkActionEntry http_entries[] = {
+
+ { "http-open",
+ "emblem-web",
+ N_("_Open Link in Browser"),
+ NULL,
+ N_("Open the link in a web browser"),
+ G_CALLBACK (action_http_open_cb) }
+};
+
+static GtkActionEntry mailto_entries[] = {
+
+ { "mailto-copy",
+ "edit-copy",
+ N_("_Copy Email Address"),
+ "<Control>c",
+ N_("Copy the email address to the clipboard"),
+ G_CALLBACK (action_mailto_copy_cb) },
+
+ { "mailto-copy-raw",
+ "edit-copy",
+ N_("Copy _Raw Email Address"),
+ NULL,
+ N_("Copy the raw email address to the clipboard"),
+ G_CALLBACK (action_mailto_copy_raw_cb) },
+
+ { "send-message",
+ "mail-message-new",
+ N_("_Send New Message To…"),
+ NULL,
+ N_("Send a mail message to this address"),
+ G_CALLBACK (action_send_message_cb) }
+};
+
+static GtkActionEntry image_entries[] = {
+
+ { "image-copy",
+ "edit-copy",
+ N_("_Copy Image"),
+ "<Control>c",
+ N_("Copy the image to the clipboard"),
+ G_CALLBACK (action_image_copy_cb) },
+
+ { "image-save",
+ "document-save",
+ N_("Save _Image…"),
+ "<Control>s",
+ N_("Save the image to a file"),
+ G_CALLBACK (action_image_save_cb) }
+};
+
+static GtkActionEntry selection_entries[] = {
+
+ { "copy-clipboard",
+ "edit-copy",
+ N_("_Copy"),
+ "<Control>c",
+ N_("Copy the selection"),
+ G_CALLBACK (action_copy_clipboard_cb) },
+
+ { "search-web",
+ NULL,
+ N_("Search _Web…"),
+ NULL,
+ N_("Search the Web with the selected text"),
+ G_CALLBACK (action_search_web_cb) }
+};
+
+static GtkActionEntry standard_entries[] = {
+
+ { "select-all",
+ "edit-select-all",
+ N_("Select _All"),
+ NULL,
+ N_("Select all text and images"),
+ G_CALLBACK (action_select_all_cb) }
+};
+
+static void
+web_view_menu_item_select_cb (EWebView *web_view,
+ GtkWidget *widget)
+{
+ GtkAction *action;
+ GtkActivatable *activatable;
+ const gchar *tooltip;
+
+ activatable = GTK_ACTIVATABLE (widget);
+ action = gtk_activatable_get_related_action (activatable);
+ tooltip = gtk_action_get_tooltip (action);
+
+ if (tooltip == NULL)
+ return;
+
+ e_web_view_status_message (web_view, tooltip);
+}
+
+static void
+webkit_find_controller_found_text_cb (WebKitFindController *find_controller,
+ guint match_count,
+ EWebView *web_view)
+{
+ if (web_view->priv->highlights_enabled && !g_queue_is_empty (&web_view->priv->highlights))
+ e_web_view_unselect_all (web_view);
+}
+
+static void
+webkit_find_controller_failed_to_found_text_cb (WebKitFindController *find_controller,
+ EWebView *web_view)
+{
+}
+
+static void
+web_view_set_find_controller (EWebView *web_view)
+{
+ WebKitFindController *find_controller;
+
+ find_controller =
+ webkit_web_view_get_find_controller (WEBKIT_WEB_VIEW (web_view));
+
+ web_view->priv->found_text_handler_id = g_signal_connect (
+ find_controller, "found-text",
+ G_CALLBACK (webkit_find_controller_found_text_cb), web_view);
+
+ web_view->priv->failed_to_find_text_handler_id = g_signal_connect (
+ find_controller, "failed-to-find-text",
+ G_CALLBACK (webkit_find_controller_failed_to_found_text_cb), web_view);
+
+ web_view->priv->find_controller = find_controller;
+}
+
+static void
+web_view_update_document_highlights (EWebView *web_view)
+{
+ GList *head, *link;
+
+ head = g_queue_peek_head_link (&web_view->priv->highlights);
+
+ for (link = head; link != NULL; link = g_list_next (link)) {
+ webkit_find_controller_search (
+ web_view->priv->find_controller,
+ link->data,
+ WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE,
+ G_MAXUINT);
+ }
+}
+
+static void
+web_view_menu_item_deselect_cb (EWebView *web_view)
+{
+ e_web_view_status_message (web_view, NULL);
+}
+
+static void
+web_view_connect_proxy_cb (EWebView *web_view,
+ GtkAction *action,
+ GtkWidget *proxy)
+{
+ if (!GTK_IS_MENU_ITEM (proxy))
+ return;
+
+ g_signal_connect_swapped (
+ proxy, "select",
+ G_CALLBACK (web_view_menu_item_select_cb), web_view);
+
+ g_signal_connect_swapped (
+ proxy, "deselect",
+ G_CALLBACK (web_view_menu_item_deselect_cb), web_view);
+}
+
+static void
+web_view_got_elem_from_point_for_popup_event_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EWebView *web_view;
+ GdkEvent *event = user_data;
+ GError *error = NULL;
+
+ g_return_if_fail (E_IS_WEB_VIEW (source_object));
+
+ web_view = E_WEB_VIEW (source_object);
+
+ g_clear_pointer (&web_view->priv->last_popup_iframe_src, g_free);
+ g_clear_pointer (&web_view->priv->last_popup_iframe_id, g_free);
+ g_clear_pointer (&web_view->priv->last_popup_element_id, g_free);
+
+ if (!e_web_view_jsc_get_element_from_point_finish (WEBKIT_WEB_VIEW (web_view), result,
+ &web_view->priv->last_popup_iframe_src,
+ &web_view->priv->last_popup_iframe_id,
+ &web_view->priv->last_popup_element_id,
+ &error)) {
+ g_warning ("%s: Failed to get element from point: %s", G_STRFUNC, error ? error->message : "Unknown error");
+ }
+
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ gboolean handled = FALSE;
+
+ g_signal_emit (web_view, signals[BEFORE_POPUP_EVENT], 0,
+ web_view->priv->last_popup_link_uri, NULL);
+
+ g_signal_emit (web_view, signals[POPUP_EVENT], 0,
+ web_view->priv->last_popup_link_uri, event, &handled);
+ }
+
+ if (event)
+ gdk_event_free (event);
+ g_clear_error (&error);
+}
+
+static gboolean
+web_view_context_menu_cb (WebKitWebView *webkit_web_view,
+ WebKitContextMenu *context_menu,
+ GdkEvent *event,
+ WebKitHitTestResult *hit_test_result,
+ gpointer user_data)
+{
+ WebKitHitTestResultContext context;
+ EWebView *web_view;
+ gchar *link_uri = NULL;
+ gdouble xx, yy;
+
+ web_view = E_WEB_VIEW (webkit_web_view);
+
+ g_clear_pointer (&web_view->priv->cursor_image_src, g_free);
+ g_clear_pointer (&web_view->priv->last_popup_iframe_src, g_free);
+ g_clear_pointer (&web_view->priv->last_popup_iframe_id, g_free);
+ g_clear_pointer (&web_view->priv->last_popup_element_id, g_free);
+ g_clear_pointer (&web_view->priv->last_popup_link_uri, g_free);
+
+ if (!hit_test_result)
+ return FALSE;
+
+ context = webkit_hit_test_result_get_context (hit_test_result);
+
+ if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE) {
+ gchar *image_uri = NULL;
+
+ g_object_get (hit_test_result, "image-uri", &image_uri, NULL);
+
+ web_view->priv->cursor_image_src = image_uri;
+ }
+
+ if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK)
+ g_object_get (hit_test_result, "link-uri", &link_uri, NULL);
+
+ web_view->priv->last_popup_link_uri = link_uri;
+
+ if (!gdk_event_get_coords (event, &xx, &yy)) {
+ xx = 1;
+ yy = 1;
+ }
+
+ e_web_view_jsc_get_element_from_point (WEBKIT_WEB_VIEW (web_view), xx, yy, web_view->priv->cancellable,
+ web_view_got_elem_from_point_for_popup_event_cb, event ? gdk_event_copy (event) : NULL);
+
+ return TRUE;
+}
+
+static void
+web_view_mouse_target_changed_cb (EWebView *web_view,
+ WebKitHitTestResult *hit_test_result,
+ guint modifiers,
+ gpointer user_data)
+{
+ EWebViewClass *class;
+ const gchar *title, *uri;
+
+ title = webkit_hit_test_result_get_link_title (hit_test_result);
+ uri = webkit_hit_test_result_get_link_uri (hit_test_result);
+
+ web_view->priv->has_hover_link = uri && *uri;
+
+ /* XXX WebKitWebView does not provide a class method for
+ * this signal, so we do so we can override the default
+ * behavior from subclasses for special URI types. */
+
+ class = E_WEB_VIEW_GET_CLASS (web_view);
+ g_return_if_fail (class != NULL);
+ g_return_if_fail (class->hovering_over_link != NULL);
+
+ class->hovering_over_link (web_view, title, uri);
+}
+
+static gboolean
+web_view_decide_policy_cb (EWebView *web_view,
+ WebKitPolicyDecision *decision,
+ WebKitPolicyDecisionType type)
+{
+ EWebViewClass *class;
+ WebKitNavigationPolicyDecision *navigation_decision;
+ WebKitNavigationAction *navigation_action;
+ WebKitNavigationType navigation_type;
+ WebKitURIRequest *request;
+ const gchar *uri, *view_uri;
+
+ if (type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION &&
+ type != WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION)
+ return FALSE;
+
+ navigation_decision = WEBKIT_NAVIGATION_POLICY_DECISION (decision);
+ navigation_action = webkit_navigation_policy_decision_get_navigation_action (navigation_decision);
+ navigation_type = webkit_navigation_action_get_navigation_type (navigation_action);
+
+ if (navigation_type != WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
+ return FALSE;
+
+ request = webkit_navigation_action_get_request (navigation_action);
+ uri = webkit_uri_request_get_uri (request);
+ view_uri = webkit_web_view_get_uri (WEBKIT_WEB_VIEW (web_view));
+
+ /* Allow navigation through fragments in page */
+ if (uri && *uri && view_uri && *view_uri) {
+ GUri *uri_link, *uri_view;
+
+ uri_link = g_uri_parse (uri, SOUP_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL);
+ uri_view = g_uri_parse (view_uri, SOUP_HTTP_URI_FLAGS | G_URI_FLAGS_PARSE_RELAXED, NULL);
+ if (uri_link && uri_view) {
+ const gchar *tmp1, *tmp2;
+
+ tmp1 = g_uri_get_scheme (uri_link);
+ tmp2 = g_uri_get_scheme (uri_view);
+
+ /* The scheme on both URIs should be the same */
+ if (tmp1 && tmp2 && g_ascii_strcasecmp (tmp1, tmp2) != 0)
+ goto free_uris;
+
+ tmp1 = g_uri_get_host (uri_link);
+ tmp2 = g_uri_get_host (uri_view);
+
+ /* The host on both URIs should be the same */
+ if (tmp1 && tmp2 && g_ascii_strcasecmp (tmp1, tmp2) != 0)
+ goto free_uris;
+
+ /* URI from link should have fragment set - could be empty */
+ if (g_uri_get_fragment (uri_link)) {
+ g_uri_unref (uri_link);
+ g_uri_unref (uri_view);
+ webkit_policy_decision_use (decision);
+ return TRUE;
+ }
+ }
+
+ free_uris:
+ if (uri_link)
+ g_uri_unref (uri_link);
+ if (uri_view)
+ g_uri_unref (uri_view);
+ }
+
+ /* XXX WebKitWebView does not provide a class method for
+ * this signal, so we do so we can override the default
+ * behavior from subclasses for special URI types. */
+
+ class = E_WEB_VIEW_GET_CLASS (web_view);
+ g_return_val_if_fail (class != NULL, FALSE);
+ g_return_val_if_fail (class->link_clicked != NULL, FALSE);
+
+ webkit_policy_decision_ignore (decision);
+
+ class->link_clicked (web_view, uri);
+
+ return TRUE;
+}
+
+static void
+e_web_view_update_styles (EWebView *web_view,
+ const gchar *iframe_id)
+{
+ GdkRGBA color;
+ gchar *color_value;
+ gchar *style;
+ GtkStyleContext *style_context;
+ WebKitWebView *webkit_web_view;
+
+ style_context = gtk_widget_get_style_context (GTK_WIDGET (web_view));
+
+ if (gtk_style_context_lookup_color (style_context, "theme_base_color", &color))
+ color_value = g_strdup_printf ("#%06x", e_rgba_to_value (&color));
+ else {
+ color_value = g_strdup (E_UTILS_DEFAULT_THEME_BASE_COLOR);
+ if (!gdk_rgba_parse (&color, color_value)) {
+ color.red = 1.0;
+ color.green = 1.0;
+ color.blue = 1.0;
+ color.alpha = 1.0;
+ }
+ }
+
+ style = g_strconcat ("background-color: ", color_value, ";", NULL);
+
+ webkit_web_view = WEBKIT_WEB_VIEW (web_view);
+
+ webkit_web_view_set_background_color (webkit_web_view, &color);
+
+ e_web_view_jsc_add_rule_into_style_sheet (webkit_web_view,
+ iframe_id,
+ "-e-web-view-style-sheet",
+ ".-e-web-view-background-color",
+ style,
+ web_view->priv->cancellable);
+
+ g_free (color_value);
+ g_free (style);
+
+ if (gtk_style_context_lookup_color (style_context, "theme_fg_color", &color))
+ color_value = g_strdup_printf ("#%06x", e_rgba_to_value (&color));
+ else
+ color_value = g_strdup (E_UTILS_DEFAULT_THEME_FG_COLOR);
+
+ style = g_strconcat ("color: ", color_value, ";", NULL);
+
+ e_web_view_jsc_add_rule_into_style_sheet (webkit_web_view,
+ iframe_id,
+ "-e-web-view-style-sheet",
+ ".-e-web-view-text-color",
+ style,
+ web_view->priv->cancellable);
+
+ g_free (color_value);
+ g_free (style);
+
+ e_web_view_jsc_add_rule_into_style_sheet (webkit_web_view,
+ iframe_id,
+ "-e-web-view-style-sheet",
+ "body, div, p, td",
+ "unicode-bidi: plaintext;",
+ web_view->priv->cancellable);
+}
+
+static void
+style_updated_cb (EWebView *web_view)
+{
+ e_web_view_update_styles (web_view, "*");
+}
+
+static void
+e_web_view_set_has_selection (EWebView *web_view,
+ gboolean has_selection)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if ((!web_view->priv->has_selection) == (!has_selection))
+ return;
+
+ web_view->priv->has_selection = has_selection;
+
+ g_object_notify (G_OBJECT (web_view), "has-selection");
+}
+
+static void
+web_view_load_changed_cb (WebKitWebView *webkit_web_view,
+ WebKitLoadEvent load_event,
+ gpointer user_data)
+{
+ EWebView *web_view;
+
+ web_view = E_WEB_VIEW (webkit_web_view);
+
+ if (load_event == WEBKIT_LOAD_STARTED) {
+ g_hash_table_remove_all (web_view->priv->element_clicked_cbs);
+ e_web_view_set_has_selection (web_view, FALSE);
+ }
+
+ if (load_event != WEBKIT_LOAD_FINISHED)
+ return;
+
+ /* Make sure the initialize function is called for the top document when it is loaded. */
+ e_web_view_jsc_run_script (webkit_web_view, web_view->priv->cancellable,
+ "Evo.EnsureMainDocumentInitialized();");
+
+ e_web_view_update_styles (web_view, "");
+
+ web_view_update_document_highlights (web_view);
+}
+
+static GObjectConstructParam*
+find_property (guint n_properties,
+ GObjectConstructParam* properties,
+ GParamSpec* param_spec)
+{
+ while (n_properties--) {
+ if (properties->pspec == param_spec)
+ return properties;
+ properties++;
+ }
+
+ return NULL;
+}
+
+static void
+web_view_uri_request_done_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ WebKitURISchemeRequest *request = user_data;
+ GInputStream *stream = NULL;
+ gint64 stream_length = -1;
+ gchar *mime_type = NULL;
+ GError *error = NULL;
+
+ g_return_if_fail (E_IS_CONTENT_REQUEST (source_object));
+ g_return_if_fail (WEBKIT_IS_URI_SCHEME_REQUEST (request));
+
+ if (!e_content_request_process_finish (E_CONTENT_REQUEST (source_object),
+ result, &stream, &stream_length, &mime_type, &error)) {
+ if (!error)
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED, "Failed to get '%s'", webkit_uri_scheme_request_get_uri (request));
+ webkit_uri_scheme_request_finish_error (request, error);
+ g_clear_error (&error);
+ } else {
+ webkit_uri_scheme_request_finish (request, stream, stream_length, mime_type);
+
+ g_clear_object (&stream);
+ g_free (mime_type);
+ }
+
+ g_object_unref (request);
+}
+
+static void
+e_web_view_process_uri_request (EWebView *web_view,
+ WebKitURISchemeRequest *request)
+{
+ EContentRequest *content_request;
+ const gchar *scheme;
+ const gchar *uri;
+ gchar *redirect_to_uri = NULL;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (WEBKIT_IS_URI_SCHEME_REQUEST (request));
+
+ scheme = webkit_uri_scheme_request_get_scheme (request);
+ g_return_if_fail (scheme != NULL);
+
+ content_request = g_hash_table_lookup (web_view->priv->scheme_handlers, scheme);
+
+ if (!content_request) {
+ g_warning ("%s: Cannot find handler for scheme '%s'", G_STRFUNC, scheme);
+ return;
+ }
+
+ uri = webkit_uri_scheme_request_get_uri (request);
+
+ g_return_if_fail (e_content_request_can_process_uri (content_request, uri));
+
+ /* Expects an empty string to abandon the request,
+ or NULL to keep the passed-in uri,
+ or a new uri to load instead. */
+ g_signal_emit (web_view, signals[URI_REQUESTED], 0, uri, &redirect_to_uri);
+
+ if (redirect_to_uri && *redirect_to_uri) {
+ uri = redirect_to_uri;
+ } else if (redirect_to_uri) {
+ GError *error;
+
+ g_free (redirect_to_uri);
+
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_CANCELLED, "Cancelled");
+
+ webkit_uri_scheme_request_finish_error (request, error);
+ g_clear_error (&error);
+
+ return;
+ }
+
+ e_content_request_process (content_request, uri, G_OBJECT (web_view), web_view->priv->cancellable,
+ web_view_uri_request_done_cb, g_object_ref (request));
+
+ g_free (redirect_to_uri);
+}
+
+static void
+web_view_process_uri_request_cb (WebKitURISchemeRequest *request,
+ gpointer user_data)
+{
+ WebKitWebView *web_view;
+
+ web_view = webkit_uri_scheme_request_get_web_view (request);
+
+ if (E_IS_WEB_VIEW (web_view)) {
+ e_web_view_process_uri_request (E_WEB_VIEW (web_view), request);
+ } else {
+ GError *error;
+
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, "Unexpected WebView type");
+ webkit_uri_scheme_request_finish_error (request, error);
+ g_clear_error (&error);
+
+ g_warning ("%s: Unexpected WebView type '%s' received", G_STRFUNC, web_view ? G_OBJECT_TYPE_NAME (web_view) : "null");
+
+ return;
+ }
+}
+
+static GSList *known_schemes = NULL;
+
+static void
+web_view_web_context_gone (gpointer user_data,
+ GObject *obj)
+{
+ gpointer *pweb_context = user_data;
+
+ g_return_if_fail (pweb_context != NULL);
+
+ *pweb_context = NULL;
+
+ g_slist_free_full (known_schemes, g_free);
+ known_schemes = NULL;
+}
+
+static void
+web_view_ensure_scheme_known (WebKitWebContext *web_context,
+ const gchar *scheme)
+{
+ GSList *link;
+
+ g_return_if_fail (WEBKIT_IS_WEB_CONTEXT (web_context));
+ g_return_if_fail (scheme != NULL);
+
+ for (link = known_schemes; link; link = g_slist_next (link)) {
+ if (g_strcmp0 (scheme, link->data) == 0)
+ break;
+ }
+
+ if (!link) {
+ known_schemes = g_slist_prepend (known_schemes, g_strdup (scheme));
+
+ webkit_web_context_register_uri_scheme (web_context, scheme, web_view_process_uri_request_cb, NULL, NULL);
+ }
+}
+
+static GObject*
+web_view_constructor (GType type,
+ guint n_construct_properties,
+ GObjectConstructParam *construct_properties)
+{
+ GObjectClass* object_class;
+ GParamSpec* param_spec;
+ GObjectConstructParam *param = NULL;
+
+ object_class = G_OBJECT_CLASS (g_type_class_ref(type));
+ g_return_val_if_fail (object_class != NULL, NULL);
+
+ if (construct_properties && n_construct_properties != 0) {
+ param_spec = g_object_class_find_property (object_class, "settings");
+ if ((param = find_property (n_construct_properties, construct_properties, param_spec)))
+ g_value_take_object (param->value, e_web_view_get_default_webkit_settings ());
+ param_spec = g_object_class_find_property(object_class, "user-content-manager");
+ if ((param = find_property (n_construct_properties, construct_properties, param_spec)))
+ g_value_take_object (param->value, webkit_user_content_manager_new ());
+ param_spec = g_object_class_find_property (object_class, "web-context");
+ if ((param = find_property (n_construct_properties, construct_properties, param_spec))) {
+ /* Share one web_context between all previews. */
+ static gpointer web_context = NULL;
+
+ if (!web_context) {
+ GSList *link;
+ gchar *plugins_path;
+ #ifdef ENABLE_MAINTAINER_MODE
+ const gchar *source_webkitdatadir;
+ #endif
+
+ web_context = webkit_web_context_new ();
+
+ webkit_web_context_set_cache_model (web_context, WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
+ webkit_web_context_set_web_extensions_directory (web_context, EVOLUTION_WEB_EXTENSIONS_DIR);
+ webkit_web_context_set_sandbox_enabled (web_context, TRUE);
+ webkit_web_context_add_path_to_sandbox (web_context, EVOLUTION_WEBKITDATADIR, TRUE);
+
+ plugins_path = g_build_filename (e_get_user_data_dir (), "preview-plugins", NULL);
+ webkit_web_context_add_path_to_sandbox (web_context, plugins_path, TRUE);
+ g_free (plugins_path);
+
+ #ifdef ENABLE_MAINTAINER_MODE
+ source_webkitdatadir = g_getenv ("EVOLUTION_SOURCE_WEBKITDATADIR");
+ if (source_webkitdatadir && *source_webkitdatadir)
+ webkit_web_context_add_path_to_sandbox (web_context, source_webkitdatadir, TRUE);
+ #endif
+
+ g_object_weak_ref (G_OBJECT (web_context), web_view_web_context_gone, &web_context);
+
+ for (link = known_schemes; link; link = g_slist_next (link)) {
+ const gchar *scheme = link->data;
+
+ webkit_web_context_register_uri_scheme (web_context, scheme, web_view_process_uri_request_cb, NULL, NULL);
+ }
+ } else {
+ g_object_ref (web_context);
+ }
+
+ g_value_take_object (param->value, web_context);
+ }
+ }
+
+ g_type_class_unref (object_class);
+
+ return G_OBJECT_CLASS (e_web_view_parent_class)->constructor (type, n_construct_properties, construct_properties);
+}
+
+static void
+web_view_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CARET_MODE:
+ e_web_view_set_caret_mode (
+ E_WEB_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_COPY_TARGET_LIST:
+ /* This is a fake property. */
+ g_warning ("%s: EWebView::copy-target-list not used", G_STRFUNC);
+ return;
+
+ case PROP_CURSOR_IMAGE_SRC:
+ e_web_view_set_cursor_image_src (
+ E_WEB_VIEW (object),
+ g_value_get_string (value));
+ return;
+
+ case PROP_DISABLE_PRINTING:
+ e_web_view_set_disable_printing (
+ E_WEB_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_DISABLE_SAVE_TO_DISK:
+ e_web_view_set_disable_save_to_disk (
+ E_WEB_VIEW (object),
+ g_value_get_boolean (value));
+ return;
+
+ case PROP_MINIMUM_FONT_SIZE:
+ e_web_view_set_minimum_font_size (
+ E_WEB_VIEW (object),
+ g_value_get_int (value));
+ return;
+
+ case PROP_OPEN_PROXY:
+ e_web_view_set_open_proxy (
+ E_WEB_VIEW (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_PASTE_TARGET_LIST:
+ /* This is a fake property. */
+ g_warning ("%s: EWebView::paste-target-list not used", G_STRFUNC);
+ return;
+
+ case PROP_PRINT_PROXY:
+ e_web_view_set_print_proxy (
+ E_WEB_VIEW (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SAVE_AS_PROXY:
+ e_web_view_set_save_as_proxy (
+ E_WEB_VIEW (object),
+ g_value_get_object (value));
+ return;
+
+ case PROP_SELECTED_URI:
+ e_web_view_set_selected_uri (
+ E_WEB_VIEW (object),
+ g_value_get_string (value));
+ return;
+ }
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id) {
+ case PROP_CARET_MODE:
+ g_value_set_boolean (
+ value, e_web_view_get_caret_mode (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_COPY_TARGET_LIST:
+ /* This is a fake property. */
+ g_value_set_boxed (value, NULL);
+ return;
+
+ case PROP_CURSOR_IMAGE_SRC:
+ g_value_set_string (
+ value, e_web_view_get_cursor_image_src (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_DISABLE_PRINTING:
+ g_value_set_boolean (
+ value, e_web_view_get_disable_printing (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_DISABLE_SAVE_TO_DISK:
+ g_value_set_boolean (
+ value, e_web_view_get_disable_save_to_disk (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_HAS_SELECTION:
+ g_value_set_boolean (value, e_web_view_has_selection (E_WEB_VIEW (object)));
+ return;
+
+ case PROP_MINIMUM_FONT_SIZE:
+ g_value_set_int (
+ value, e_web_view_get_minimum_font_size (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_NEED_INPUT:
+ g_value_set_boolean (
+ value, e_web_view_get_need_input (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_OPEN_PROXY:
+ g_value_set_object (
+ value, e_web_view_get_open_proxy (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_PASTE_TARGET_LIST:
+ /* This is a fake property. */
+ g_value_set_boxed (value, NULL);
+ return;
+
+ case PROP_PRINT_PROXY:
+ g_value_set_object (
+ value, e_web_view_get_print_proxy (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_SAVE_AS_PROXY:
+ g_value_set_object (
+ value, e_web_view_get_save_as_proxy (
+ E_WEB_VIEW (object)));
+ return;
+
+ case PROP_SELECTED_URI:
+ g_value_set_string (
+ value, e_web_view_get_selected_uri (
+ E_WEB_VIEW (object)));
+ return;
+ }
+
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+web_view_dispose (GObject *object)
+{
+ EWebViewPrivate *priv;
+
+ /* This can be called during dispose, thus disconnect early */
+ g_signal_handlers_disconnect_by_func (object, G_CALLBACK (style_updated_cb), NULL);
+
+ priv = E_WEB_VIEW_GET_PRIVATE (object);
+
+ if (priv->cancellable) {
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ }
+
+ if (priv->font_name_changed_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->font_settings,
+ priv->font_name_changed_handler_id);
+ priv->font_name_changed_handler_id = 0;
+ }
+
+ if (priv->monospace_font_name_changed_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->font_settings,
+ priv->monospace_font_name_changed_handler_id);
+ priv->monospace_font_name_changed_handler_id = 0;
+ }
+
+ if (priv->found_text_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->find_controller,
+ priv->found_text_handler_id);
+ priv->found_text_handler_id = 0;
+ }
+
+ if (priv->failed_to_find_text_handler_id > 0) {
+ g_signal_handler_disconnect (
+ priv->find_controller,
+ priv->failed_to_find_text_handler_id);
+ priv->failed_to_find_text_handler_id = 0;
+ }
+
+ g_hash_table_remove_all (priv->scheme_handlers);
+ g_hash_table_remove_all (priv->element_clicked_cbs);
+
+ g_clear_object (&priv->ui_manager);
+ g_clear_object (&priv->open_proxy);
+ g_clear_object (&priv->print_proxy);
+ g_clear_object (&priv->save_as_proxy);
+ g_clear_object (&priv->font_settings);
+
+ /* Chain up to parent's dispose() method. */
+ G_OBJECT_CLASS (e_web_view_parent_class)->dispose (object);
+}
+
+static void
+web_view_finalize (GObject *object)
+{
+ EWebViewPrivate *priv;
+
+ priv = E_WEB_VIEW_GET_PRIVATE (object);
+
+ g_clear_pointer (&priv->last_popup_iframe_src, g_free);
+ g_clear_pointer (&priv->last_popup_iframe_id, g_free);
+ g_clear_pointer (&priv->last_popup_element_id, g_free);
+ g_clear_pointer (&priv->last_popup_link_uri, g_free);
+ g_free (priv->selected_uri);
+ g_free (priv->cursor_image_src);
+
+ while (!g_queue_is_empty (&priv->highlights))
+ g_free (g_queue_pop_head (&priv->highlights));
+
+ g_clear_pointer (&priv->old_settings, g_hash_table_destroy);
+
+ g_hash_table_destroy (priv->scheme_handlers);
+ g_hash_table_destroy (priv->element_clicked_cbs);
+
+ /* Chain up to parent's finalize() method. */
+ G_OBJECT_CLASS (e_web_view_parent_class)->finalize (object);
+}
+
+/* 'scheme' is like "file", not "file:" */
+void
+e_web_view_register_content_request_for_scheme (EWebView *web_view,
+ const gchar *scheme,
+ EContentRequest *content_request)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (E_IS_CONTENT_REQUEST (content_request));
+ g_return_if_fail (scheme != NULL);
+
+ g_hash_table_insert (web_view->priv->scheme_handlers, g_strdup (scheme), g_object_ref (content_request));
+
+ web_view_ensure_scheme_known (webkit_web_view_get_context (WEBKIT_WEB_VIEW (web_view)), scheme);
+}
+
+static void
+web_view_initialize (WebKitWebView *web_view)
+{
+ EContentRequest *content_request;
+ GSettings *font_settings;
+
+ content_request = e_file_request_new ();
+ e_web_view_register_content_request_for_scheme (E_WEB_VIEW (web_view), "evo-file", content_request);
+ g_object_unref (content_request);
+
+ content_request = e_stock_request_new ();
+ e_binding_bind_property (web_view, "scale-factor",
+ content_request, "scale-factor",
+ G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
+ e_web_view_register_content_request_for_scheme (E_WEB_VIEW (web_view), "gtk-stock", content_request);
+ g_object_unref (content_request);
+
+ font_settings = e_util_ref_settings ("org.gnome.desktop.interface");
+
+ e_web_view_update_fonts_settings (font_settings, NULL, NULL, GTK_WIDGET (web_view));
+
+ g_object_unref (font_settings);
+}
+
+static void
+e_web_view_initialize_web_extensions_cb (WebKitWebContext *web_context,
+ gpointer user_data)
+{
+ EWebView *web_view = user_data;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_context_set_web_extensions_directory (web_context, EVOLUTION_WEB_EXTENSIONS_DIR);
+}
+
+static void
+e_web_view_element_clicked_cb (WebKitUserContentManager *manager,
+ WebKitJavascriptResult *js_result,
+ gpointer user_data)
+{
+ EWebView *web_view = user_data;
+ GtkAllocation elem_position;
+ GPtrArray *listeners;
+ JSCValue *jsc_object;
+ gchar *iframe_id, *elem_id, *elem_class, *elem_value;
+ gdouble zoom_level;
+
+ g_return_if_fail (web_view != NULL);
+ g_return_if_fail (js_result != NULL);
+
+ jsc_object = webkit_javascript_result_get_js_value (js_result);
+ g_return_if_fail (jsc_value_is_object (jsc_object));
+
+ iframe_id = e_web_view_jsc_get_object_property_string (jsc_object, "iframe-id", NULL);
+ elem_id = e_web_view_jsc_get_object_property_string (jsc_object, "elem-id", NULL);
+ elem_class = e_web_view_jsc_get_object_property_string (jsc_object, "elem-class", NULL);
+ elem_value = e_web_view_jsc_get_object_property_string (jsc_object, "elem-value", NULL);
+ elem_position.x = e_web_view_jsc_get_object_property_int32 (jsc_object, "left", 0);
+ elem_position.y = e_web_view_jsc_get_object_property_int32 (jsc_object, "top", 0);
+ elem_position.width = e_web_view_jsc_get_object_property_int32 (jsc_object, "width", 0);
+ elem_position.height = e_web_view_jsc_get_object_property_int32 (jsc_object, "height", 0);
+
+ zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (web_view));
+ elem_position.x *= zoom_level;
+ elem_position.y *= zoom_level;
+ elem_position.width *= zoom_level;
+ elem_position.height *= zoom_level;
+
+ listeners = g_hash_table_lookup (web_view->priv->element_clicked_cbs, elem_class);
+
+ if (listeners) {
+ guint ii;
+
+ for (ii = 0; ii < listeners->len; ii++) {
+ ElementClickedData *ecd = g_ptr_array_index (listeners, ii);
+
+ if (ecd && ecd->callback)
+ ecd->callback (web_view, iframe_id, elem_id, elem_class, elem_value, &elem_position, ecd->user_data);
+ }
+ }
+
+ g_free (iframe_id);
+ g_free (elem_id);
+ g_free (elem_class);
+ g_free (elem_value);
+}
+
+static void
+web_view_call_register_element_clicked (EWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *only_elem_class)
+{
+ gchar *elem_classes = NULL;
+
+ if (!only_elem_class) {
+ GHashTableIter iter;
+ gpointer key;
+ GString *classes;
+
+ classes = g_string_sized_new (128);
+
+ g_hash_table_iter_init (&iter, web_view->priv->element_clicked_cbs);
+ while (g_hash_table_iter_next (&iter, &key, NULL)) {
+ if (classes->len)
+ g_string_append_c (classes, '\n');
+
+ g_string_append (classes, key);
+ }
+
+ elem_classes = g_string_free (classes, FALSE);
+ }
+
+ e_web_view_jsc_register_element_clicked (WEBKIT_WEB_VIEW (web_view), iframe_id,
+ only_elem_class ? only_elem_class : elem_classes,
+ web_view->priv->cancellable);
+
+ g_free (elem_classes);
+}
+
+static void
+e_web_view_content_loaded_cb (WebKitUserContentManager *manager,
+ WebKitJavascriptResult *js_result,
+ gpointer user_data)
+{
+ EWebView *web_view = user_data;
+ JSCValue *jsc_value;
+ gchar *iframe_id;
+
+ g_return_if_fail (web_view != NULL);
+ g_return_if_fail (js_result != NULL);
+
+ jsc_value = webkit_javascript_result_get_js_value (js_result);
+ g_return_if_fail (jsc_value_is_string (jsc_value));
+
+ iframe_id = jsc_value_to_string (jsc_value);
+
+ if (!iframe_id || !*iframe_id)
+ e_web_view_update_fonts (web_view);
+ else
+ e_web_view_update_styles (web_view, iframe_id);
+
+ web_view_call_register_element_clicked (web_view, iframe_id, NULL);
+
+ g_signal_emit (web_view, signals[CONTENT_LOADED], 0, iframe_id);
+
+ g_free (iframe_id);
+}
+
+static void
+e_web_view_has_selection_cb (WebKitUserContentManager *manager,
+ WebKitJavascriptResult *js_result,
+ gpointer user_data)
+{
+ EWebView *web_view = user_data;
+ JSCValue *jsc_value;
+
+ g_return_if_fail (web_view != NULL);
+ g_return_if_fail (js_result != NULL);
+
+ jsc_value = webkit_javascript_result_get_js_value (js_result);
+ g_return_if_fail (jsc_value_is_boolean (jsc_value));
+
+ e_web_view_set_has_selection (web_view, jsc_value_to_boolean (jsc_value));
+}
+
+static void
+e_web_view_set_need_input (EWebView *web_view,
+ gboolean need_input)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if ((!web_view->priv->need_input) == (!need_input))
+ return;
+
+ web_view->priv->need_input = need_input;
+
+ g_object_notify (G_OBJECT (web_view), "need-input");
+}
+
+static void
+e_web_view_need_input_changed_cb (WebKitUserContentManager *manager,
+ WebKitJavascriptResult *js_result,
+ gpointer user_data)
+{
+ EWebView *web_view = user_data;
+ JSCValue *jsc_value;
+
+ g_return_if_fail (web_view != NULL);
+ g_return_if_fail (js_result != NULL);
+
+ jsc_value = webkit_javascript_result_get_js_value (js_result);
+ g_return_if_fail (jsc_value_is_boolean (jsc_value));
+
+ e_web_view_set_need_input (web_view, jsc_value_to_boolean (jsc_value));
+}
+
+static void
+web_view_constructed (GObject *object)
+{
+ WebKitSettings *web_settings;
+ WebKitUserContentManager *manager;
+ EWebView *web_view = E_WEB_VIEW (object);
+ GSettings *settings;
+
+#ifndef G_OS_WIN32
+ settings = e_util_ref_settings ("org.gnome.desktop.lockdown");
+
+ g_settings_bind (
+ settings, "disable-printing",
+ object, "disable-printing",
+ G_SETTINGS_BIND_GET);
+
+ g_settings_bind (
+ settings, "disable-save-to-disk",
+ object, "disable-save-to-disk",
+ G_SETTINGS_BIND_GET);
+
+ g_object_unref (settings);
+#endif
+
+ settings = e_util_ref_settings ("org.gnome.evolution.shell");
+
+ g_settings_bind (
+ settings, "webkit-minimum-font-size",
+ object, "minimum-font-size",
+ G_SETTINGS_BIND_GET);
+
+ g_clear_object (&settings);
+
+ g_signal_connect_object (webkit_web_view_get_context (WEBKIT_WEB_VIEW (web_view)), "initialize-web-extensions",
+ G_CALLBACK (e_web_view_initialize_web_extensions_cb), web_view, 0);
+
+ /* Chain up to parent's constructed() method. */
+ G_OBJECT_CLASS (e_web_view_parent_class)->constructed (object);
+
+ e_extensible_load_extensions (E_EXTENSIBLE (object));
+
+ web_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (object));
+ webkit_settings_set_enable_write_console_messages_to_stdout (web_settings, e_util_get_webkit_developer_mode_enabled ());
+
+ g_object_set (
+ G_OBJECT (web_settings),
+ "default-charset", "UTF-8",
+ NULL);
+
+ e_binding_bind_property (
+ web_settings, "enable-caret-browsing",
+ E_WEB_VIEW (object), "caret-mode",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ web_view_initialize (WEBKIT_WEB_VIEW (object));
+
+ web_view_set_find_controller (web_view);
+
+ manager = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (object));
+
+ g_signal_connect_object (manager, "script-message-received::elementClicked",
+ G_CALLBACK (e_web_view_element_clicked_cb), web_view, 0);
+
+ g_signal_connect_object (manager, "script-message-received::contentLoaded",
+ G_CALLBACK (e_web_view_content_loaded_cb), web_view, 0);
+
+ g_signal_connect_object (manager, "script-message-received::hasSelection",
+ G_CALLBACK (e_web_view_has_selection_cb), web_view, 0);
+
+ g_signal_connect_object (manager, "script-message-received::needInputChanged",
+ G_CALLBACK (e_web_view_need_input_changed_cb), web_view, 0);
+
+ webkit_user_content_manager_register_script_message_handler (manager, "contentLoaded");
+ webkit_user_content_manager_register_script_message_handler (manager, "elementClicked");
+ webkit_user_content_manager_register_script_message_handler (manager, "hasSelection");
+ webkit_user_content_manager_register_script_message_handler (manager, "needInputChanged");
+}
+
+static void
+e_web_view_replace_load_cancellable (EWebView *web_view,
+ gboolean create_new)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->cancellable) {
+ g_cancellable_cancel (web_view->priv->cancellable);
+ g_clear_object (&web_view->priv->cancellable);
+ }
+
+ if (create_new)
+ web_view->priv->cancellable = g_cancellable_new ();
+}
+
+static gboolean
+web_view_scroll_event (GtkWidget *widget,
+ GdkEventScroll *event)
+{
+ if (event->state & GDK_CONTROL_MASK) {
+ GdkScrollDirection direction = event->direction;
+
+ if (direction == GDK_SCROLL_SMOOTH) {
+ static gdouble total_delta_y = 0.0;
+
+ total_delta_y += event->delta_y;
+
+ if (total_delta_y >= 1.0) {
+ total_delta_y = 0.0;
+ direction = GDK_SCROLL_DOWN;
+ } else if (total_delta_y <= -1.0) {
+ total_delta_y = 0.0;
+ direction = GDK_SCROLL_UP;
+ } else if (event->delta_y >= 1e-9 || event->delta_y <= -1e-9) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+ }
+
+ switch (direction) {
+ case GDK_SCROLL_UP:
+ e_web_view_zoom_in (E_WEB_VIEW (widget));
+ return TRUE;
+ case GDK_SCROLL_DOWN:
+ e_web_view_zoom_out (E_WEB_VIEW (widget));
+ return TRUE;
+ default:
+ break;
+ }
+ }
+
+ return GTK_WIDGET_CLASS (e_web_view_parent_class)->
+ scroll_event (widget, event);
+}
+
+static gboolean
+web_view_drag_motion (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time_)
+{
+ /* Made this way to not pretend that this is a drop zone,
+ when only FALSE had been returned, even it is supposed
+ to be enough. Remove web_view_drag_leave() once fixed. */
+ gdk_drag_status (context, 0, time_);
+
+ return TRUE;
+}
+
+static gboolean
+web_view_drag_drop (GtkWidget *widget,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ guint time_)
+{
+ /* Defined to avoid crash in WebKit */
+ return FALSE;
+}
+
+static void
+web_view_drag_leave (GtkWidget *widget,
+ GdkDragContext *context,
+ guint time_)
+{
+ /* Defined to avoid crash in WebKit, when the web_view_drag_motion()
+ uses the other way around. */
+}
+
+static void
+web_view_hovering_over_link (EWebView *web_view,
+ const gchar *title,
+ const gchar *uri)
+{
+ gchar *message = e_util_get_uri_tooltip (uri);
+
+ e_web_view_status_message (web_view, message);
+
+ g_free (message);
+}
+
+static void
+web_view_link_clicked (EWebView *web_view,
+ const gchar *uri)
+{
+ gpointer parent;
+
+ if (uri && g_ascii_strncasecmp (uri, "mailto:", 7) == 0) {
+ gboolean handled = FALSE;
+
+ g_signal_emit (
+ web_view, signals[PROCESS_MAILTO], 0, uri, &handled);
+
+ if (handled)
+ return;
+ }
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ e_show_uri (parent, uri);
+}
+
+static void
+web_view_load_string (EWebView *web_view,
+ const gchar *string)
+{
+ if (!string || !*string) {
+ webkit_web_view_load_html (WEBKIT_WEB_VIEW (web_view), "", "evo-file:///");
+ } else {
+ GBytes *bytes;
+
+ bytes = g_bytes_new (string, strlen (string));
+ webkit_web_view_load_bytes (WEBKIT_WEB_VIEW (web_view), bytes, NULL, NULL, "evo-file:///");
+ g_bytes_unref (bytes);
+ }
+}
+
+static void
+web_view_load_uri (EWebView *web_view,
+ const gchar *uri)
+{
+ if (!uri)
+ uri = "about:blank";
+
+ webkit_web_view_load_uri (WEBKIT_WEB_VIEW (web_view), uri);
+}
+
+static gchar *
+web_view_suggest_filename (EWebView *web_view,
+ const gchar *uri)
+{
+ const gchar *cp;
+
+ /* Try to derive a filename from the last path segment. */
+
+ cp = strrchr (uri, '/');
+ if (cp != NULL) {
+ if (strchr (cp, '?') == NULL)
+ cp++;
+ else
+ cp = NULL;
+ }
+
+ return g_strdup (cp);
+}
+
+static void
+web_view_before_popup_event (EWebView *web_view,
+ const gchar *uri)
+{
+ e_web_view_set_selected_uri (web_view, uri);
+ e_web_view_update_actions (web_view);
+}
+
+static gboolean
+web_view_popup_event (EWebView *web_view,
+ const gchar *uri,
+ GdkEvent *event)
+{
+ e_web_view_set_selected_uri (web_view, uri);
+ e_web_view_show_popup_menu (web_view, event);
+
+ return TRUE;
+}
+
+static void
+web_view_stop_loading (EWebView *web_view)
+{
+ e_web_view_replace_load_cancellable (web_view, FALSE);
+ webkit_web_view_stop_loading (WEBKIT_WEB_VIEW (web_view));
+}
+
+static void
+web_view_update_actions (EWebView *web_view)
+{
+ GtkActionGroup *action_group;
+ gboolean can_copy;
+ gboolean scheme_is_http = FALSE;
+ gboolean scheme_is_mailto = FALSE;
+ gboolean uri_is_valid = FALSE;
+ gboolean visible;
+ const gchar *cursor_image_src;
+ const gchar *group_name;
+ const gchar *uri;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ uri = e_web_view_get_selected_uri (web_view);
+ can_copy = e_web_view_has_selection (web_view);
+ cursor_image_src = e_web_view_get_cursor_image_src (web_view);
+
+ /* Parse the URI early so we know if the actions will work. */
+ if (uri != NULL) {
+ CamelURL *curl;
+
+ curl = camel_url_new (uri, NULL);
+ uri_is_valid = (curl != NULL);
+ camel_url_free (curl);
+
+ scheme_is_http =
+ (g_ascii_strncasecmp (uri, "http:", 5) == 0) ||
+ (g_ascii_strncasecmp (uri, "https:", 6) == 0);
+
+ scheme_is_mailto =
+ (g_ascii_strncasecmp (uri, "mailto:", 7) == 0);
+ }
+
+ /* Allow copying the URI even if it's malformed. */
+ group_name = "uri";
+ visible = (uri != NULL) && !scheme_is_mailto;
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ group_name = "http";
+ visible = uri_is_valid && scheme_is_http;
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ group_name = "mailto";
+ visible = uri_is_valid && scheme_is_mailto;
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ if (visible) {
+ CamelURL *curl;
+
+ curl = camel_url_new (uri, NULL);
+ if (curl) {
+ CamelInternetAddress *inet_addr;
+ const gchar *name = NULL, *email = NULL;
+ GtkAction *action;
+
+ inet_addr = camel_internet_address_new ();
+ camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
+
+ action = gtk_action_group_get_action (action_group, "mailto-copy-raw");
+ gtk_action_set_visible (action,
+ camel_internet_address_get (inet_addr, 0, &name, &email) &&
+ name && *name && email && *email);
+
+ g_object_unref (inet_addr);
+ camel_url_free (curl);
+ }
+ }
+
+ group_name = "image";
+ visible = (cursor_image_src != NULL);
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ group_name = "selection";
+ visible = can_copy;
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ group_name = "standard";
+ visible = (uri == NULL);
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ group_name = "lockdown-printing";
+ visible = (uri == NULL) && !web_view->priv->disable_printing;
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+
+ group_name = "lockdown-save-to-disk";
+ visible = (uri == NULL) && !web_view->priv->disable_save_to_disk;
+ action_group = e_web_view_get_action_group (web_view, group_name);
+ gtk_action_group_set_visible (action_group, visible);
+}
+
+static void
+web_view_submit_alert (EAlertSink *alert_sink,
+ EAlert *alert)
+{
+ EWebView *web_view;
+ GtkWidget *dialog;
+ GString *buffer;
+ const gchar *icon_name = NULL;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+ gint icon_width, icon_height;
+ gpointer parent;
+
+ web_view = E_WEB_VIEW (alert_sink);
+
+ parent = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ parent = gtk_widget_is_toplevel (parent) ? parent : NULL;
+
+ switch (e_alert_get_message_type (alert)) {
+ case GTK_MESSAGE_INFO:
+ icon_name = "dialog-information";
+ break;
+
+ case GTK_MESSAGE_WARNING:
+ icon_name = "dialog-warning";
+ break;
+
+ case GTK_MESSAGE_ERROR:
+ icon_name = "dialog-error";
+ break;
+
+ default:
+ dialog = e_alert_dialog_new (parent, alert);
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+ return;
+ }
+
+ /* Primary text is required. */
+ primary_text = e_alert_get_primary_text (alert);
+ g_return_if_fail (primary_text != NULL);
+
+ /* Secondary text is optional. */
+ secondary_text = e_alert_get_secondary_text (alert);
+ if (secondary_text == NULL)
+ secondary_text = "";
+
+ if (!gtk_icon_size_lookup (GTK_ICON_SIZE_DIALOG, &icon_width, &icon_height)) {
+ icon_width = 48;
+ icon_height = 48;
+ }
+
+ buffer = g_string_sized_new (512);
+
+ g_string_append (
+ buffer,
+ "<html>"
+ "<head>"
+ "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"
+ "<meta name=\"color-scheme\" content=\"light dark\">"
+ "</head>"
+ "<body>");
+
+ g_string_append (
+ buffer,
+ "<table bgcolor='#000000' width='100%'"
+ " cellpadding='1' cellspacing='0'>"
+ "<tr>"
+ "<td>"
+ "<table bgcolor='#dddddd' width='100%' cellpadding='6' style=\"color:#000000;\">"
+ "<tr>");
+
+ g_string_append_printf (
+ buffer,
+ "<tr>"
+ "<td valign='top'>"
+ "<img src='gtk-stock://%s/?size=%d' width=\"%dpx\" height=\"%dpx\"/>"
+ "</td>"
+ "<td align='left' width='100%%'>"
+ "<h3>%s</h3>"
+ "%s"
+ "</td>"
+ "</tr>",
+ icon_name,
+ GTK_ICON_SIZE_DIALOG,
+ icon_width,
+ icon_height,
+ primary_text,
+ secondary_text);
+
+ g_string_append (
+ buffer,
+ "</table>"
+ "</td>"
+ "</tr>"
+ "</table>"
+ "</body>"
+ "</html>");
+
+ e_web_view_load_string (web_view, buffer->str);
+
+ g_string_free (buffer, TRUE);
+}
+
+static void
+web_view_can_execute_editing_command_cb (WebKitWebView *webkit_web_view,
+ GAsyncResult *result,
+ GtkAction *action)
+{
+ gboolean can_do_command;
+
+ can_do_command = webkit_web_view_can_execute_editing_command_finish (
+ webkit_web_view, result, NULL);
+
+ gtk_action_set_sensitive (action, can_do_command);
+ g_object_unref (action);
+}
+
+static void
+web_view_selectable_update_actions (ESelectable *selectable,
+ EFocusTracker *focus_tracker,
+ GdkAtom *clipboard_targets,
+ gint n_clipboard_targets)
+{
+ EWebView *web_view;
+ GtkAction *action;
+ gboolean can_copy = FALSE;
+
+ web_view = E_WEB_VIEW (selectable);
+
+ can_copy = e_web_view_has_selection (web_view);
+
+ action = e_focus_tracker_get_copy_clipboard_action (focus_tracker);
+ gtk_action_set_sensitive (action, can_copy);
+ gtk_action_set_tooltip (action, _("Copy the selection"));
+
+ action = e_focus_tracker_get_cut_clipboard_action (focus_tracker);
+ webkit_web_view_can_execute_editing_command (
+ WEBKIT_WEB_VIEW (web_view),
+ WEBKIT_EDITING_COMMAND_CUT,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) web_view_can_execute_editing_command_cb,
+ g_object_ref (action));
+ gtk_action_set_tooltip (action, _("Cut the selection"));
+
+ action = e_focus_tracker_get_paste_clipboard_action (focus_tracker);
+ webkit_web_view_can_execute_editing_command (
+ WEBKIT_WEB_VIEW (web_view),
+ WEBKIT_EDITING_COMMAND_PASTE,
+ NULL, /* cancellable */
+ (GAsyncReadyCallback) web_view_can_execute_editing_command_cb,
+ g_object_ref (action));
+ gtk_action_set_tooltip (action, _("Paste the clipboard"));
+
+ action = e_focus_tracker_get_select_all_action (focus_tracker);
+ gtk_action_set_sensitive (action, TRUE);
+ gtk_action_set_tooltip (action, _("Select all text and images"));
+}
+
+static void
+web_view_selectable_cut_clipboard (ESelectable *selectable)
+{
+ e_web_view_cut_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
+web_view_selectable_copy_clipboard (ESelectable *selectable)
+{
+ e_web_view_copy_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
+web_view_selectable_paste_clipboard (ESelectable *selectable)
+{
+ e_web_view_paste_clipboard (E_WEB_VIEW (selectable));
+}
+
+static void
+web_view_selectable_select_all (ESelectable *selectable)
+{
+ e_web_view_select_all (E_WEB_VIEW (selectable));
+}
+
+static void
+e_web_view_test_change_and_update_fonts_cb (EWebView *web_view,
+ const gchar *key,
+ GSettings *settings)
+{
+ GVariant *new_value, *old_value;
+
+ new_value = g_settings_get_value (settings, key);
+ old_value = g_hash_table_lookup (web_view->priv->old_settings, key);
+
+ if (!new_value || !old_value || !g_variant_equal (new_value, old_value)) {
+ if (new_value)
+ g_hash_table_insert (web_view->priv->old_settings, g_strdup (key), new_value);
+ else
+ g_hash_table_remove (web_view->priv->old_settings, key);
+
+ e_web_view_update_fonts (web_view);
+ } else if (new_value) {
+ g_variant_unref (new_value);
+ }
+}
+
+static void
+web_view_toplevel_event_after_cb (GtkWidget *widget,
+ GdkEvent *event,
+ EWebView *web_view)
+{
+ if (event && event->type == GDK_MOTION_NOTIFY && web_view->priv->has_hover_link &&
+ gdk_event_get_window (event) != gtk_widget_get_window (GTK_WIDGET (web_view))) {
+ /* This won't reset WebKitGTK's cached link the cursor stays on, but do this,
+ instead of sending a fake signal to the WebKitGTK. */
+ e_web_view_status_message (web_view, NULL);
+
+ web_view->priv->has_hover_link = FALSE;
+ }
+}
+
+static void
+web_view_map (GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+
+ g_signal_connect (toplevel, "event-after", G_CALLBACK (web_view_toplevel_event_after_cb), widget);
+
+ GTK_WIDGET_CLASS (e_web_view_parent_class)->map (widget);
+}
+
+static void
+web_view_unmap (GtkWidget *widget)
+{
+ GtkWidget *toplevel;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+
+ g_signal_handlers_disconnect_by_func (toplevel, G_CALLBACK (web_view_toplevel_event_after_cb), widget);
+
+ GTK_WIDGET_CLASS (e_web_view_parent_class)->unmap (widget);
+
+ e_web_view_status_message (E_WEB_VIEW (widget), NULL);
+}
+
+static void
+e_web_view_class_init (EWebViewClass *class)
+{
+ GObjectClass *object_class;
+ GtkWidgetClass *widget_class;
+
+ g_type_class_add_private (class, sizeof (EWebViewPrivate));
+
+ object_class = G_OBJECT_CLASS (class);
+ object_class->constructor = web_view_constructor;
+ object_class->set_property = web_view_set_property;
+ object_class->get_property = web_view_get_property;
+ object_class->dispose = web_view_dispose;
+ object_class->finalize = web_view_finalize;
+ object_class->constructed = web_view_constructed;
+
+ widget_class = GTK_WIDGET_CLASS (class);
+ widget_class->scroll_event = web_view_scroll_event;
+ widget_class->drag_motion = web_view_drag_motion;
+ widget_class->drag_drop = web_view_drag_drop;
+ widget_class->drag_leave = web_view_drag_leave;
+ widget_class->map = web_view_map;
+ widget_class->unmap = web_view_unmap;
+
+ class->hovering_over_link = web_view_hovering_over_link;
+ class->link_clicked = web_view_link_clicked;
+ class->load_string = web_view_load_string;
+ class->load_uri = web_view_load_uri;
+ class->suggest_filename = web_view_suggest_filename;
+ class->before_popup_event = web_view_before_popup_event;
+ class->popup_event = web_view_popup_event;
+ class->stop_loading = web_view_stop_loading;
+ class->update_actions = web_view_update_actions;
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CARET_MODE,
+ g_param_spec_boolean (
+ "caret-mode",
+ "Caret Mode",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE));
+
+ /* Inherited from ESelectableInterface; just a fake property here */
+ g_object_class_override_property (
+ object_class,
+ PROP_COPY_TARGET_LIST,
+ "copy-target-list");
+
+ /* Inherited from ESelectableInterface; just a fake property here */
+ g_object_class_override_property (
+ object_class,
+ PROP_PASTE_TARGET_LIST,
+ "paste-target-list");
+
+ g_object_class_install_property (
+ object_class,
+ PROP_CURSOR_IMAGE_SRC,
+ g_param_spec_string (
+ "cursor-image-src",
+ "Image source uri at the mouse cursor",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DISABLE_PRINTING,
+ g_param_spec_boolean (
+ "disable-printing",
+ "Disable Printing",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_DISABLE_SAVE_TO_DISK,
+ g_param_spec_boolean (
+ "disable-save-to-disk",
+ "Disable Save-to-Disk",
+ NULL,
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_HAS_SELECTION,
+ g_param_spec_boolean (
+ "has-selection",
+ "Has Selection",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_MINIMUM_FONT_SIZE,
+ g_param_spec_int (
+ "minimum-font-size",
+ "Minimum Font Size",
+ NULL,
+ G_MININT, G_MAXINT, 0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_NEED_INPUT,
+ g_param_spec_boolean (
+ "need-input",
+ "Need Input",
+ NULL,
+ FALSE,
+ G_PARAM_READABLE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_OPEN_PROXY,
+ g_param_spec_object (
+ "open-proxy",
+ "Open Proxy",
+ NULL,
+ GTK_TYPE_ACTION,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_PRINT_PROXY,
+ g_param_spec_object (
+ "print-proxy",
+ "Print Proxy",
+ NULL,
+ GTK_TYPE_ACTION,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SAVE_AS_PROXY,
+ g_param_spec_object (
+ "save-as-proxy",
+ "Save As Proxy",
+ NULL,
+ GTK_TYPE_ACTION,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (
+ object_class,
+ PROP_SELECTED_URI,
+ g_param_spec_string (
+ "selected-uri",
+ "Selected URI",
+ NULL,
+ NULL,
+ G_PARAM_READWRITE));
+
+ signals[NEW_ACTIVITY] = g_signal_new (
+ "new-activity",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, new_activity),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1,
+ E_TYPE_ACTIVITY);
+
+ signals[POPUP_EVENT] = g_signal_new (
+ "popup-event",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, popup_event),
+ g_signal_accumulator_true_handled, NULL,
+ NULL,
+ G_TYPE_BOOLEAN, 2, G_TYPE_STRING, GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
+
+ signals[BEFORE_POPUP_EVENT] = g_signal_new (
+ "before-popup-event",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, before_popup_event),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+
+ signals[STATUS_MESSAGE] = g_signal_new (
+ "status-message",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, status_message),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__STRING,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ signals[STOP_LOADING] = g_signal_new (
+ "stop-loading",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, stop_loading),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[UPDATE_ACTIONS] = g_signal_new (
+ "update-actions",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, update_actions),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ /* return TRUE when a signal handler processed the mailto URI */
+ signals[PROCESS_MAILTO] = g_signal_new (
+ "process-mailto",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, process_mailto),
+ NULL, NULL,
+ e_marshal_BOOLEAN__STRING,
+ G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
+
+ /* Expects an empty string to abandon the request,
+ or NULL to keep the passed-in uri,
+ or a new uri to load instead. */
+ signals[URI_REQUESTED] = g_signal_new (
+ "uri-requested",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, uri_requested),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER);
+
+ signals[CONTENT_LOADED] = g_signal_new (
+ "content-loaded",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EWebViewClass, content_loaded),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1, G_TYPE_STRING);
+}
+
+static void
+e_web_view_alert_sink_init (EAlertSinkInterface *iface)
+{
+ iface->submit_alert = web_view_submit_alert;
+}
+
+static void
+e_web_view_selectable_init (ESelectableInterface *iface)
+{
+ iface->update_actions = web_view_selectable_update_actions;
+ iface->cut_clipboard = web_view_selectable_cut_clipboard;
+ iface->copy_clipboard = web_view_selectable_copy_clipboard;
+ iface->paste_clipboard = web_view_selectable_paste_clipboard;
+ iface->select_all = web_view_selectable_select_all;
+}
+
+static void
+e_web_view_init (EWebView *web_view)
+{
+ GtkUIManager *ui_manager;
+ GtkActionGroup *action_group;
+ EPopupAction *popup_action;
+ GSettings *settings;
+ const gchar *domain = GETTEXT_PACKAGE;
+ const gchar *id;
+ gulong handler_id;
+ GError *error = NULL;
+
+ web_view->priv = E_WEB_VIEW_GET_PRIVATE (web_view);
+
+ web_view->priv->highlights_enabled = TRUE;
+ web_view->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
+ web_view->priv->scheme_handlers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+ g_signal_connect (
+ web_view, "context-menu",
+ G_CALLBACK (web_view_context_menu_cb), NULL);
+
+ g_signal_connect (
+ web_view, "mouse-target-changed",
+ G_CALLBACK (web_view_mouse_target_changed_cb), NULL);
+
+ g_signal_connect (
+ web_view, "decide-policy",
+ G_CALLBACK (web_view_decide_policy_cb),
+ NULL);
+
+ g_signal_connect (
+ web_view, "load-changed",
+ G_CALLBACK (web_view_load_changed_cb), NULL);
+
+ g_signal_connect (
+ web_view, "style-updated",
+ G_CALLBACK (style_updated_cb), NULL);
+
+ g_signal_connect (
+ web_view, "state-flags-changed",
+ G_CALLBACK (style_updated_cb), NULL);
+
+ ui_manager = gtk_ui_manager_new ();
+ web_view->priv->ui_manager = ui_manager;
+
+ g_signal_connect_swapped (
+ ui_manager, "connect-proxy",
+ G_CALLBACK (web_view_connect_proxy_cb), web_view);
+
+ settings = e_util_ref_settings ("org.gnome.desktop.interface");
+ web_view->priv->font_settings = g_object_ref (settings);
+ handler_id = g_signal_connect_swapped (
+ settings, "changed::font-name",
+ G_CALLBACK (e_web_view_test_change_and_update_fonts_cb), web_view);
+ web_view->priv->font_name_changed_handler_id = handler_id;
+ handler_id = g_signal_connect_swapped (
+ settings, "changed::monospace-font-name",
+ G_CALLBACK (e_web_view_test_change_and_update_fonts_cb), web_view);
+ web_view->priv->monospace_font_name_changed_handler_id = handler_id;
+ g_object_unref (settings);
+
+ action_group = gtk_action_group_new ("uri");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ gtk_action_group_add_actions (
+ action_group, uri_entries,
+ G_N_ELEMENTS (uri_entries), web_view);
+
+ action_group = gtk_action_group_new ("http");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ gtk_action_group_add_actions (
+ action_group, http_entries,
+ G_N_ELEMENTS (http_entries), web_view);
+
+ action_group = gtk_action_group_new ("mailto");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ gtk_action_group_add_actions (
+ action_group, mailto_entries,
+ G_N_ELEMENTS (mailto_entries), web_view);
+
+ action_group = gtk_action_group_new ("image");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ gtk_action_group_add_actions (
+ action_group, image_entries,
+ G_N_ELEMENTS (image_entries), web_view);
+
+ action_group = gtk_action_group_new ("selection");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ gtk_action_group_add_actions (
+ action_group, selection_entries,
+ G_N_ELEMENTS (selection_entries), web_view);
+
+ action_group = gtk_action_group_new ("standard");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ gtk_action_group_add_actions (
+ action_group, standard_entries,
+ G_N_ELEMENTS (standard_entries), web_view);
+
+ popup_action = e_popup_action_new ("open");
+ gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+ g_object_unref (popup_action);
+
+ e_binding_bind_property (
+ web_view, "open-proxy",
+ popup_action, "related-action",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ /* Support lockdown. */
+
+ action_group = gtk_action_group_new ("lockdown-printing");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ popup_action = e_popup_action_new ("print");
+ gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+ g_object_unref (popup_action);
+
+ e_binding_bind_property (
+ web_view, "print-proxy",
+ popup_action, "related-action",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ action_group = gtk_action_group_new ("lockdown-save-to-disk");
+ gtk_action_group_set_translation_domain (action_group, domain);
+ gtk_ui_manager_insert_action_group (ui_manager, action_group, 0);
+ g_object_unref (action_group);
+
+ popup_action = e_popup_action_new ("save-as");
+ gtk_action_group_add_action (action_group, GTK_ACTION (popup_action));
+ g_object_unref (popup_action);
+
+ e_binding_bind_property (
+ web_view, "save-as-proxy",
+ popup_action, "related-action",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ /* Because we are loading from a hard-coded string, there is
+ * no chance of I/O errors. Failure here implies a malformed
+ * UI definition. Full stop. */
+ gtk_ui_manager_add_ui_from_string (ui_manager, ui, -1, &error);
+ if (error != NULL)
+ g_error ("%s", error->message);
+
+ id = "org.gnome.evolution.webview";
+ e_plugin_ui_register_manager (ui_manager, id, web_view);
+ e_plugin_ui_enable_manager (ui_manager, id);
+
+ web_view->priv->element_clicked_cbs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_ptr_array_unref);
+
+ web_view->priv->cancellable = NULL;
+}
+
+GtkWidget *
+e_web_view_new (void)
+{
+ return g_object_new (
+ E_TYPE_WEB_VIEW,
+ NULL);
+}
+
+void
+e_web_view_clear (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ e_web_view_replace_load_cancellable (web_view, FALSE);
+
+ e_web_view_load_string (web_view,
+ "<html>"
+ "<head>"
+ "<meta name=\"color-scheme\" content=\"light dark\">"
+ "</head>"
+ "<body class=\"-e-web-view-background-color -e-web-view-text-color\"></body>"
+ "</html>");
+}
+
+void
+e_web_view_load_string (EWebView *web_view,
+ const gchar *string)
+{
+ EWebViewClass *class;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ class = E_WEB_VIEW_GET_CLASS (web_view);
+ g_return_if_fail (class != NULL);
+ g_return_if_fail (class->load_string != NULL);
+
+ e_web_view_replace_load_cancellable (web_view, TRUE);
+
+ class->load_string (web_view, string);
+}
+
+void
+e_web_view_load_uri (EWebView *web_view,
+ const gchar *uri)
+{
+ EWebViewClass *class;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ class = E_WEB_VIEW_GET_CLASS (web_view);
+ g_return_if_fail (class != NULL);
+ g_return_if_fail (class->load_uri != NULL);
+
+ e_web_view_replace_load_cancellable (web_view, TRUE);
+
+ class->load_uri (web_view, uri);
+}
+
+/**
+ * e_web_view_suggest_filename:
+ * @web_view: an #EWebView
+ * @uri: a URI string
+ *
+ * Attempts to derive a suggested filename from the @uri for use in a
+ * "Save As" dialog.
+ *
+ * By default the suggested filename is the last path segment of the @uri
+ * (unless @uri looks like a query), but subclasses can use other mechanisms
+ * for custom URI schemes. For example, "cid:" URIs in an email message may
+ * refer to a MIME part with a suggested filename in its Content-Disposition
+ * header.
+ *
+ * The returned string should be freed with g_free() when finished with it,
+ * but callers should also be prepared for the function to return %NULL if
+ * a filename cannot be determined.
+ *
+ * Returns: a suggested filename, or %NULL
+ **/
+gchar *
+e_web_view_suggest_filename (EWebView *web_view,
+ const gchar *uri)
+{
+ EWebViewClass *class;
+ gchar *filename;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+ g_return_val_if_fail (uri != NULL, NULL);
+
+ class = E_WEB_VIEW_GET_CLASS (web_view);
+ g_return_val_if_fail (class != NULL, NULL);
+ g_return_val_if_fail (class->suggest_filename != NULL, NULL);
+
+ filename = class->suggest_filename (web_view, uri);
+
+ if (filename != NULL)
+ e_util_make_safe_filename (filename);
+
+ return filename;
+}
+
+void
+e_web_view_reload (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ e_web_view_replace_load_cancellable (web_view, TRUE);
+
+ webkit_web_view_reload (WEBKIT_WEB_VIEW (web_view));
+}
+
+gboolean
+e_web_view_get_caret_mode (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+ return web_view->priv->caret_mode;
+}
+
+void
+e_web_view_set_caret_mode (EWebView *web_view,
+ gboolean caret_mode)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->caret_mode == caret_mode)
+ return;
+
+ web_view->priv->caret_mode = caret_mode;
+
+ g_object_notify (G_OBJECT (web_view), "caret-mode");
+}
+
+GtkTargetList *
+e_web_view_get_copy_target_list (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return NULL;
+ /* FIXME WK2 */
+ /*return webkit_web_view_get_copy_target_list (
+ WEBKIT_WEB_VIEW (web_view));*/
+}
+
+gboolean
+e_web_view_get_disable_printing (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+ return web_view->priv->disable_printing;
+}
+
+void
+e_web_view_set_disable_printing (EWebView *web_view,
+ gboolean disable_printing)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->disable_printing == disable_printing)
+ return;
+
+ web_view->priv->disable_printing = disable_printing;
+
+ g_object_notify (G_OBJECT (web_view), "disable-printing");
+}
+
+gboolean
+e_web_view_get_disable_save_to_disk (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+ return web_view->priv->disable_save_to_disk;
+}
+
+void
+e_web_view_set_disable_save_to_disk (EWebView *web_view,
+ gboolean disable_save_to_disk)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->disable_save_to_disk == disable_save_to_disk)
+ return;
+
+ web_view->priv->disable_save_to_disk = disable_save_to_disk;
+
+ g_object_notify (G_OBJECT (web_view), "disable-save-to-disk");
+}
+
+gboolean
+e_web_view_get_editable (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+ return webkit_web_view_is_editable (WEBKIT_WEB_VIEW (web_view));
+}
+
+void
+e_web_view_set_editable (EWebView *web_view,
+ gboolean editable)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_set_editable (WEBKIT_WEB_VIEW (web_view), editable);
+}
+
+gboolean
+e_web_view_get_need_input (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+ return web_view->priv->need_input;
+}
+
+const gchar *
+e_web_view_get_selected_uri (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->selected_uri;
+}
+
+void
+e_web_view_set_selected_uri (EWebView *web_view,
+ const gchar *selected_uri)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (g_strcmp0 (web_view->priv->selected_uri, selected_uri) == 0)
+ return;
+
+ g_free (web_view->priv->selected_uri);
+ web_view->priv->selected_uri = g_strdup (selected_uri);
+
+ g_object_notify (G_OBJECT (web_view), "selected-uri");
+}
+
+const gchar *
+e_web_view_get_cursor_image_src (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->cursor_image_src;
+}
+
+void
+e_web_view_set_cursor_image_src (EWebView *web_view,
+ const gchar *src_uri)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (g_strcmp0 (web_view->priv->cursor_image_src, src_uri) == 0)
+ return;
+
+ g_free (web_view->priv->cursor_image_src);
+ web_view->priv->cursor_image_src = g_strdup (src_uri);
+
+ g_object_notify (G_OBJECT (web_view), "cursor-image-src");
+}
+
+GtkAction *
+e_web_view_get_open_proxy (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->open_proxy;
+}
+
+void
+e_web_view_set_open_proxy (EWebView *web_view,
+ GtkAction *open_proxy)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->open_proxy == open_proxy)
+ return;
+
+ if (open_proxy != NULL) {
+ g_return_if_fail (GTK_IS_ACTION (open_proxy));
+ g_object_ref (open_proxy);
+ }
+
+ if (web_view->priv->open_proxy != NULL)
+ g_object_unref (web_view->priv->open_proxy);
+
+ web_view->priv->open_proxy = open_proxy;
+
+ g_object_notify (G_OBJECT (web_view), "open-proxy");
+}
+
+GtkTargetList *
+e_web_view_get_paste_target_list (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ /* FIXME WK2
+ return webkit_web_view_get_paste_target_list (
+ WEBKIT_WEB_VIEW (web_view)); */
+ return NULL;
+}
+
+GtkAction *
+e_web_view_get_print_proxy (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->print_proxy;
+}
+
+void
+e_web_view_set_print_proxy (EWebView *web_view,
+ GtkAction *print_proxy)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->print_proxy == print_proxy)
+ return;
+
+ if (print_proxy != NULL) {
+ g_return_if_fail (GTK_IS_ACTION (print_proxy));
+ g_object_ref (print_proxy);
+ }
+
+ if (web_view->priv->print_proxy != NULL)
+ g_object_unref (web_view->priv->print_proxy);
+
+ web_view->priv->print_proxy = print_proxy;
+
+ g_object_notify (G_OBJECT (web_view), "print-proxy");
+}
+
+GtkAction *
+e_web_view_get_save_as_proxy (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->save_as_proxy;
+}
+
+void
+e_web_view_set_save_as_proxy (EWebView *web_view,
+ GtkAction *save_as_proxy)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->save_as_proxy == save_as_proxy)
+ return;
+
+ if (save_as_proxy != NULL) {
+ g_return_if_fail (GTK_IS_ACTION (save_as_proxy));
+ g_object_ref (save_as_proxy);
+ }
+
+ if (web_view->priv->save_as_proxy != NULL)
+ g_object_unref (web_view->priv->save_as_proxy);
+
+ web_view->priv->save_as_proxy = save_as_proxy;
+
+ g_object_notify (G_OBJECT (web_view), "save-as-proxy");
+}
+
+void
+e_web_view_get_last_popup_place (EWebView *web_view,
+ gchar **out_iframe_src,
+ gchar **out_iframe_id,
+ gchar **out_element_id,
+ gchar **out_link_uri)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (out_iframe_src)
+ *out_iframe_src = g_strdup (web_view->priv->last_popup_iframe_src);
+
+ if (out_iframe_id)
+ *out_iframe_id = g_strdup (web_view->priv->last_popup_iframe_id);
+
+ if (out_element_id)
+ *out_element_id = g_strdup (web_view->priv->last_popup_element_id);
+
+ if (out_link_uri)
+ *out_link_uri = g_strdup (web_view->priv->last_popup_link_uri);
+}
+
+void
+e_web_view_add_highlight (EWebView *web_view,
+ const gchar *highlight)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (highlight && *highlight);
+
+ g_queue_push_tail (
+ &web_view->priv->highlights,
+ g_strdup (highlight));
+
+ webkit_find_controller_search (
+ web_view->priv->find_controller,
+ highlight,
+ WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE,
+ G_MAXUINT);
+}
+
+void
+e_web_view_clear_highlights (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_find_controller_search_finish (web_view->priv->find_controller);
+
+ while (!g_queue_is_empty (&web_view->priv->highlights))
+ g_free (g_queue_pop_head (&web_view->priv->highlights));
+}
+
+void
+e_web_view_update_highlights (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ web_view->priv->highlights_enabled = TRUE;
+ web_view_update_document_highlights (web_view);
+}
+
+void
+e_web_view_disable_highlights (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ web_view->priv->highlights_enabled = FALSE;
+}
+
+GtkAction *
+e_web_view_get_action (EWebView *web_view,
+ const gchar *action_name)
+{
+ GtkUIManager *ui_manager;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+
+ ui_manager = e_web_view_get_ui_manager (web_view);
+
+ return e_lookup_action (ui_manager, action_name);
+}
+
+GtkActionGroup *
+e_web_view_get_action_group (EWebView *web_view,
+ const gchar *group_name)
+{
+ GtkUIManager *ui_manager;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+ g_return_val_if_fail (group_name != NULL, NULL);
+
+ ui_manager = e_web_view_get_ui_manager (web_view);
+
+ return e_lookup_action_group (ui_manager, group_name);
+}
+
+void
+e_web_view_copy_clipboard (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_execute_editing_command (
+ WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_COPY);
+}
+
+void
+e_web_view_cut_clipboard (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_execute_editing_command (
+ WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_CUT);
+}
+
+gboolean
+e_web_view_has_selection (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+
+ return web_view->priv->has_selection;
+}
+
+void
+e_web_view_paste_clipboard (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_execute_editing_command (
+ WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_PASTE);
+}
+
+gboolean
+e_web_view_scroll_forward (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+/* FIXME WK2
+ webkit_web_view_move_cursor (
+ WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, 1);
+*/
+ return TRUE; /* XXX This means nothing. */
+}
+
+gboolean
+e_web_view_scroll_backward (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), FALSE);
+/* FIXME WK2
+ webkit_web_view_move_cursor (
+ WEBKIT_WEB_VIEW (web_view), GTK_MOVEMENT_PAGES, -1);
+*/
+ return TRUE; /* XXX This means nothing. */
+}
+
+void
+e_web_view_select_all (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_execute_editing_command (
+ WEBKIT_WEB_VIEW (web_view), WEBKIT_EDITING_COMMAND_SELECT_ALL);
+}
+
+void
+e_web_view_unselect_all (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_execute_editing_command (WEBKIT_WEB_VIEW (web_view), "Unselect");
+}
+
+void
+e_web_view_zoom_100 (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), 1.0);
+}
+
+void
+e_web_view_zoom_in (EWebView *web_view)
+{
+ gdouble zoom_level;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ /* There is no webkit_web_view_zoom_in function in WK2, so emulate it */
+ zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (web_view));
+ /* zoom-step in WK1 was 0.1 */
+ zoom_level += 0.1;
+ if (zoom_level < 4.9999)
+ webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), zoom_level);
+}
+
+void
+e_web_view_zoom_out (EWebView *web_view)
+{
+ gdouble zoom_level;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ /* There is no webkit_web_view_zoom_out function in WK2, so emulate it */
+ zoom_level = webkit_web_view_get_zoom_level (WEBKIT_WEB_VIEW (web_view));
+ /* zoom-step in WK1 was 0.1 */
+ zoom_level -= 0.1;
+ if (zoom_level > 0.7999)
+ webkit_web_view_set_zoom_level (WEBKIT_WEB_VIEW (web_view), zoom_level);
+}
+
+GtkUIManager *
+e_web_view_get_ui_manager (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->ui_manager;
+}
+
+static void
+e_web_view_popup_menu_deactivate_cb (GtkMenu *popup_menu,
+ GtkWidget *web_view)
+{
+ g_return_if_fail (GTK_IS_MENU (popup_menu));
+
+ g_signal_handlers_disconnect_by_func (popup_menu, e_web_view_popup_menu_deactivate_cb, web_view);
+ gtk_menu_detach (popup_menu);
+}
+
+GtkWidget *
+e_web_view_get_popup_menu (EWebView *web_view)
+{
+ GtkUIManager *ui_manager;
+ GtkWidget *menu;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ ui_manager = e_web_view_get_ui_manager (web_view);
+ menu = gtk_ui_manager_get_widget (ui_manager, "/context");
+ g_return_val_if_fail (GTK_IS_MENU (menu), NULL);
+
+ g_warn_if_fail (!gtk_menu_get_attach_widget (GTK_MENU (menu)));
+
+ gtk_menu_attach_to_widget (GTK_MENU (menu),
+ GTK_WIDGET (web_view),
+ NULL);
+
+ g_signal_connect (
+ menu, "deactivate",
+ G_CALLBACK (e_web_view_popup_menu_deactivate_cb), web_view);
+
+ return menu;
+}
+
+void
+e_web_view_show_popup_menu (EWebView *web_view,
+ GdkEvent *event)
+{
+ GtkWidget *menu;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ e_web_view_update_actions (web_view);
+
+ menu = e_web_view_get_popup_menu (web_view);
+
+ gtk_menu_popup_at_pointer (GTK_MENU (menu), event);
+}
+
+/**
+ * e_web_view_new_activity:
+ * @web_view: an #EWebView
+ *
+ * Returns a new #EActivity for an #EWebView-related asynchronous operation,
+ * and emits the #EWebView::new-activity signal. By default the #EActivity
+ * comes loaded with a #GCancellable and sets the @web_view itself as the
+ * #EActivity:alert-sink (which means alerts are displayed directly in the
+ * content area). The signal emission allows the #EActivity to be further
+ * customized and/or tracked by the application.
+ *
+ * Returns: an #EActivity
+ **/
+EActivity *
+e_web_view_new_activity (EWebView *web_view)
+{
+ EActivity *activity;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ activity = e_activity_new ();
+
+ alert_sink = E_ALERT_SINK (web_view);
+ e_activity_set_alert_sink (activity, alert_sink);
+
+ cancellable = g_cancellable_new ();
+ e_activity_set_cancellable (activity, cancellable);
+ g_object_unref (cancellable);
+
+ g_signal_emit (web_view, signals[NEW_ACTIVITY], 0, activity);
+
+ return activity;
+}
+
+void
+e_web_view_status_message (EWebView *web_view,
+ const gchar *status_message)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ g_signal_emit (web_view, signals[STATUS_MESSAGE], 0, status_message);
+}
+
+void
+e_web_view_stop_loading (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ g_signal_emit (web_view, signals[STOP_LOADING], 0);
+}
+
+void
+e_web_view_update_actions (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ g_signal_emit (web_view, signals[UPDATE_ACTIONS], 0);
+}
+
+const gchar *
+e_web_view_get_citation_color_for_level (gint level)
+{
+ /* Block quote border colors are borrowed from Thunderbird. */
+ static const gchar *citation_color_levels[5] = {
+ "rgb(233,185,110)", /* level 5 - Chocolate 1 */
+ "rgb(114,159,207)", /* level 1 - Sky Blue 1 */
+ "rgb(173,127,168)", /* level 2 - Plum 1 */
+ "rgb(138,226,52)", /* level 3 - Chameleon 1 */
+ "rgb(252,175,62)", /* level 4 - Orange 1 */
+ };
+
+ g_return_val_if_fail (level > 0, citation_color_levels[1]);
+
+ return citation_color_levels[level % 5];
+}
+
+void
+e_web_view_update_fonts_settings (GSettings *font_settings,
+ PangoFontDescription *ms_font,
+ PangoFontDescription *vw_font,
+ GtkWidget *view_widget)
+{
+ gboolean clean_ms = FALSE, clean_vw = FALSE;
+ const gchar *styles[] = { "normal", "oblique", "italic" };
+ gchar fsbuff[G_ASCII_DTOSTR_BUF_SIZE];
+ GdkColor *link = NULL;
+ GdkColor *visited = NULL;
+ GString *stylesheet;
+ GtkStyleContext *context;
+ PangoFontDescription *ms, *vw;
+ WebKitSettings *wk_settings;
+ WebKitUserContentManager *manager;
+ WebKitUserStyleSheet *style_sheet;
+
+ if (!ms_font) {
+ gchar *font;
+
+ font = g_settings_get_string (
+ font_settings,
+ "monospace-font-name");
+
+ ms = pango_font_description_from_string (
+ (font && *font) ? font : "monospace 10");
+
+ clean_ms = TRUE;
+
+ g_free (font);
+ } else
+ ms = ms_font;
+
+ if (!pango_font_description_get_family (ms) ||
+ !pango_font_description_get_size (ms)) {
+ if (clean_ms)
+ pango_font_description_free (ms);
+
+ clean_ms = TRUE;
+ ms = pango_font_description_from_string ("monospace 10");
+ }
+
+ if (!vw_font) {
+ gchar *font;
+
+ font = g_settings_get_string (
+ font_settings,
+ "font-name");
+
+ vw = pango_font_description_from_string (
+ (font && *font) ? font : "serif 10");
+
+ clean_vw = TRUE;
+
+ g_free (font);
+ } else
+ vw = vw_font;
+
+ if (!pango_font_description_get_family (vw) ||
+ !pango_font_description_get_size (vw)) {
+ if (clean_vw)
+ pango_font_description_free (vw);
+
+ clean_vw = TRUE;
+ vw = pango_font_description_from_string ("serif 10");
+ }
+
+ stylesheet = g_string_new ("");
+ g_ascii_dtostr (fsbuff, G_ASCII_DTOSTR_BUF_SIZE,
+ ((gdouble) pango_font_description_get_size (vw)) / PANGO_SCALE);
+
+ g_string_append_printf (
+ stylesheet,
+ "body {\n"
+ " font-family: '%s';\n"
+ " font-size: %spt;\n"
+ " font-weight: %d;\n"
+ " font-style: %s;\n",
+ pango_font_description_get_family (vw),
+ fsbuff,
+ pango_font_description_get_weight (vw),
+ styles[pango_font_description_get_style (vw)]);
+
+ g_string_append (stylesheet, "}\n");
+
+ g_ascii_dtostr (fsbuff, G_ASCII_DTOSTR_BUF_SIZE,
+ ((gdouble) pango_font_description_get_size (ms)) / PANGO_SCALE);
+
+ g_string_append_printf (
+ stylesheet,
+ "pre,code,.pre {\n"
+ " font-family: '%s';\n"
+ " font-size: %spt;\n"
+ " font-weight: %d;\n"
+ " font-style: %s;\n"
+ " margin: 0px;\n"
+ "}\n",
+ pango_font_description_get_family (ms),
+ fsbuff,
+ pango_font_description_get_weight (ms),
+ styles[pango_font_description_get_style (ms)]);
+
+ if (view_widget) {
+ context = gtk_widget_get_style_context (view_widget);
+ gtk_style_context_get_style (
+ context,
+ "link-color", &link,
+ "visited-link-color", &visited,
+ NULL);
+
+ if (link == NULL) {
+ GdkRGBA rgba;
+ GtkStateFlags state;
+
+ link = g_slice_new0 (GdkColor);
+ link->blue = G_MAXINT16;
+
+ rgba.alpha = 1;
+ rgba.red = 0;
+ rgba.green = 0;
+ rgba.blue = 1;
+
+ state = gtk_style_context_get_state (context);
+ state = state & (~(GTK_STATE_FLAG_VISITED | GTK_STATE_FLAG_LINK));
+ state = state | GTK_STATE_FLAG_LINK;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_get_color (context, state, &rgba);
+ gtk_style_context_restore (context);
+
+ e_rgba_to_color (&rgba, link);
+ }
+
+ if (visited == NULL) {
+ GdkRGBA rgba;
+ GtkStateFlags state;
+
+ visited = g_slice_new0 (GdkColor);
+ visited->red = G_MAXINT16;
+
+ rgba.alpha = 1;
+ rgba.red = 1;
+ rgba.green = 0;
+ rgba.blue = 0;
+
+ state = gtk_style_context_get_state (context);
+ state = state & (~(GTK_STATE_FLAG_VISITED | GTK_STATE_FLAG_LINK));
+ state = state | GTK_STATE_FLAG_VISITED;
+
+ gtk_style_context_save (context);
+ gtk_style_context_set_state (context, state);
+ gtk_style_context_get_color (context, state, &rgba);
+ gtk_style_context_restore (context);
+
+ e_rgba_to_color (&rgba, visited);
+ }
+
+ g_string_append_printf (
+ stylesheet,
+ "span.navigable, div.navigable, p.navigable {\n"
+ " color: #%06x;\n"
+ "}\n"
+ "a {\n"
+ " color: #%06x;\n"
+ "}\n"
+ "a:visited {\n"
+ " color: #%06x;\n"
+ "}\n",
+ e_color_to_value (link),
+ e_color_to_value (link),
+ e_color_to_value (visited));
+
+ gdk_color_free (link);
+ gdk_color_free (visited);
+
+ g_string_append (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " padding: 0ch 1ch 0ch 1ch;\n"
+ " margin: 0ch;\n"
+ " border-width: 0px 2px 0px 2px;\n"
+ " border-style: none solid none solid;\n"
+ " border-radius: 2px;\n"
+ "}\n");
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ " margin: 0 0 6px 0;\n"
+ "}\n",
+ e_web_view_get_citation_color_for_level (1));
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ " margin: 0ch;\n"
+ "}\n",
+ e_web_view_get_citation_color_for_level (2));
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ " margin: 0ch;\n"
+ "}\n",
+ e_web_view_get_citation_color_for_level (3));
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ " margin: 0ch;\n"
+ "}\n",
+ e_web_view_get_citation_color_for_level (4));
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ " margin: 0ch;\n"
+ "}\n",
+ e_web_view_get_citation_color_for_level (5));
+
+ g_string_append_printf (
+ stylesheet,
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
+ "{\n"
+ " border-color: %s;\n"
+ " padding: 0ch 0ch 0ch 1ch;\n"
+ " margin: 0ch;\n"
+ " border-width: 0px 0px 0px 2px;\n"
+ " border-style: none none none solid;\n"
+ "}\n",
+ e_web_view_get_citation_color_for_level (1));
+ }
+
+ wk_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view_widget));
+
+ g_object_set (
+ wk_settings,
+ "default-font-size",
+ e_util_normalize_font_size (
+ view_widget, pango_font_description_get_size (vw) / PANGO_SCALE),
+ "default-font-family",
+ pango_font_description_get_family (vw),
+ "monospace-font-family",
+ pango_font_description_get_family (ms),
+ "default-monospace-font-size",
+ e_util_normalize_font_size (
+ view_widget, pango_font_description_get_size (ms) / PANGO_SCALE),
+ NULL);
+
+ manager = webkit_web_view_get_user_content_manager (WEBKIT_WEB_VIEW (view_widget));
+ webkit_user_content_manager_remove_all_style_sheets (manager);
+
+ style_sheet = webkit_user_style_sheet_new (
+ stylesheet->str,
+ WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
+ WEBKIT_USER_STYLE_LEVEL_USER,
+ NULL,
+ NULL);
+
+ webkit_user_content_manager_add_style_sheet (manager, style_sheet);
+
+ webkit_user_style_sheet_unref (style_sheet);
+
+ g_string_free (stylesheet, TRUE);
+
+ if (clean_ms)
+ pango_font_description_free (ms);
+ if (clean_vw)
+ pango_font_description_free (vw);
+
+ e_web_view_update_styles (E_WEB_VIEW (view_widget), "*");
+}
+
+WebKitSettings *
+e_web_view_get_default_webkit_settings (void)
+{
+ WebKitSettings *settings;
+
+ settings = webkit_settings_new_with_settings (
+ "auto-load-images", TRUE,
+ "default-charset", "utf-8",
+ "enable-html5-database", FALSE,
+ "enable-dns-prefetching", FALSE,
+ "enable-html5-local-storage", FALSE,
+ "enable-java", FALSE,
+ "enable-javascript", TRUE, /* Needed for JavaScriptCore API to work */
+ "enable-javascript-markup", FALSE, /* Discards user-provided javascript in HTML */
+ "enable-offline-web-application-cache", FALSE,
+ "enable-page-cache", FALSE,
+ "enable-plugins", FALSE,
+ "enable-smooth-scrolling", FALSE,
+ "media-playback-allows-inline", FALSE,
+ "hardware-acceleration-policy", WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER,
+ NULL);
+
+ e_web_view_utils_apply_minimum_font_size (settings);
+
+ return settings;
+}
+
+void
+e_web_view_utils_apply_minimum_font_size (WebKitSettings *wk_settings)
+{
+ GSettings *settings;
+ gint value;
+
+ g_return_if_fail (WEBKIT_IS_SETTINGS (wk_settings));
+
+ settings = e_util_ref_settings ("org.gnome.evolution.shell");
+ value = g_settings_get_int (settings, "webkit-minimum-font-size");
+ g_clear_object (&settings);
+
+ if (value < 0)
+ value = 0;
+
+ if (webkit_settings_get_minimum_font_size (wk_settings) != (guint32) value)
+ webkit_settings_set_minimum_font_size (wk_settings, value);
+}
+
+gint
+e_web_view_get_minimum_font_size (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), -1);
+
+ return web_view->priv->minimum_font_size;
+}
+
+void
+e_web_view_set_minimum_font_size (EWebView *web_view,
+ gint pixels)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->minimum_font_size != pixels) {
+ WebKitSettings *wk_settings;
+
+ web_view->priv->minimum_font_size = pixels;
+
+ wk_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (web_view));
+ e_web_view_utils_apply_minimum_font_size (wk_settings);
+
+ g_object_notify (G_OBJECT (web_view), "minimum-font-size");
+ }
+}
+
+GCancellable *
+e_web_view_get_cancellable (EWebView *web_view)
+{
+ g_return_val_if_fail (E_IS_WEB_VIEW (web_view), NULL);
+
+ return web_view->priv->cancellable;
+}
+
+void
+e_web_view_update_fonts (EWebView *web_view)
+{
+ EWebViewClass *class;
+ PangoFontDescription *ms = NULL, *vw = NULL;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ class = E_WEB_VIEW_GET_CLASS (web_view);
+ g_return_if_fail (class != NULL);
+
+ if (class->set_fonts != NULL)
+ class->set_fonts (web_view, &ms, &vw);
+
+ e_web_view_update_fonts_settings (
+ web_view->priv->font_settings,
+ ms, vw, GTK_WIDGET (web_view));
+
+ pango_font_description_free (ms);
+ pango_font_description_free (vw);
+}
+
+/* Helper for e_web_view_cursor_image_copy() */
+static void
+web_view_cursor_image_copy_pixbuf_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EActivity *activity;
+ EAlertSink *alert_sink;
+ GdkPixbuf *pixbuf;
+ GError *local_error = NULL;
+
+ activity = E_ACTIVITY (user_data);
+ alert_sink = e_activity_get_alert_sink (activity);
+
+ pixbuf = gdk_pixbuf_new_from_stream_finish (result, &local_error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((pixbuf != NULL) && (local_error == NULL)) ||
+ ((pixbuf == NULL) && (local_error != NULL)));
+
+ if (e_activity_handle_cancellation (activity, local_error)) {
+ g_error_free (local_error);
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ alert_sink,
+ "widgets:no-image-copy",
+ local_error->message, NULL);
+ g_error_free (local_error);
+
+ } else {
+ GtkClipboard *clipboard;
+
+ clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
+ gtk_clipboard_set_image (clipboard, pixbuf);
+ gtk_clipboard_store (clipboard);
+
+ e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
+ }
+
+ g_clear_object (&activity);
+ g_clear_object (&pixbuf);
+}
+
+/* Helper for e_web_view_cursor_image_copy() */
+static void
+web_view_cursor_image_copy_request_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EActivity *activity;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+ GInputStream *input_stream;
+ GError *local_error = NULL;
+
+ activity = E_ACTIVITY (user_data);
+ alert_sink = e_activity_get_alert_sink (activity);
+ cancellable = e_activity_get_cancellable (activity);
+
+ input_stream = e_web_view_request_finish (
+ E_WEB_VIEW (source_object), result, &local_error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((input_stream != NULL) && (local_error == NULL)) ||
+ ((input_stream == NULL) && (local_error != NULL)));
+
+ if (e_activity_handle_cancellation (activity, local_error)) {
+ g_error_free (local_error);
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ alert_sink,
+ "widgets:no-image-copy",
+ local_error->message, NULL);
+ g_error_free (local_error);
+
+ } else {
+ gdk_pixbuf_new_from_stream_async (
+ input_stream, cancellable,
+ web_view_cursor_image_copy_pixbuf_cb,
+ g_object_ref (activity));
+ }
+
+ g_clear_object (&activity);
+ g_clear_object (&input_stream);
+}
+
+/**
+ * e_web_view_cursor_image_copy:
+ * @web_view: an #EWebView
+ *
+ * Asynchronously copies the image under the cursor to the clipboard.
+ *
+ * This function triggers an #EWebView::new-activity signal emission so
+ * the asynchronous operation can be tracked and/or cancelled.
+ **/
+void
+e_web_view_cursor_image_copy (EWebView *web_view)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->cursor_image_src != NULL) {
+ EActivity *activity;
+ GCancellable *cancellable;
+ const gchar *text;
+
+ activity = e_web_view_new_activity (web_view);
+ cancellable = e_activity_get_cancellable (activity);
+
+ text = _("Copying image to clipboard");
+ e_activity_set_text (activity, text);
+
+ e_web_view_request (
+ web_view,
+ web_view->priv->cursor_image_src,
+ cancellable,
+ web_view_cursor_image_copy_request_cb,
+ g_object_ref (activity));
+
+ g_object_unref (activity);
+ }
+}
+
+/* Helper for e_web_view_cursor_image_save() */
+static void
+web_view_cursor_image_save_splice_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EActivity *activity;
+ EAlertSink *alert_sink;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) user_data;
+
+ activity = async_context->activity;
+ alert_sink = e_activity_get_alert_sink (activity);
+
+ g_output_stream_splice_finish (
+ G_OUTPUT_STREAM (source_object), result, &local_error);
+
+ if (e_activity_handle_cancellation (activity, local_error)) {
+ g_error_free (local_error);
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ alert_sink,
+ "widgets:no-image-save",
+ local_error->message, NULL);
+ g_error_free (local_error);
+
+ } else {
+ e_activity_set_state (activity, E_ACTIVITY_COMPLETED);
+ }
+
+ async_context_free (async_context);
+}
+
+/* Helper for e_web_view_cursor_image_save() */
+static void
+web_view_cursor_image_save_replace_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EActivity *activity;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+ GFileOutputStream *output_stream;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) user_data;
+
+ activity = async_context->activity;
+ alert_sink = e_activity_get_alert_sink (activity);
+ cancellable = e_activity_get_cancellable (activity);
+
+ output_stream = g_file_replace_finish (
+ G_FILE (source_object), result, &local_error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((output_stream != NULL) && (local_error == NULL)) ||
+ ((output_stream == NULL) && (local_error != NULL)));
+
+ if (e_activity_handle_cancellation (activity, local_error)) {
+ g_error_free (local_error);
+ async_context_free (async_context);
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ alert_sink,
+ "widgets:no-image-save",
+ local_error->message, NULL);
+ g_error_free (local_error);
+ async_context_free (async_context);
+
+ } else {
+ g_output_stream_splice_async (
+ G_OUTPUT_STREAM (output_stream),
+ async_context->input_stream,
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE |
+ G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ web_view_cursor_image_save_splice_cb,
+ async_context);
+ }
+
+ g_clear_object (&output_stream);
+}
+
+/* Helper for e_web_view_cursor_image_save() */
+static void
+web_view_cursor_image_save_request_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EActivity *activity;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+ GInputStream *input_stream;
+ AsyncContext *async_context;
+ GError *local_error = NULL;
+
+ async_context = (AsyncContext *) user_data;
+
+ activity = async_context->activity;
+ alert_sink = e_activity_get_alert_sink (activity);
+ cancellable = e_activity_get_cancellable (activity);
+
+ input_stream = e_web_view_request_finish (
+ E_WEB_VIEW (source_object), result, &local_error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((input_stream != NULL) && (local_error == NULL)) ||
+ ((input_stream == NULL) && (local_error != NULL)));
+
+ if (e_activity_handle_cancellation (activity, local_error)) {
+ g_error_free (local_error);
+ async_context_free (async_context);
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ alert_sink,
+ "widgets:no-image-save",
+ local_error->message, NULL);
+ g_error_free (local_error);
+ async_context_free (async_context);
+
+ } else {
+ async_context->input_stream = g_object_ref (input_stream);
+
+ /* Open an output stream to the destination file. */
+ g_file_replace_async (
+ async_context->destination,
+ NULL, FALSE,
+ G_FILE_CREATE_REPLACE_DESTINATION,
+ G_PRIORITY_DEFAULT,
+ cancellable,
+ web_view_cursor_image_save_replace_cb,
+ async_context);
+ }
+
+ g_clear_object (&input_stream);
+}
+
+/**
+ * e_web_view_cursor_image_save:
+ * @web_view: an #EWebView
+ *
+ * Prompts the user to choose a destination file and then asynchronously
+ * saves the image under the cursor to the destination file.
+ *
+ * This function triggers an #EWebView::new-activity signal emission so
+ * the asynchronous operation can be tracked and/or cancelled.
+ **/
+void
+e_web_view_cursor_image_save (EWebView *web_view)
+{
+ GtkFileChooser *file_chooser;
+ GtkFileChooserNative *native;
+ GFile *destination = NULL;
+ gchar *suggestion;
+ gpointer toplevel;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ if (web_view->priv->cursor_image_src == NULL)
+ return;
+
+ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (web_view));
+ toplevel = gtk_widget_is_toplevel (toplevel) ? toplevel : NULL;
+
+ native = gtk_file_chooser_native_new (
+ _("Save Image"), toplevel,
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ _("_Save"), _("_Cancel"));
+
+ file_chooser = GTK_FILE_CHOOSER (native);
+ gtk_file_chooser_set_local_only (file_chooser, FALSE);
+ gtk_file_chooser_set_do_overwrite_confirmation (file_chooser, TRUE);
+
+ suggestion = e_web_view_suggest_filename (
+ web_view, web_view->priv->cursor_image_src);
+
+ if (suggestion != NULL) {
+ gtk_file_chooser_set_current_name (file_chooser, suggestion);
+ g_free (suggestion);
+ }
+
+ e_util_load_file_chooser_folder (file_chooser);
+
+ if (gtk_native_dialog_run (GTK_NATIVE_DIALOG (native)) == GTK_RESPONSE_ACCEPT) {
+ e_util_save_file_chooser_folder (file_chooser);
+
+ destination = gtk_file_chooser_get_file (file_chooser);
+ }
+
+ g_object_unref (native);
+
+ if (destination != NULL) {
+ EActivity *activity;
+ GCancellable *cancellable;
+ AsyncContext *async_context;
+ gchar *text;
+ gchar *uri;
+
+ activity = e_web_view_new_activity (web_view);
+ cancellable = e_activity_get_cancellable (activity);
+
+ uri = g_file_get_uri (destination);
+ text = g_strdup_printf (_("Saving image to “%s”"), uri);
+ e_activity_set_text (activity, text);
+ g_free (text);
+ g_free (uri);
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->activity = g_object_ref (activity);
+ async_context->destination = g_object_ref (destination);
+
+ e_web_view_request (
+ web_view,
+ web_view->priv->cursor_image_src,
+ cancellable,
+ web_view_cursor_image_save_request_cb,
+ async_context);
+
+ g_object_unref (activity);
+ g_object_unref (destination);
+ }
+}
+
+/* Helper for e_web_view_request() */
+static void
+web_view_request_process_thread (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ AsyncContext *async_context = task_data;
+ gint64 stream_length = -1;
+ gchar *mime_type = NULL;
+ GError *local_error = NULL;
+
+ if (!e_content_request_process_sync (async_context->content_request,
+ async_context->uri, source_object, &async_context->input_stream,
+ &stream_length, &mime_type, cancellable, &local_error)) {
+ g_task_return_error (task, local_error);
+ } else {
+ g_task_return_boolean (task, TRUE);
+ }
+
+ g_free (mime_type);
+}
+
+/**
+ * e_web_view_request:
+ * @web_view: an #EWebView
+ * @uri: the URI to load
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the request is satisfied
+ * @user_data: data to pass to the callback function
+ *
+ * Asynchronously requests data at @uri as displaed in the @web_view.
+ *
+ * When the operation is finished, @callback will be called. You can then
+ * call e_web_view_request_finish() to get the result of the operation.
+ **/
+void
+e_web_view_request (EWebView *web_view,
+ const gchar *uri,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ EContentRequest *content_request = NULL;
+ AsyncContext *async_context;
+ GHashTableIter iter;
+ GTask *task;
+ gboolean is_processed = FALSE;
+ gpointer value;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (uri != NULL);
+
+ g_hash_table_iter_init (&iter, web_view->priv->scheme_handlers);
+
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ EContentRequest *adept = value;
+
+ if (!E_IS_CONTENT_REQUEST (adept) ||
+ !e_content_request_can_process_uri (adept, uri))
+ continue;
+
+ content_request = adept;
+ break;
+ }
+
+ async_context = g_slice_new0 (AsyncContext);
+ async_context->uri = g_strdup (uri);
+
+ task = g_task_new (web_view, cancellable, callback, user_data);
+ g_task_set_task_data (task, async_context, async_context_free);
+ g_task_set_check_cancellable (task, TRUE);
+
+ if (content_request) {
+ async_context->content_request = g_object_ref (content_request);
+ g_task_run_in_thread (task, web_view_request_process_thread);
+ is_processed = TRUE;
+
+ /* Handle base64-encoded "data:" URIs manually */
+ } else if (g_ascii_strncasecmp (uri, "data:", 5) == 0) {
+ /* data:[<mime type>][;charset=<charset>][;base64],<encoded data> */
+ const gchar *ptr, *from;
+ gboolean is_base64 = FALSE;
+
+ ptr = uri + 5;
+ from = ptr;
+ while (*ptr && *ptr != ',') {
+ ptr++;
+
+ if (*ptr == ',' || *ptr == ';') {
+ if (g_ascii_strncasecmp (from, "base64", ptr - from) == 0)
+ is_base64 = TRUE;
+
+ from = ptr + 1;
+ }
+ }
+
+ if (is_base64 && *ptr == ',') {
+ guchar *data;
+ gsize len = 0;
+
+ data = g_base64_decode (ptr + 1, &len);
+
+ if (data && len > 0) {
+ async_context->input_stream = g_memory_input_stream_new_from_data (data, len, g_free);
+ g_task_return_boolean (task, TRUE);
+ is_processed = TRUE;
+ } else {
+ g_free (data);
+ }
+ }
+ }
+
+ if (!is_processed) {
+ GString *shorten_uri = NULL;
+ gint len;
+
+ len = g_utf8_strlen (uri, -1);
+
+ /* The "data:" URIs can be quite long */
+ if (len > 512) {
+ const gchar *ptr = g_utf8_offset_to_pointer (uri, 512);
+
+ shorten_uri = g_string_sized_new (ptr - uri + 16);
+ g_string_append_len (shorten_uri, uri, ptr - uri);
+ g_string_append (shorten_uri, _("…"));
+ }
+
+ g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Cannot get URI “%s”, do not know how to download it."), shorten_uri ? shorten_uri->str : uri);
+
+ if (shorten_uri)
+ g_string_free (shorten_uri, TRUE);
+ }
+
+ g_object_unref (task);
+}
+
+/**
+ * e_web_view_request_finish:
+ * @web_view: an #EWebView
+ * @result: a #GAsyncResult
+ * @error: return location for a #GError, or %NULL
+ *
+ * Finishes the operation started with e_web_view_request().
+ *
+ * Unreference the returned #GInputStream with g_object_unref() when finished
+ * with it. If an error occurred, the function will set @error and return
+ * %NULL.
+ *
+ * Returns: a #GInputStream, or %NULL
+ **/
+GInputStream *
+e_web_view_request_finish (EWebView *web_view,
+ GAsyncResult *result,
+ GError **error)
+{
+ AsyncContext *async_context;
+
+ g_return_val_if_fail (g_task_is_valid (result, web_view), NULL);
+
+ if (!g_task_propagate_boolean (G_TASK (result), error))
+ return NULL;
+
+ async_context = g_task_get_task_data (G_TASK (result));
+
+ g_return_val_if_fail (async_context->input_stream != NULL, NULL);
+
+ return g_object_ref (async_context->input_stream);
+}
+
+/**
+ * e_web_view_set_iframe_src:
+ * @web_view: an #EWebView
+ * @document_uri: a document URI for whose IFrame change the source
+ * @src_uri: the source to change the IFrame to
+ *
+ * Change IFrame source for the given @document_uri IFrame
+ * to the @new_iframe_src.
+ *
+ * Since: 3.22
+ **/
+void
+e_web_view_set_iframe_src (EWebView *web_view,
+ const gchar *iframe_id,
+ const gchar *src_uri)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+
+ e_web_view_jsc_run_script (WEBKIT_WEB_VIEW (web_view), web_view->priv->cancellable,
+ "Evo.SetIFrameSrc(%s, %s);",
+ iframe_id, src_uri);
+}
+
+/**
+ * EWebViewElementClickedFunc:
+ * @web_view: an #EWebView
+ * @iframe_id: an iframe ID in which the click happened; empty string for the main frame
+ * @element_id: an element ID
+ * @element_class: an element class, as set on the element which had been clicked
+ * @element_value: a 'value' attribute content of the clicked element
+ * @element_position: a #GtkAllocation with the position of the clicked element
+ * @user_data: user data as provided in the e_web_view_register_element_clicked() call
+ *
+ * The callback is called whenever an element of class @element_class is clicked.
+ * The @element_value is a content of the 'value' attribute of the clicked element.
+ * The @element_position is the place of the element within the web page, already
+ * accounting scrollbar positions.
+ *
+ * See: e_web_view_register_element_clicked, e_web_view_unregister_element_clicked
+ *
+ * Since: 3.22
+ **/
+
+/**
+ * e_web_view_register_element_clicked:
+ * @web_view: an #EWebView
+ * @element_class: an element class on which to listen for clicking
+ * @callback: an #EWebViewElementClickedFunc to call, when the element is clicked
+ * @user_data: user data to pass to @callback
+ *
+ * Registers a @callback to be called when any element of the class @element_class
+ * is clicked. If the element contains a 'value' attribute, then it is passed to
+ * the @callback too. These callback are valid until a new content of the @web_view
+ * is loaded, after which all the registered callbacks are forgotten.
+ *
+ * Since: 3.22
+ **/
+void
+e_web_view_register_element_clicked (EWebView *web_view,
+ const gchar *element_class,
+ EWebViewElementClickedFunc callback,
+ gpointer user_data)
+{
+ ElementClickedData *ecd;
+ GPtrArray *cbs;
+ guint ii;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_class != NULL);
+ g_return_if_fail (callback != NULL);
+
+ cbs = g_hash_table_lookup (web_view->priv->element_clicked_cbs, element_class);
+ if (cbs) {
+ for (ii = 0; ii < cbs->len; ii++) {
+ ecd = g_ptr_array_index (cbs, ii);
+
+ if (ecd && ecd->callback == callback && ecd->user_data == user_data) {
+ /* Callback is already registered, but re-register it, in case the page
+ was changed dynamically and new elements with the given call are added. */
+ web_view_call_register_element_clicked (web_view, "*", element_class);
+ return;
+ }
+ }
+ }
+
+ ecd = g_new0 (ElementClickedData, 1);
+ ecd->callback = callback;
+ ecd->user_data = user_data;
+
+ if (!cbs) {
+ cbs = g_ptr_array_new_full (1, g_free);
+ g_ptr_array_add (cbs, ecd);
+
+ g_hash_table_insert (web_view->priv->element_clicked_cbs, g_strdup (element_class), cbs);
+ } else {
+ g_ptr_array_add (cbs, ecd);
+ }
+
+ /* Dynamically changing page can call this multiple times; re-register all classes */
+ web_view_call_register_element_clicked (web_view, "*", NULL);
+}
+
+/**
+ * e_web_view_unregister_element_clicked:
+ * @web_view: an #EWebView
+ * @element_class: an element class on which to listen for clicking
+ * @callback: an #EWebViewElementClickedFunc to call, when the element is clicked
+ * @user_data: user data to pass to @callback
+ *
+ * Unregisters the @callback for the @element_class with the given @user_data, which
+ * should be previously registered with e_web_view_register_element_clicked(). This
+ * unregister is usually not needed, because the @web_view unregisters all callbacks
+ * when a new content is loaded.
+ *
+ * Since: 3.22
+ **/
+void
+e_web_view_unregister_element_clicked (EWebView *web_view,
+ const gchar *element_class,
+ EWebViewElementClickedFunc callback,
+ gpointer user_data)
+{
+ ElementClickedData *ecd;
+ GPtrArray *cbs;
+ guint ii;
+
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_class != NULL);
+ g_return_if_fail (callback != NULL);
+
+ cbs = g_hash_table_lookup (web_view->priv->element_clicked_cbs, element_class);
+ if (!cbs)
+ return;
+
+ for (ii = 0; ii < cbs->len; ii++) {
+ ecd = g_ptr_array_index (cbs, ii);
+
+ if (ecd && ecd->callback == callback && ecd->user_data == user_data) {
+ g_ptr_array_remove (cbs, ecd);
+ if (!cbs->len)
+ g_hash_table_remove (web_view->priv->element_clicked_cbs, element_class);
+ break;
+ }
+ }
+}
+
+void
+e_web_view_set_element_hidden (EWebView *web_view,
+ const gchar *element_id,
+ gboolean hidden)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_id && *element_id);
+
+ e_web_view_jsc_set_element_hidden (WEBKIT_WEB_VIEW (web_view),
+ "*", element_id, hidden,
+ web_view->priv->cancellable);
+}
+
+void
+e_web_view_set_element_style_property (EWebView *web_view,
+ const gchar *element_id,
+ const gchar *property_name,
+ const gchar *value)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_id && *element_id);
+ g_return_if_fail (property_name && *property_name);
+
+ e_web_view_jsc_set_element_style_property (WEBKIT_WEB_VIEW (web_view),
+ "*", element_id, property_name, value,
+ web_view->priv->cancellable);
+}
+
+void
+e_web_view_set_element_attribute (EWebView *web_view,
+ const gchar *element_id,
+ const gchar *namespace_uri,
+ const gchar *qualified_name,
+ const gchar *value)
+{
+ g_return_if_fail (E_IS_WEB_VIEW (web_view));
+ g_return_if_fail (element_id && *element_id);
+ g_return_if_fail (qualified_name && *qualified_name);
+
+ e_web_view_jsc_set_element_attribute (WEBKIT_WEB_VIEW (web_view),
+ "*", element_id, namespace_uri, qualified_name, value,
+ web_view->priv->cancellable);
+}
diff -urN a/src/e-util/test-html-editor-units-utils.c b/src/e-util/test-html-editor-units-utils.c
--- a/src/e-util/test-html-editor-units-utils.c 2025-06-07 14:20:57.327397173 -0700
+++ b/src/e-util/test-html-editor-units-utils.c 2025-06-07 14:27:24.051673010 -0700
@@ -227,7 +227,7 @@
return FALSE;
}
-/* <Control>+<Shift>+I */
+/* <Super>+<Shift>+I */
#define WEBKIT_INSPECTOR_MOD (GDK_CONTROL_MASK | GDK_SHIFT_MASK)
#define WEBKIT_INSPECTOR_KEY (GDK_KEY_I)
diff -urN a/src/e-util/test-html-editor.c b/src/e-util/test-html-editor.c
--- a/src/e-util/test-html-editor.c 2025-06-07 14:20:57.331397221 -0700
+++ b/src/e-util/test-html-editor.c 2025-06-07 14:27:24.051673010 -0700
@@ -423,7 +423,7 @@
{ "new-editor",
"document-new",
N_("_New editor"),
- "<Control>N",
+ "<Super>N",
NULL,
G_CALLBACK (action_new_editor_cb) },
@@ -431,14 +431,14 @@
{ "print",
"document-print",
N_("_Print…"),
- "<Control>p",
+ "<Super>p",
NULL,
G_CALLBACK (action_print_cb) },
{ "print-preview",
"document-print-preview",
N_("Print Pre_view"),
- "<Control><Shift>p",
+ "<Super><Shift>p",
NULL,
G_CALLBACK (action_print_preview_cb) },
#endif /* ENABLE_PRINT */
@@ -513,7 +513,7 @@
NULL,
N_("Inspector"),
NULL,
- "<Control><Shift>I",
+ "<Super><Shift>I",
G_CALLBACK (action_view_inspector) },
{ "view-menu",
diff -urN a/src/mail/e-mail-browser.c b/src/mail/e-mail-browser.c
--- a/src/mail/e-mail-browser.c 2025-06-07 14:20:57.347397414 -0700
+++ b/src/mail/e-mail-browser.c 2025-06-07 14:27:24.055673052 -0700
@@ -164,28 +164,28 @@
{ "close",
"window-close",
N_("_Close"),
- "<Control>w",
+ "<Super>w",
N_("Close this window"),
G_CALLBACK (action_close_cb) },
{ "copy-clipboard",
"edit-copy",
N_("_Copy"),
- "<Control>c",
+ "<Super>c",
N_("Copy the selection"),
NULL }, /* Handled by EFocusTracker */
{ "cut-clipboard",
"edit-cut",
N_("Cu_t"),
- "<Control>x",
+ "<Super>x",
N_("Cut the selection"),
NULL }, /* Handled by EFocusTracker */
{ "paste-clipboard",
"edit-paste",
N_("_Paste"),
- "<Control>v",
+ "<Super>v",
N_("Paste the clipboard"),
NULL }, /* Handled by EFocusTracker */
diff -urN a/src/mail/e-mail-notes.c b/src/mail/e-mail-notes.c
--- a/src/mail/e-mail-notes.c 2025-06-07 14:20:57.351397462 -0700
+++ b/src/mail/e-mail-notes.c 2025-06-07 14:27:24.055673052 -0700
@@ -1107,14 +1107,14 @@
{ "close",
"window-close",
N_("_Close"),
- "<Control>w",
+ "<Super>w",
N_("Close"),
G_CALLBACK (action_close_cb) },
{ "save-and-close",
"document-save",
N_("_Save and Close"),
- "<Control>Return",
+ "<Alt><Super>s",
N_("Save and Close"),
G_CALLBACK (action_save_and_close_cb) },
diff -urN a/src/mail/e-mail-reader.c b/src/mail/e-mail-reader.c
--- a/src/mail/e-mail-reader.c 2025-06-07 14:20:57.351397462 -0700
+++ b/src/mail/e-mail-reader.c 2025-06-07 14:27:24.055673052 -0700
@@ -2514,14 +2514,14 @@
{ "mail-archive",
"mail-archive",
N_("_Archive…"),
- "<Alt><Control>a",
+ "<Alt><Super>a",
N_("Move selected messages to the Archive folder for the account"),
G_CALLBACK (action_mail_archive_cb) },
{ "mail-check-for-junk",
"mail-mark-junk",
N_("Check for _Junk"),
- "<Control><Alt>j",
+ "<Super><Alt>j",
N_("Filter the selected messages for junk status"),
G_CALLBACK (action_mail_check_for_junk_cb) },
@@ -2542,14 +2542,14 @@
{ "mail-copy",
"mail-copy",
N_("_Copy to Folder…"),
- "<Shift><Control>y",
+ "<Shift><Super>y",
N_("Copy selected messages to another folder"),
G_CALLBACK (action_mail_copy_cb) },
{ "mail-delete",
"user-trash",
N_("_Delete Message"),
- "<Control>d",
+ "<Super>d",
N_("Mark the selected messages for deletion"),
G_CALLBACK (action_mail_delete_cb) },
@@ -2605,14 +2605,14 @@
{ "mail-filters-apply",
"stock_mail-filters-apply",
N_("A_pply Filters"),
- "<Control>y",
+ "<Super>y",
N_("Apply filter rules to the selected messages"),
G_CALLBACK (action_mail_filters_apply_cb) },
{ "mail-find",
"edit-find",
N_("_Find in Message…"),
- "<Shift><Control>f",
+ "<Shift><Super>f",
N_("Search for text in the body of the displayed message"),
G_CALLBACK (action_mail_find_cb) },
@@ -2633,14 +2633,14 @@
{ "mail-flag-for-followup",
"stock_mail-flag-for-followup",
N_("Follow _Up…"),
- "<Shift><Control>g",
+ "<Shift><Super>g",
N_("Flag the selected messages for follow-up"),
G_CALLBACK (action_mail_flag_for_followup_cb) },
{ "mail-forward",
"mail-forward",
N_("_Forward"),
- "<Control>f",
+ "<Super>f",
N_("Forward the selected message to someone"),
G_CALLBACK (action_mail_forward_cb) },
@@ -2705,7 +2705,7 @@
{ "mail-load-images",
"image-x-generic",
N_("_Load Images"),
- "<Control>i",
+ "<Super>i",
N_("Force images in HTML mail to be loaded"),
G_CALLBACK (action_mail_load_images_cb) },
@@ -2733,21 +2733,21 @@
{ "mail-mark-junk",
"mail-mark-junk",
N_("_Junk"),
- "<Control>j",
+ "<Super>j",
N_("Mark the selected messages as junk"),
G_CALLBACK (action_mail_mark_junk_cb) },
{ "mail-mark-notjunk",
"mail-mark-notjunk",
N_("_Not Junk"),
- "<Shift><Control>j",
+ "<Shift><Super>j",
N_("Mark the selected messages as not being junk"),
G_CALLBACK (action_mail_mark_notjunk_cb) },
{ "mail-mark-read",
"mail-mark-read",
N_("_Read"),
- "<Control>k",
+ "<Super>k",
N_("Mark the selected messages as having been read"),
G_CALLBACK (action_mail_mark_read_cb) },
@@ -2775,7 +2775,7 @@
{ "mail-mark-unread",
"mail-mark-unread",
N_("_Unread"),
- "<Shift><Control>k",
+ "<Shift><Super>k",
N_("Mark the selected messages as not having been read"),
G_CALLBACK (action_mail_mark_unread_cb) },
@@ -2789,28 +2789,28 @@
{ "mail-message-new",
"mail-message-new",
N_("Compose _New Message"),
- "<Shift><Control>m",
+ "<Shift><Super>m",
N_("Open a window for composing a mail message"),
G_CALLBACK (action_mail_message_new_cb) },
{ "mail-message-open",
NULL,
N_("_Open in New Window"),
- "<Control>o",
+ "<Super>o",
N_("Open the selected messages in a new window"),
G_CALLBACK (action_mail_message_open_cb) },
{ "mail-move",
"mail-move",
N_("_Move to Folder…"),
- "<Shift><Control>v",
+ "<Shift><Super>v",
N_("Move selected messages to another folder"),
G_CALLBACK (action_mail_move_cb) },
{ "mail-next",
"go-next",
N_("_Next Message"),
- "<Control>Page_Down",
+ "<Super>Page_Down",
N_("Display the next message"),
G_CALLBACK (action_mail_next_cb) },
@@ -2831,14 +2831,14 @@
{ "mail-next-unread",
"go-jump",
N_("Next _Unread Message"),
- "<Control>bracketright",
+ "<Super>bracketright",
N_("Display the next unread message"),
G_CALLBACK (action_mail_next_unread_cb) },
{ "mail-previous",
"go-previous",
N_("_Previous Message"),
- "<Control>Page_Up",
+ "<Super>Page_Up",
N_("Display the previous message"),
G_CALLBACK (action_mail_previous_cb) },
@@ -2859,14 +2859,14 @@
{ "mail-previous-unread",
NULL,
N_("P_revious Unread Message"),
- "<Control>bracketleft",
+ "<Super>bracketleft",
N_("Display the previous unread message"),
G_CALLBACK (action_mail_previous_unread_cb) },
{ "mail-print",
"document-print",
N_("_Print…"),
- "<Control>p",
+ "<Super>p",
N_("Print this message"),
G_CALLBACK (action_mail_print_cb) },
@@ -2901,35 +2901,35 @@
{ "mail-reply-all",
NULL,
N_("Reply to _All"),
- "<Shift><Control>r",
+ "<Shift><Super>r",
N_("Compose a reply to all the recipients of the selected message"),
G_CALLBACK (action_mail_reply_all_cb) },
{ "mail-reply-alternative",
NULL,
N_("Al_ternative Reply…"),
- "<Alt><Control>r",
+ "<Alt><Super>r",
N_("Choose reply options for the selected message"),
G_CALLBACK (action_mail_reply_alternative_cb) },
{ "mail-reply-group",
"mail-reply-all",
N_("Group Reply"),
- "<Control>g",
+ "<Super>g",
N_("Reply to the mailing list, or to all recipients"),
G_CALLBACK (action_mail_reply_group_cb) },
{ "mail-reply-list",
NULL,
N_("Reply to _List"),
- "<Control>l",
+ "<Super>l",
N_("Compose a reply to the mailing list of the selected message"),
G_CALLBACK (action_mail_reply_list_cb) },
{ "mail-reply-sender",
"mail-reply-sender",
N_("_Reply to Sender"),
- "<Control>r",
+ "<Super>r",
N_("Compose a reply to the sender of the selected message"),
G_CALLBACK (action_mail_reply_sender_cb) },
@@ -2943,7 +2943,7 @@
{ "mail-save-as",
"document-save-as",
N_("_Save as mbox…"),
- "<Control>s",
+ "<Super>s",
N_("Save selected messages as an mbox file"),
G_CALLBACK (action_mail_save_as_cb) },
@@ -2957,7 +2957,7 @@
{ "mail-show-source",
NULL,
N_("_Message Source"),
- "<Control>u",
+ "<Super>u",
N_("Show the raw email source of the message"),
G_CALLBACK (action_mail_show_source_cb) },
@@ -2971,28 +2971,28 @@
{ "mail-undelete",
NULL,
N_("_Undelete Message"),
- "<Shift><Control>d",
+ "<Shift><Super>d",
N_("Undelete the selected messages"),
G_CALLBACK (action_mail_undelete_cb) },
{ "mail-zoom-100",
"zoom-original",
N_("_Normal Size"),
- "<Control>0",
+ "<Super>0",
N_("Reset the text to its original size"),
G_CALLBACK (action_mail_zoom_100_cb) },
{ "mail-zoom-in",
"zoom-in",
N_("_Zoom In"),
- "<Control>plus",
+ "<Super>plus",
N_("Increase the text size"),
G_CALLBACK (action_mail_zoom_in_cb) },
{ "mail-zoom-out",
"zoom-out",
N_("Zoom _Out"),
- "<Control>minus",
+ "<Super>minus",
N_("Decrease the text size"),
G_CALLBACK (action_mail_zoom_out_cb) },
@@ -5238,7 +5238,7 @@
G_CALLBACK (action_mail_forward_cb), reader);
gtk_action_group_add_action_with_accel (
- action_group, GTK_ACTION (menu_tool_action), "<Control>f");
+ action_group, GTK_ACTION (menu_tool_action), "<Super>f");
/* Likewise the "mail-reply-group" action. */
@@ -5263,7 +5263,7 @@
G_CALLBACK (action_mail_reply_group_cb), reader);
gtk_action_group_add_action_with_accel (
- action_group, GTK_ACTION (menu_tool_action), "<Control>g");
+ action_group, GTK_ACTION (menu_tool_action), "<Super>g");
/* Add EMailReader actions for Search Folders. The action group
* should be made invisible if Search Folders are disabled. */
diff -urN a/src/mail/e-mail-reader.c.orig b/src/mail/e-mail-reader.c.orig
--- a/src/mail/e-mail-reader.c.orig 1969-12-31 16:00:00.000000000 -0800
+++ b/src/mail/e-mail-reader.c.orig 2025-06-07 14:20:57.351397462 -0700
@@ -0,0 +1,6452 @@
+/*
+ * e-mail-reader.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "evolution-config.h"
+
+#include "e-mail-reader.h"
+
+#include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
+
+#ifdef HAVE_XFREE
+#include <X11/XF86keysym.h>
+#endif
+
+#include <shell/e-shell-headerbar.h>
+#include <shell/e-shell-utils.h>
+
+#include <libemail-engine/libemail-engine.h>
+
+#include <em-format/e-mail-formatter.h>
+#include <em-format/e-mail-parser.h>
+#include <em-format/e-mail-part-utils.h>
+
+#include "e-mail-backend.h"
+#include "e-mail-browser.h"
+#include "e-mail-enumtypes.h"
+#include "e-mail-label-action.h"
+#include "e-mail-label-dialog.h"
+#include "e-mail-label-list-store.h"
+#include "e-mail-notes.h"
+#include "e-mail-reader-utils.h"
+#include "e-mail-remote-content-popover.h"
+#include "e-mail-ui-session.h"
+#include "e-mail-view.h"
+#include "em-composer-utils.h"
+#include "em-event.h"
+#include "em-folder-selector.h"
+#include "em-folder-tree.h"
+#include "em-utils.h"
+#include "mail-autofilter.h"
+#include "mail-vfolder-ui.h"
+#include "message-list.h"
+
+#define E_MAIL_READER_GET_PRIVATE(obj) \
+ ((EMailReaderPrivate *) g_object_get_qdata \
+ (G_OBJECT (obj), quark_private))
+
+#define d(x)
+
+typedef struct _EMailReaderClosure EMailReaderClosure;
+typedef struct _EMailReaderPrivate EMailReaderPrivate;
+
+struct _EMailReaderClosure {
+ EMailReader *reader;
+ EActivity *activity;
+ CamelMimeMessage *message;
+ CamelFolder *folder;
+ gchar *message_uid;
+ gboolean selection_is_html;
+};
+
+struct _EMailReaderPrivate {
+
+ EMailForwardStyle forward_style;
+ EMailReplyStyle reply_style;
+
+ /* This timer runs when the user selects a single message. */
+ guint message_selected_timeout_id;
+
+ /* This allows message retrieval to be cancelled if another
+ * message is selected before the retrieval has completed. */
+ GCancellable *retrieving_message;
+
+ /* These flags work to prevent a folder switch from
+ * automatically marking the message as read. We only want
+ * that to happen when the -user- selects a message. */
+ guint folder_was_just_selected : 1;
+ guint avoid_next_mark_as_seen : 1;
+ guint did_try_to_open_message : 1;
+
+ guint group_by_threads : 1;
+ guint mark_seen_always : 1;
+ guint delete_selects_previous : 1;
+
+ /* to be able to start the mark_seen timeout only after
+ * the message is loaded into the EMailDisplay */
+ gboolean schedule_mark_seen;
+ guint schedule_mark_seen_interval;
+
+ gpointer followup_alert; /* weak pointer to an EAlert */
+
+ GSList *ongoing_operations; /* GCancellable * */
+
+ guint main_menu_label_merge_id;
+ guint popup_menu_label_merge_id;
+};
+
+enum {
+ CHANGED,
+ COMPOSER_CREATED,
+ FOLDER_LOADED,
+ MESSAGE_LOADED,
+ MESSAGE_SEEN,
+ SHOW_SEARCH_BAR,
+ UPDATE_ACTIONS,
+ LAST_SIGNAL
+};
+
+/* Remembers the previously selected folder when transferring messages. */
+static gchar *default_xfer_messages_uri;
+
+static GQuark quark_private;
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_INTERFACE (EMailReader, e_mail_reader, G_TYPE_INITIALLY_UNOWNED)
+
+static void
+mail_reader_set_display_formatter_for_message (EMailReader *reader,
+ EMailDisplay *display,
+ const gchar *message_uid,
+ CamelMimeMessage *message,
+ CamelFolder *folder);
+
+static void
+mail_reader_closure_free (EMailReaderClosure *closure)
+{
+ g_clear_object (&closure->reader);
+ g_clear_object (&closure->activity);
+ g_clear_object (&closure->folder);
+ g_clear_object (&closure->message);
+ g_free (closure->message_uid);
+
+ g_slice_free (EMailReaderClosure, closure);
+}
+
+static void
+mail_reader_private_free (EMailReaderPrivate *priv)
+{
+ if (priv->message_selected_timeout_id > 0)
+ g_source_remove (priv->message_selected_timeout_id);
+
+ if (priv->retrieving_message != NULL) {
+ g_cancellable_cancel (priv->retrieving_message);
+ g_object_unref (priv->retrieving_message);
+ priv->retrieving_message = NULL;
+ }
+
+ g_slice_free (EMailReaderPrivate, priv);
+}
+
+static void
+action_mail_add_sender_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EShell *shell;
+ EMailBackend *backend;
+ EMailSession *session;
+ EShellBackend *shell_backend;
+ CamelInternetAddress *cia;
+ CamelMessageInfo *info = NULL;
+ CamelFolder *folder;
+ GPtrArray *uids;
+ const gchar *address;
+ const gchar *message_uid;
+
+ folder = e_mail_reader_ref_folder (reader);
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ uids = e_mail_reader_get_selected_uids (reader);
+ g_return_if_fail (uids != NULL && uids->len == 1);
+ message_uid = g_ptr_array_index (uids, 0);
+
+ info = camel_folder_get_message_info (folder, message_uid);
+ if (info == NULL)
+ goto exit;
+
+ address = camel_message_info_get_from (info);
+ if (address == NULL || *address == '\0')
+ goto exit;
+
+ /* XXX EBookShellBackend should be listening for this
+ * event. Kind of kludgey, but works for now. */
+ shell_backend = E_SHELL_BACKEND (backend);
+ shell = e_shell_backend_get_shell (shell_backend);
+ e_shell_event (shell, "contact-quick-add-email", (gpointer) address);
+
+ /* Remove this address from the photo cache. */
+ cia = camel_internet_address_new ();
+ if (camel_address_decode (CAMEL_ADDRESS (cia), address) > 0) {
+ EPhotoCache *photo_cache;
+ const gchar *address_only = NULL;
+
+ photo_cache = e_mail_ui_session_get_photo_cache (
+ E_MAIL_UI_SESSION (session));
+ if (camel_internet_address_get (cia, 0, NULL, &address_only))
+ e_photo_cache_remove_photo (photo_cache, address_only);
+ }
+ g_object_unref (cia);
+
+exit:
+ g_clear_object (&info);
+ g_ptr_array_unref (uids);
+
+ g_clear_object (&folder);
+}
+
+static void
+action_add_to_address_book_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EShell *shell;
+ EMailBackend *backend;
+ EMailDisplay *display;
+ EMailSession *session;
+ EShellBackend *shell_backend;
+ CamelInternetAddress *cia;
+ EPhotoCache *photo_cache;
+ EWebView *web_view;
+ CamelURL *curl;
+ const gchar *uri;
+ const gchar *address_only = NULL;
+ gchar *email;
+
+ /* This action is defined in EMailDisplay. */
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ display = e_mail_reader_get_mail_display (reader);
+ if (display == NULL)
+ return;
+
+ web_view = E_WEB_VIEW (display);
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ curl = camel_url_new (uri, NULL);
+ g_return_if_fail (curl != NULL);
+
+ if (curl->path == NULL || *curl->path == '\0')
+ goto exit;
+
+ cia = camel_internet_address_new ();
+ if (camel_address_decode (CAMEL_ADDRESS (cia), curl->path) < 0) {
+ g_object_unref (cia);
+ goto exit;
+ }
+
+ /* XXX EBookShellBackend should be listening for this
+ * event. Kind of kludgey, but works for now. */
+ shell_backend = E_SHELL_BACKEND (backend);
+ shell = e_shell_backend_get_shell (shell_backend);
+ email = camel_address_format (CAMEL_ADDRESS (cia));
+ e_shell_event (shell, "contact-quick-add-email", email);
+ g_free (email);
+
+ /* Remove this address from the photo cache. */
+ photo_cache = e_mail_ui_session_get_photo_cache (
+ E_MAIL_UI_SESSION (session));
+ if (camel_internet_address_get (cia, 0, NULL, &address_only))
+ e_photo_cache_remove_photo (photo_cache, address_only);
+
+ g_object_unref (cia);
+
+exit:
+ camel_url_free (curl);
+}
+
+static void
+action_mail_charset_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+ EMailFormatter *formatter;
+
+ if (action != current)
+ return;
+
+ display = e_mail_reader_get_mail_display (reader);
+ formatter = e_mail_display_get_formatter (display);
+
+ if (formatter != NULL) {
+ const gchar *charset;
+
+ /* Charset for "Default" action will be NULL. */
+ charset = g_object_get_data (G_OBJECT (action), "charset");
+ e_mail_formatter_set_charset (formatter, charset);
+ }
+}
+
+static void
+action_mail_check_for_junk_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailBackend *backend;
+ EMailSession *session;
+ CamelFolder *folder;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ backend = e_mail_reader_get_backend (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ session = e_mail_backend_get_session (backend);
+
+ mail_filter_folder (
+ session, folder, uids,
+ E_FILTER_SOURCE_JUNKTEST, FALSE);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+mail_reader_copy_or_move_selected_messages (EMailReader *reader,
+ gboolean is_move)
+{
+ CamelFolder *folder;
+ EMailBackend *backend;
+ EMailSession *session;
+ EMFolderSelector *selector;
+ EMFolderTree *folder_tree;
+ EMFolderTreeModel *model;
+ GSettings *settings;
+ GtkWidget *dialog;
+ GtkWindow *window;
+ GPtrArray *uids;
+ const gchar *uri;
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ folder = e_mail_reader_ref_folder (reader);
+ window = e_mail_reader_get_window (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ model = em_folder_tree_model_get_default ();
+
+ dialog = em_folder_selector_new (window, model);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), is_move ? _("Move to Folder") : _("Copy to Folder"));
+
+ selector = EM_FOLDER_SELECTOR (dialog);
+ em_folder_selector_set_can_create (selector, TRUE);
+ em_folder_selector_set_default_button_label (selector, is_move ? _("_Move") : _("C_opy"));
+
+ folder_tree = em_folder_selector_get_folder_tree (selector);
+
+ em_folder_tree_set_excluded (
+ folder_tree,
+ EMFT_EXCLUDE_NOSELECT |
+ EMFT_EXCLUDE_VIRTUAL |
+ EMFT_EXCLUDE_VTRASH);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ if (!g_settings_get_boolean (settings, "copy-move-to-folder-preserve-expand"))
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (folder_tree));
+
+ g_clear_object (&settings);
+
+ em_folder_selector_maybe_collapse_archive_folders (selector);
+
+ if (default_xfer_messages_uri != NULL) {
+ em_folder_tree_set_selected (
+ folder_tree, default_xfer_messages_uri, FALSE);
+ } else if (folder) {
+ gchar *uri = e_mail_folder_uri_from_folder (folder);
+
+ if (uri) {
+ em_folder_tree_set_selected (folder_tree, uri, FALSE);
+ g_free (uri);
+ }
+ }
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
+ goto exit;
+
+ uri = em_folder_selector_get_selected_uri (selector);
+
+ g_free (default_xfer_messages_uri);
+ default_xfer_messages_uri = g_strdup (uri);
+
+ if (uri != NULL)
+ mail_transfer_messages (
+ session, folder, uids,
+ is_move, uri, 0, NULL, NULL);
+
+exit:
+ gtk_widget_destroy (dialog);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+mail_reader_manage_color_flag_on_selection (EMailReader *reader,
+ const gchar *color)
+{
+ CamelFolder *folder;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ if (folder != NULL) {
+ GPtrArray *uids;
+ guint ii;
+
+ camel_folder_freeze (folder);
+
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ CamelMessageInfo *info;
+
+ info = camel_folder_get_message_info (folder, uids->pdata[ii]);
+ if (info) {
+ camel_message_info_set_user_tag (info, "color", color);
+ g_object_unref (info);
+ }
+ }
+
+ g_ptr_array_unref (uids);
+
+ camel_folder_thaw (folder);
+
+ g_object_unref (folder);
+ }
+}
+
+static void
+action_mail_color_assign_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_color_chooser_dialog_new (NULL, e_mail_reader_get_window (reader));
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+ GdkRGBA rgba;
+ gchar *color;
+
+ gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (dialog), &rgba);
+
+ color = g_strdup_printf ("#%02X%02X%02X",
+ 0xFF & ((gint) (255 * rgba.red)),
+ 0xFF & ((gint) (255 * rgba.green)),
+ 0xFF & ((gint) (255 * rgba.blue)));
+
+ if (color) {
+ mail_reader_manage_color_flag_on_selection (reader, color);
+
+ g_free (color);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_mail_color_unset_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ mail_reader_manage_color_flag_on_selection (reader, NULL);
+}
+
+static void
+action_mail_copy_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ mail_reader_copy_or_move_selected_messages (reader, FALSE);
+}
+
+static gboolean
+mail_reader_replace_vee_folder_with_real (CamelFolder **inout_folder,
+ const gchar *uid,
+ gchar **out_real_uid)
+{
+ g_return_val_if_fail (inout_folder != NULL, FALSE);
+ g_return_val_if_fail (CAMEL_IS_FOLDER (*inout_folder), FALSE);
+ g_return_val_if_fail (uid != NULL, FALSE);
+ g_return_val_if_fail (out_real_uid != NULL, FALSE);
+
+ *out_real_uid = NULL;
+
+ if (CAMEL_IS_VEE_FOLDER (*inout_folder)) {
+ CamelMessageInfo *info;
+
+ info = camel_folder_get_message_info (*inout_folder, uid);
+ if (info) {
+ CamelFolder *real_folder;
+
+ real_folder = camel_vee_folder_get_location (CAMEL_VEE_FOLDER (*inout_folder), CAMEL_VEE_MESSAGE_INFO (info), out_real_uid);
+
+ if (real_folder && *out_real_uid) {
+ g_object_unref (*inout_folder);
+
+ *inout_folder = g_object_ref (real_folder);
+ }
+
+ g_object_unref (info);
+ } else {
+ g_warn_if_reached ();
+ }
+ }
+
+ return *out_real_uid != NULL;
+}
+
+static void
+action_mail_edit_note_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids (reader);
+
+ if (uids && uids->len == 1) {
+ gchar *real_uid = NULL;
+ const gchar *uid = uids->pdata[0];
+
+ if (mail_reader_replace_vee_folder_with_real (&folder, uid, &real_uid))
+ uid = real_uid;
+
+ e_mail_notes_edit (e_mail_reader_get_window (reader), folder, uid);
+
+ g_free (real_uid);
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+typedef struct {
+ CamelFolder *folder;
+ gchar *uid;
+} DeleteNoteData;
+
+static void
+delete_note_data_free (gpointer ptr)
+{
+ DeleteNoteData *dnd = ptr;
+
+ if (dnd) {
+ g_clear_object (&dnd->folder);
+ g_free (dnd->uid);
+ g_slice_free (DeleteNoteData, dnd);
+ }
+}
+
+static void
+mail_delete_note_thread (EAlertSinkThreadJobData *job_data,
+ gpointer user_data,
+ GCancellable *cancellable,
+ GError **error)
+{
+ DeleteNoteData *dnd = user_data;
+
+ g_return_if_fail (dnd != NULL);
+ g_return_if_fail (CAMEL_IS_FOLDER (dnd->folder));
+ g_return_if_fail (dnd->uid != NULL);
+
+ e_mail_notes_remove_sync (dnd->folder, dnd->uid, cancellable, error);
+}
+
+static void
+action_mail_delete_note_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids (reader);
+
+ if (uids && uids->len == 1) {
+ DeleteNoteData *dnd;
+ EAlertSink *alert_sink;
+ EActivity *activity;
+ gchar *full_display_name;
+ gchar *real_uid = NULL;
+ const gchar *uid = uids->pdata[0];
+
+ if (mail_reader_replace_vee_folder_with_real (&folder, uid, &real_uid))
+ uid = real_uid;
+
+ dnd = g_slice_new0 (DeleteNoteData);
+ dnd->folder = g_object_ref (folder);
+ dnd->uid = g_strdup (uid);
+
+ full_display_name = e_mail_folder_to_full_display_name (folder, NULL);
+ alert_sink = e_mail_reader_get_alert_sink (reader);
+
+ activity = e_alert_sink_submit_thread_job (alert_sink,
+ _("Deleting message note…"),
+ "mail:failed-delete-note",
+ full_display_name ? full_display_name : camel_folder_get_full_name (folder),
+ mail_delete_note_thread, dnd, delete_note_data_free);
+
+ if (activity)
+ e_shell_backend_add_activity (E_SHELL_BACKEND (e_mail_reader_get_backend (reader)), activity);
+
+ g_clear_object (&activity);
+ g_free (full_display_name);
+ g_free (real_uid);
+ } else {
+ g_warn_if_reached ();
+ }
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_delete_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED;
+ guint32 set = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED;
+
+ if (!e_mail_reader_confirm_delete (reader))
+ return;
+
+ /* FIXME Verify all selected messages are deletable.
+ * But handle it by disabling this action. */
+
+ if (e_mail_reader_mark_selected (reader, mask, set) != 0 &&
+ !e_mail_reader_close_on_delete_or_junk (reader)) {
+ if (e_mail_reader_get_delete_selects_previous (reader))
+ e_mail_reader_select_previous_message (reader, FALSE);
+ else
+ e_mail_reader_select_next_message (reader, FALSE);
+ }
+}
+
+static void
+action_mail_filter_on_mailing_list_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_filter_from_selected (reader, AUTO_MLIST);
+}
+
+static void
+action_mail_filter_on_recipients_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_filter_from_selected (reader, AUTO_TO);
+}
+
+static void
+action_mail_filter_on_sender_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_filter_from_selected (reader, AUTO_FROM);
+}
+
+static void
+action_mail_filter_on_subject_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_filter_from_selected (reader, AUTO_SUBJECT);
+}
+
+static void
+action_mail_filters_apply_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailBackend *backend;
+ EMailSession *session;
+ CamelFolder *folder;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ backend = e_mail_reader_get_backend (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ session = e_mail_backend_get_session (backend);
+
+ mail_filter_folder (
+ session, folder, uids,
+ E_FILTER_SOURCE_DEMAND, FALSE);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_remove_attachments_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_remove_attachments (reader);
+}
+
+static void
+action_mail_remove_duplicates_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_remove_duplicates (reader);
+}
+
+static void
+action_mail_find_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_show_search_bar (reader);
+}
+
+static void
+action_mail_flag_clear_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GtkWindow *window;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+ window = e_mail_reader_get_window (reader);
+
+ em_utils_flag_for_followup_clear (window, folder, uids);
+
+ e_mail_reader_reload (reader);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_flag_completed_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GtkWindow *window;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+ window = e_mail_reader_get_window (reader);
+
+ em_utils_flag_for_followup_completed (window, folder, uids);
+
+ e_mail_reader_reload (reader);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_flag_for_followup_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ em_utils_flag_for_followup (reader, folder, uids);
+
+ e_mail_reader_reload (reader);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_forward_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWindow *window;
+ GPtrArray *uids;
+
+ window = e_mail_reader_get_window (reader);
+ uids = e_mail_reader_get_selected_uids (reader);
+ g_return_if_fail (uids != NULL);
+
+ if (em_utils_ask_open_many (window, uids->len)) {
+ CamelFolder *folder;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ e_mail_reader_forward_messages (
+ reader, folder, uids,
+ e_mail_reader_get_forward_style (reader));
+
+ g_clear_object (&folder);
+ }
+
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_forward_attached_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWindow *window;
+ GPtrArray *uids;
+
+ window = e_mail_reader_get_window (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+ g_return_if_fail (uids != NULL);
+
+ if (em_utils_ask_open_many (window, uids->len)) {
+ CamelFolder *folder;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ e_mail_reader_forward_messages (
+ reader, folder, uids,
+ E_MAIL_FORWARD_STYLE_ATTACHED);
+
+ g_clear_object (&folder);
+ }
+
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_forward_inline_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWindow *window;
+ GPtrArray *uids;
+
+ window = e_mail_reader_get_window (reader);
+ uids = e_mail_reader_get_selected_uids (reader);
+ g_return_if_fail (uids != NULL);
+
+ if (em_utils_ask_open_many (window, uids->len)) {
+ CamelFolder *folder;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ e_mail_reader_forward_messages (
+ reader, folder, uids,
+ E_MAIL_FORWARD_STYLE_INLINE);
+
+ g_clear_object (&folder);
+ }
+
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_forward_quoted_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWindow *window;
+ GPtrArray *uids;
+
+ window = e_mail_reader_get_window (reader);
+ uids = e_mail_reader_get_selected_uids (reader);
+ g_return_if_fail (uids != NULL);
+
+ if (em_utils_ask_open_many (window, uids->len)) {
+ CamelFolder *folder;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ e_mail_reader_forward_messages (
+ reader, folder, uids,
+ E_MAIL_FORWARD_STYLE_QUOTED);
+
+ g_clear_object (&folder);
+ }
+
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_label_new_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailLabelDialog *label_dialog;
+ EMailLabelListStore *label_store;
+ EMailBackend *backend;
+ EMailSession *session;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ GtkWidget *dialog;
+ GPtrArray *uids;
+ GdkColor label_color;
+ const gchar *label_name;
+ gchar *label_tag;
+ gint n_children;
+ guint ii;
+
+ dialog = e_mail_label_dialog_new (e_mail_reader_get_window (reader));
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Add Label"));
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_OK)
+ goto exit;
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+ label_store = e_mail_ui_session_get_label_store (
+ E_MAIL_UI_SESSION (session));
+
+ label_dialog = E_MAIL_LABEL_DIALOG (dialog);
+ label_name = e_mail_label_dialog_get_label_name (label_dialog);
+ e_mail_label_dialog_get_label_color (label_dialog, &label_color);
+
+ e_mail_label_list_store_set (
+ label_store, NULL, label_name, &label_color);
+
+ /* XXX This is awkward. We've added a new label to the list store
+ * but we don't have the new label's tag nor an iterator to use
+ * to fetch it. We know the label was appended to the store,
+ * so we have to dig it out manually. EMailLabelListStore API
+ * probably needs some rethinking. */
+ model = GTK_TREE_MODEL (label_store);
+ n_children = gtk_tree_model_iter_n_children (model, NULL);
+ g_warn_if_fail (gtk_tree_model_iter_nth_child (model, &iter, NULL, n_children - 1));
+ label_tag = e_mail_label_list_store_get_tag (label_store, &iter);
+
+ uids = e_mail_reader_get_selected_uids (reader);
+ if (uids) {
+ CamelFolder *folder;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], label_tag, TRUE);
+ }
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+ }
+
+ g_free (label_tag);
+
+ exit:
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_mail_label_none_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailBackend *backend;
+ EMailSession *session;
+ EMailLabelListStore *label_store;
+ CamelFolder *folder;
+ GtkTreeIter iter;
+ GPtrArray *uids;
+ gboolean valid;
+ guint ii;
+
+ uids = e_mail_reader_get_selected_uids (reader);
+ if (!uids)
+ return;
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+ label_store = e_mail_ui_session_get_label_store (
+ E_MAIL_UI_SESSION (session));
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ valid = gtk_tree_model_get_iter_first (
+ GTK_TREE_MODEL (label_store), &iter);
+
+ while (valid) {
+ gchar *tag;
+
+ tag = e_mail_label_list_store_get_tag (label_store, &iter);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], tag, FALSE);
+ camel_folder_set_message_user_tag (
+ folder, uids->pdata[ii], "label", NULL);
+ }
+
+ g_free (tag);
+
+ valid = gtk_tree_model_iter_next (
+ GTK_TREE_MODEL (label_store), &iter);
+ }
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_load_images_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ e_mail_display_load_images (display);
+}
+
+static void
+action_mail_mark_important_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask = CAMEL_MESSAGE_FLAGGED | CAMEL_MESSAGE_DELETED;
+ guint32 set = CAMEL_MESSAGE_FLAGGED;
+
+ e_mail_reader_mark_selected (reader, mask, set);
+}
+
+static void
+action_mail_mark_junk_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask =
+ CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_JUNK |
+ CAMEL_MESSAGE_NOTJUNK |
+ CAMEL_MESSAGE_JUNK_LEARN;
+ guint32 set =
+ CAMEL_MESSAGE_SEEN |
+ CAMEL_MESSAGE_JUNK |
+ CAMEL_MESSAGE_JUNK_LEARN;
+
+ if (e_mail_reader_mark_selected (reader, mask, set) != 0 &&
+ !e_mail_reader_close_on_delete_or_junk (reader)) {
+ if (e_mail_reader_get_delete_selects_previous (reader))
+ e_mail_reader_select_previous_message (reader, TRUE);
+ else
+ e_mail_reader_select_next_message (reader, TRUE);
+ }
+}
+
+static void
+action_mail_mark_notjunk_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask =
+ CAMEL_MESSAGE_JUNK |
+ CAMEL_MESSAGE_NOTJUNK |
+ CAMEL_MESSAGE_JUNK_LEARN;
+ guint32 set =
+ CAMEL_MESSAGE_NOTJUNK |
+ CAMEL_MESSAGE_JUNK_LEARN;
+
+ if (e_mail_reader_mark_selected (reader, mask, set) != 0) {
+ if (e_mail_reader_get_delete_selects_previous (reader))
+ e_mail_reader_select_previous_message (reader, TRUE);
+ else
+ e_mail_reader_select_next_message (reader, TRUE);
+ }
+}
+
+static void
+action_mail_mark_read_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask = CAMEL_MESSAGE_SEEN;
+ guint32 set = CAMEL_MESSAGE_SEEN;
+
+ e_mail_reader_mark_selected (reader, mask, set);
+}
+
+static void
+action_mail_mark_unimportant_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask = CAMEL_MESSAGE_FLAGGED;
+ guint32 set = 0;
+
+ e_mail_reader_mark_selected (reader, mask, set);
+}
+
+static void
+action_mail_mark_ignore_thread_sub_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_mark_selected_ignore_thread (reader, E_IGNORE_THREAD_SUBSET_SET);
+}
+
+static void
+action_mail_mark_unignore_thread_sub_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_mark_selected_ignore_thread (reader, E_IGNORE_THREAD_SUBSET_UNSET);
+}
+
+static void
+action_mail_mark_ignore_thread_whole_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_mark_selected_ignore_thread (reader, E_IGNORE_THREAD_WHOLE_SET);
+}
+
+static void
+action_mail_mark_unignore_thread_whole_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_mark_selected_ignore_thread (reader, E_IGNORE_THREAD_WHOLE_UNSET);
+}
+
+static void
+action_mail_mark_unread_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+ EMFolderTreeModel *model;
+ CamelFolder *folder;
+ guint32 mask = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_DELETED;
+ guint32 set = 0;
+ guint n_marked;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ n_marked = e_mail_reader_mark_selected (reader, mask, set);
+
+ if (MESSAGE_LIST (message_list)->seen_id != 0) {
+ g_source_remove (MESSAGE_LIST (message_list)->seen_id);
+ MESSAGE_LIST (message_list)->seen_id = 0;
+ }
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ /* Notify the tree model that the user has marked messages as
+ * unread so it doesn't mistake the event as new mail arriving. */
+ model = em_folder_tree_model_get_default ();
+ em_folder_tree_model_user_marked_unread (model, folder, n_marked);
+
+ g_clear_object (&folder);
+}
+
+static void
+action_mail_message_edit_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EShell *shell;
+ EMailBackend *backend;
+ ESourceRegistry *registry;
+ CamelFolder *folder;
+ GPtrArray *uids;
+ gboolean replace;
+
+ uids = e_mail_reader_get_selected_uids (reader);
+ g_return_if_fail (uids != NULL);
+
+ backend = e_mail_reader_get_backend (reader);
+ shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
+ registry = e_shell_get_registry (shell);
+
+ folder = e_mail_reader_ref_folder (reader);
+ replace = em_utils_folder_is_drafts (registry, folder);
+ e_mail_reader_edit_messages (reader, folder, uids, replace, replace);
+ g_clear_object (&folder);
+
+ g_ptr_array_unref (uids);
+}
+
+typedef struct _CreateComposerData {
+ EMailReader *reader;
+ CamelMimeMessage *message;
+ CamelFolder *folder;
+ const gchar *message_uid; /* In the Camel string pool */
+ gboolean is_redirect;
+} CreateComposerData;
+
+static void
+mail_reader_new_composer_created_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CreateComposerData *ccd = user_data;
+ EMsgComposer *composer;
+ GError *error = NULL;
+
+ g_return_if_fail (ccd != NULL);
+
+ composer = e_msg_composer_new_finish (result, &error);
+ if (error) {
+ g_warning ("%s: Failed to create msg composer: %s", G_STRFUNC, error->message);
+ g_clear_error (&error);
+ } else {
+ if (ccd->is_redirect)
+ em_utils_redirect_message (composer, ccd->message);
+ else
+ em_utils_compose_new_message_with_selection (composer, ccd->folder, ccd->message_uid);
+
+ e_mail_reader_composer_created (ccd->reader, composer, ccd->message);
+ }
+
+ g_clear_object (&ccd->reader);
+ g_clear_object (&ccd->message);
+ g_clear_object (&ccd->folder);
+ camel_pstring_free (ccd->message_uid);
+ g_slice_free (CreateComposerData, ccd);
+}
+
+static void
+action_mail_message_new_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EShell *shell;
+ EMailBackend *backend;
+ EShellBackend *shell_backend;
+ CamelFolder *folder;
+ CreateComposerData *ccd;
+ GPtrArray *selected_uids = NULL;
+ const gchar *selected_uid = NULL;
+
+ folder = e_mail_reader_ref_folder (reader);
+ backend = e_mail_reader_get_backend (reader);
+
+ selected_uids = e_mail_reader_get_selected_uids (reader);
+ if (selected_uids && selected_uids->len > 0)
+ selected_uid = g_ptr_array_index (selected_uids, 0);
+
+ if (!selected_uid) {
+ GtkWidget *message_list;
+
+ message_list = e_mail_reader_get_message_list (reader);
+ if (message_list)
+ selected_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ }
+
+ shell_backend = E_SHELL_BACKEND (backend);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ ccd = g_slice_new0 (CreateComposerData);
+ ccd->reader = g_object_ref (reader);
+ ccd->folder = folder;
+ ccd->message_uid = camel_pstring_strdup (selected_uid);
+ ccd->is_redirect = FALSE;
+
+ e_msg_composer_new (shell, mail_reader_new_composer_created_cb, ccd);
+
+ if (selected_uids)
+ g_ptr_array_unref (selected_uids);
+}
+
+static void
+action_mail_message_open_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_open_selected_mail (reader);
+}
+
+static void
+action_mail_archive_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ EMailBackend *backend;
+ EMailSession *session;
+ GPtrArray *uids;
+ gchar *archive_folder;
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+ g_return_if_fail (uids != NULL);
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ if (CAMEL_IS_VEE_FOLDER (folder) && uids->len > 1) {
+ GHashTable *split_by_folder; /* CamelFolder * ~> GPtrArray { gchar * uids } */
+ GHashTableIter iter;
+ gpointer key, value;
+ guint ii, n_successful = 0, n_failed = 0;
+
+ split_by_folder = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, (GDestroyNotify) g_ptr_array_unref);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ const gchar *uid = g_ptr_array_index (uids, ii);
+ CamelFolder *real_folder = NULL;
+ gchar *real_message_uid = NULL;
+
+ em_utils_get_real_folder_and_message_uid (folder, uid, &real_folder, NULL, &real_message_uid);
+
+ if (real_folder && real_message_uid) {
+ GPtrArray *array;
+
+ array = g_hash_table_lookup (split_by_folder, real_folder);
+
+ if (array) {
+ g_object_unref (real_folder);
+ } else {
+ array = g_ptr_array_new_with_free_func (g_free);
+ g_hash_table_insert (split_by_folder, real_folder, array);
+ }
+
+ g_ptr_array_add (array, real_message_uid);
+ } else {
+ g_clear_object (&real_folder);
+ g_free (real_message_uid);
+ }
+ }
+
+ g_hash_table_iter_init (&iter, split_by_folder);
+
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ CamelFolder *real_folder = key;
+ GPtrArray *real_uids = value;
+
+ archive_folder = em_utils_get_archive_folder_uri_from_folder (real_folder, backend, real_uids, TRUE);
+
+ if (archive_folder && *archive_folder) {
+ n_successful++;
+ mail_transfer_messages (
+ session, folder, uids,
+ TRUE, archive_folder, 0, NULL, NULL);
+ } else {
+ n_failed++;
+ }
+
+ g_free (archive_folder);
+ }
+
+ g_hash_table_destroy (split_by_folder);
+
+ if (n_failed && !n_successful) {
+ EAlertSink *alert_sink;
+
+ alert_sink = e_mail_reader_get_alert_sink (reader);
+
+ e_alert_submit (
+ alert_sink, "mail:no-archive-folder",
+ NULL);
+ }
+ } else {
+ archive_folder = em_utils_get_archive_folder_uri_from_folder (folder, backend, uids, TRUE);
+
+ if (archive_folder && *archive_folder) {
+ mail_transfer_messages (
+ session, folder, uids,
+ TRUE, archive_folder, 0, NULL, NULL);
+ } else {
+ EAlertSink *alert_sink;
+
+ alert_sink = e_mail_reader_get_alert_sink (reader);
+
+ e_alert_submit (
+ alert_sink, "mail:no-archive-folder",
+ NULL);
+ }
+
+ g_free (archive_folder);
+ }
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_move_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ mail_reader_copy_or_move_selected_messages (reader, TRUE);
+}
+
+static void
+action_mail_next_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+ MessageListSelectDirection direction;
+ guint32 flags, mask;
+
+ direction = MESSAGE_LIST_SELECT_NEXT;
+ flags = 0;
+ mask = 0;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select (
+ MESSAGE_LIST (message_list), direction, flags, mask);
+}
+
+static void
+action_mail_next_important_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+ MessageListSelectDirection direction;
+ guint32 flags, mask;
+
+ direction = MESSAGE_LIST_SELECT_NEXT | MESSAGE_LIST_SELECT_WRAP;
+ flags = CAMEL_MESSAGE_FLAGGED;
+ mask = CAMEL_MESSAGE_FLAGGED;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select (
+ MESSAGE_LIST (message_list), direction, flags, mask);
+}
+
+static void
+action_mail_next_thread_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_next_thread (MESSAGE_LIST (message_list));
+}
+
+static void
+mail_reader_select_unread (EMailReader *reader,
+ gboolean move_forward)
+{
+ GtkWidget *message_list;
+ MessageListSelectDirection direction;
+ guint32 flags, mask;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ direction = (move_forward ? MESSAGE_LIST_SELECT_NEXT : MESSAGE_LIST_SELECT_PREVIOUS) |
+ MESSAGE_LIST_SELECT_WRAP | MESSAGE_LIST_SELECT_INCLUDE_COLLAPSED;
+ flags = 0;
+ mask = CAMEL_MESSAGE_SEEN;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ if (!message_list_select (MESSAGE_LIST (message_list), direction, flags, mask)) {
+ GtkWindow *window;
+
+ window = e_mail_reader_get_window (reader);
+ if (E_IS_SHELL_WINDOW (window)) {
+ EShellWindow *shell_window;
+ EMFolderTree *folder_tree = NULL;
+ const gchar *active_view;
+
+ shell_window = E_SHELL_WINDOW (window);
+ active_view = e_shell_window_get_active_view (shell_window);
+
+ if (g_strcmp0 (active_view, "mail") == 0) {
+ EShellView *shell_view;
+ EShellSidebar *shell_sidebar;
+
+ shell_view = e_shell_window_get_shell_view (shell_window, "mail");
+ shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
+ g_object_get (shell_sidebar, "folder-tree", &folder_tree, NULL);
+
+ if (folder_tree) {
+ gboolean selected;
+
+ if (move_forward)
+ selected = em_folder_tree_select_next_path (folder_tree, TRUE);
+ else
+ selected = em_folder_tree_select_prev_path (folder_tree, TRUE);
+
+ if (selected)
+ message_list_set_regen_selects_unread (MESSAGE_LIST (message_list), TRUE);
+ }
+
+ g_clear_object (&folder_tree);
+ }
+ }
+ }
+}
+
+static void
+action_mail_next_unread_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ mail_reader_select_unread (reader, TRUE);
+}
+
+static void
+action_mail_previous_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+ MessageListSelectDirection direction;
+ guint32 flags, mask;
+
+ direction = MESSAGE_LIST_SELECT_PREVIOUS;
+ flags = 0;
+ mask = 0;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select (
+ MESSAGE_LIST (message_list), direction, flags, mask);
+}
+
+static void
+action_mail_previous_important_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+ MessageListSelectDirection direction;
+ guint32 flags, mask;
+
+ direction = MESSAGE_LIST_SELECT_PREVIOUS | MESSAGE_LIST_SELECT_WRAP;
+ flags = CAMEL_MESSAGE_FLAGGED;
+ mask = CAMEL_MESSAGE_FLAGGED;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select (
+ MESSAGE_LIST (message_list), direction, flags, mask);
+}
+
+static void
+action_mail_previous_thread_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkWidget *message_list;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_prev_thread (MESSAGE_LIST (message_list));
+}
+
+static void
+action_mail_previous_unread_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ mail_reader_select_unread (reader, FALSE);
+}
+
+static void
+action_mail_print_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkPrintOperationAction print_action;
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
+ e_mail_reader_print (reader, print_action);
+}
+
+static void
+action_mail_print_preview_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GtkPrintOperationAction print_action;
+
+ print_action = GTK_PRINT_OPERATION_ACTION_PREVIEW;
+ e_mail_reader_print (reader, print_action);
+}
+
+static void
+mail_reader_redirect_cb (CamelFolder *folder,
+ GAsyncResult *result,
+ EMailReaderClosure *closure)
+{
+ EShell *shell;
+ EMailBackend *backend;
+ EAlertSink *alert_sink;
+ CamelMimeMessage *message;
+ CreateComposerData *ccd;
+ GError *error = NULL;
+
+ alert_sink = e_activity_get_alert_sink (closure->activity);
+
+ message = camel_folder_get_message_finish (folder, result, &error);
+
+ if (e_activity_handle_cancellation (closure->activity, error)) {
+ g_warn_if_fail (message == NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (error);
+ return;
+
+ } else if (error != NULL) {
+ g_warn_if_fail (message == NULL);
+ e_alert_submit (
+ alert_sink, "mail:no-retrieve-message",
+ error->message, NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ backend = e_mail_reader_get_backend (closure->reader);
+ shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
+
+ ccd = g_slice_new0 (CreateComposerData);
+ ccd->reader = g_object_ref (closure->reader);
+ ccd->message = message;
+ ccd->message_uid = camel_pstring_strdup (closure->message_uid);
+ ccd->is_redirect = TRUE;
+
+ e_msg_composer_new (shell, mail_reader_new_composer_created_cb, ccd);
+
+ mail_reader_closure_free (closure);
+}
+
+static void
+action_mail_redirect_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EActivity *activity;
+ GCancellable *cancellable;
+ EMailReaderClosure *closure;
+ GtkWidget *message_list;
+ CamelFolder *folder;
+ const gchar *message_uid;
+
+ message_list = e_mail_reader_get_message_list (reader);
+ message_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ g_return_if_fail (message_uid != NULL);
+
+ /* Open the message asynchronously. */
+
+ activity = e_mail_reader_new_activity (reader);
+ cancellable = e_activity_get_cancellable (activity);
+
+ closure = g_slice_new0 (EMailReaderClosure);
+ closure->activity = activity;
+ closure->reader = g_object_ref (reader);
+ closure->message_uid = g_strdup (message_uid);
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ camel_folder_get_message (
+ folder, message_uid, G_PRIORITY_DEFAULT,
+ cancellable, (GAsyncReadyCallback)
+ mail_reader_redirect_cb, closure);
+
+ g_clear_object (&folder);
+}
+
+static void
+action_mail_reply_all_check (CamelFolder *folder,
+ GAsyncResult *result,
+ EMailReaderClosure *closure)
+{
+ EAlertSink *alert_sink;
+ CamelMimeMessage *message;
+ CamelInternetAddress *to, *cc;
+ gint recip_count = 0;
+ EMailReplyType type = E_MAIL_REPLY_TO_ALL;
+ GError *error = NULL;
+
+ alert_sink = e_activity_get_alert_sink (closure->activity);
+
+ message = camel_folder_get_message_finish (folder, result, &error);
+
+ if (e_activity_handle_cancellation (closure->activity, error)) {
+ g_warn_if_fail (message == NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (error);
+ return;
+
+ } else if (error != NULL) {
+ g_warn_if_fail (message == NULL);
+ e_alert_submit (
+ alert_sink, "mail:no-retrieve-message",
+ error->message, NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ to = camel_mime_message_get_recipients (
+ message, CAMEL_RECIPIENT_TYPE_TO);
+ cc = camel_mime_message_get_recipients (
+ message, CAMEL_RECIPIENT_TYPE_CC);
+
+ recip_count = camel_address_length (CAMEL_ADDRESS (to));
+ recip_count += camel_address_length (CAMEL_ADDRESS (cc));
+
+ if (recip_count >= 15) {
+ GtkWidget *dialog;
+ GtkWidget *check;
+ GtkWidget *container;
+ gint response;
+
+ dialog = e_alert_dialog_new_for_args (
+ e_mail_reader_get_window (closure->reader),
+ "mail:ask-reply-many-recips", NULL);
+
+ container = e_alert_dialog_get_content_area (
+ E_ALERT_DIALOG (dialog));
+
+ /* Check buttons */
+ check = gtk_check_button_new_with_mnemonic (
+ _("_Do not ask me again."));
+ gtk_box_pack_start (
+ GTK_BOX (container), check, FALSE, FALSE, 0);
+ gtk_widget_show (check);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check))) {
+ GSettings *settings;
+ const gchar *key;
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ key = "prompt-on-reply-many-recips";
+ g_settings_set_boolean (settings, key, FALSE);
+
+ g_object_unref (settings);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ switch (response) {
+ case GTK_RESPONSE_NO:
+ type = E_MAIL_REPLY_TO_SENDER;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_DELETE_EVENT:
+ goto exit;
+ default:
+ break;
+ }
+ }
+
+ e_mail_reader_reply_to_message (closure->reader, message, type);
+
+exit:
+ g_object_unref (message);
+
+ mail_reader_closure_free (closure);
+}
+
+static void
+action_mail_reply_all_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GSettings *settings;
+ const gchar *key;
+ guint32 state;
+ gboolean ask;
+
+ state = e_mail_reader_check_state (reader);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ key = "prompt-on-reply-many-recips";
+ ask = g_settings_get_boolean (settings, key);
+
+ g_object_unref (settings);
+
+ if (ask && !(state & E_MAIL_READER_SELECTION_IS_MAILING_LIST)) {
+ EActivity *activity;
+ GCancellable *cancellable;
+ EMailReaderClosure *closure;
+ CamelFolder *folder;
+ GtkWidget *message_list;
+ const gchar *message_uid;
+
+ message_list = e_mail_reader_get_message_list (reader);
+ message_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ g_return_if_fail (message_uid != NULL);
+
+ activity = e_mail_reader_new_activity (reader);
+ cancellable = e_activity_get_cancellable (activity);
+
+ closure = g_slice_new0 (EMailReaderClosure);
+ closure->activity = activity;
+ closure->reader = g_object_ref (reader);
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ camel_folder_get_message (
+ folder, message_uid, G_PRIORITY_DEFAULT,
+ cancellable, (GAsyncReadyCallback)
+ action_mail_reply_all_check, closure);
+
+ g_clear_object (&folder);
+
+ return;
+ }
+
+ e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_ALL);
+}
+
+static void
+action_mail_reply_alternative_got_message (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EMailReaderClosure *closure = user_data;
+ EAlertSink *alert_sink;
+ CamelMimeMessage *message;
+ gboolean is_selection;
+ CamelFolder *folder = NULL;
+ const gchar *message_uid = NULL;
+ EMailPartList *part_list = NULL;
+ EMailPartValidityFlags validity_pgp_sum = 0;
+ EMailPartValidityFlags validity_smime_sum = 0;
+ GError *error = NULL;
+
+ alert_sink = e_activity_get_alert_sink (closure->activity);
+
+ message = e_mail_reader_utils_get_selection_or_message_finish (E_MAIL_READER (source_object), result,
+ &is_selection, &folder, &message_uid, &part_list, &validity_pgp_sum, &validity_smime_sum, &error);
+
+ if (e_activity_handle_cancellation (closure->activity, error)) {
+ g_warn_if_fail (message == NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (error);
+ return;
+
+ } else if (error != NULL) {
+ g_warn_if_fail (message == NULL);
+ e_alert_submit (
+ alert_sink, "mail:no-retrieve-message",
+ error->message, NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (error);
+ return;
+ }
+
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ g_clear_object (&closure->activity);
+
+ em_utils_reply_alternative (e_mail_reader_get_window (closure->reader),
+ e_shell_backend_get_shell (E_SHELL_BACKEND (e_mail_reader_get_backend (closure->reader))),
+ alert_sink, message, folder, message_uid,
+ e_mail_reader_get_reply_style (closure->reader),
+ is_selection ? NULL : part_list, validity_pgp_sum, validity_smime_sum);
+
+ mail_reader_closure_free (closure);
+ camel_pstring_free (message_uid);
+ g_clear_object (&message);
+ g_clear_object (&folder);
+ g_clear_object (&part_list);
+ g_clear_error (&error);
+}
+
+static void
+action_mail_reply_alternative_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EActivity *activity;
+ GCancellable *cancellable;
+ EMailReaderClosure *closure;
+ GtkWidget *message_list;
+ const gchar *message_uid;
+
+ message_list = e_mail_reader_get_message_list (reader);
+ message_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ g_return_if_fail (message_uid != NULL);
+
+ activity = e_mail_reader_new_activity (reader);
+ cancellable = e_activity_get_cancellable (activity);
+
+ closure = g_slice_new0 (EMailReaderClosure);
+ closure->activity = activity;
+ closure->reader = g_object_ref (reader);
+
+ e_mail_reader_utils_get_selection_or_message (reader, NULL, cancellable,
+ action_mail_reply_alternative_got_message, closure);
+}
+
+static void
+action_mail_reply_group_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GSettings *settings;
+ gboolean reply_list;
+ guint32 state;
+ const gchar *key;
+
+ state = e_mail_reader_check_state (reader);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ key = "composer-group-reply-to-list";
+ reply_list = g_settings_get_boolean (settings, key);
+
+ g_object_unref (settings);
+
+ if (reply_list && (state & E_MAIL_READER_SELECTION_IS_MAILING_LIST)) {
+ e_mail_reader_reply_to_message (
+ reader, NULL, E_MAIL_REPLY_TO_LIST);
+ } else
+ action_mail_reply_all_cb (action, reader);
+}
+
+static void
+action_mail_reply_list_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_LIST);
+}
+
+static gboolean
+message_is_list_administrative (CamelMimeMessage *message)
+{
+ const gchar *header;
+
+ header = camel_medium_get_header (
+ CAMEL_MEDIUM (message), "X-List-Administrivia");
+ if (header == NULL)
+ return FALSE;
+
+ while (*header == ' ' || *header == '\t')
+ header++;
+
+ return g_ascii_strncasecmp (header, "yes", 3) == 0;
+}
+
+static void
+action_mail_reply_sender_check (CamelFolder *folder,
+ GAsyncResult *result,
+ EMailReaderClosure *closure)
+{
+ EAlertSink *alert_sink;
+ CamelMimeMessage *message;
+ EMailReplyType type = E_MAIL_REPLY_TO_SENDER;
+ GSettings *settings;
+ gboolean ask_ignore_list_reply_to;
+ gboolean ask_list_reply_to;
+ gboolean munged_list_message;
+ gboolean active;
+ const gchar *key;
+ GError *local_error = NULL;
+
+ alert_sink = e_activity_get_alert_sink (closure->activity);
+
+ message = camel_folder_get_message_finish (
+ folder, result, &local_error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((message != NULL) && (local_error == NULL)) ||
+ ((message == NULL) && (local_error != NULL)));
+
+ if (e_activity_handle_cancellation (closure->activity, local_error)) {
+ mail_reader_closure_free (closure);
+ g_error_free (local_error);
+ return;
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ alert_sink, "mail:no-retrieve-message",
+ local_error->message, NULL);
+ mail_reader_closure_free (closure);
+ g_error_free (local_error);
+ return;
+ }
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ key = "composer-ignore-list-reply-to";
+ ask_ignore_list_reply_to = g_settings_get_boolean (settings, key);
+
+ key = "prompt-on-list-reply-to";
+ ask_list_reply_to = g_settings_get_boolean (settings, key);
+
+ munged_list_message = em_utils_is_munged_list_message (message);
+
+ if (message_is_list_administrative (message)) {
+ /* Do not ask for messages which are list administrative,
+ * like list confirmation messages. */
+ } else if (ask_ignore_list_reply_to || !munged_list_message) {
+ /* Don't do the "Are you sure you want to reply in private?"
+ * pop-up if it's a Reply-To: munged list message... unless
+ * we're ignoring munging. */
+ GtkWidget *dialog;
+ GtkWidget *check;
+ GtkWidget *container;
+ gint response;
+
+ dialog = e_alert_dialog_new_for_args (
+ e_mail_reader_get_window (closure->reader),
+ "mail:ask-list-private-reply", NULL);
+
+ container = e_alert_dialog_get_content_area (
+ E_ALERT_DIALOG (dialog));
+
+ /* Check buttons */
+ check = gtk_check_button_new_with_mnemonic (
+ _("_Do not ask me again."));
+ gtk_box_pack_start (
+ GTK_BOX (container), check, FALSE, FALSE, 0);
+ gtk_widget_show (check);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ active = gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (check));
+ if (active) {
+ key = "prompt-on-private-list-reply";
+ g_settings_set_boolean (settings, key, FALSE);
+ }
+
+ gtk_widget_destroy (dialog);
+
+ if (response == GTK_RESPONSE_YES)
+ type = E_MAIL_REPLY_TO_ALL;
+ else if (response == GTK_RESPONSE_OK)
+ type = E_MAIL_REPLY_TO_LIST;
+ else if (response == GTK_RESPONSE_CANCEL ||
+ response == GTK_RESPONSE_DELETE_EVENT) {
+ goto exit;
+ }
+
+ } else if (ask_list_reply_to) {
+ GtkWidget *dialog;
+ GtkWidget *container;
+ GtkWidget *check_again;
+ GtkWidget *check_always_ignore;
+ gint response;
+
+ dialog = e_alert_dialog_new_for_args (
+ e_mail_reader_get_window (closure->reader),
+ "mail:ask-list-honour-reply-to", NULL);
+
+ container = e_alert_dialog_get_content_area (
+ E_ALERT_DIALOG (dialog));
+
+ check_again = gtk_check_button_new_with_mnemonic (
+ _("_Do not ask me again."));
+ gtk_box_pack_start (
+ GTK_BOX (container), check_again, FALSE, FALSE, 0);
+ gtk_widget_show (check_again);
+
+ check_always_ignore = gtk_check_button_new_with_mnemonic (
+ _("_Always ignore Reply-To: for mailing lists."));
+ gtk_box_pack_start (
+ GTK_BOX (container), check_always_ignore,
+ FALSE, FALSE, 0);
+ gtk_widget_show (check_always_ignore);
+
+ response = gtk_dialog_run (GTK_DIALOG (dialog));
+
+ active = gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (check_again));
+ if (active) {
+ key = "prompt-on-list-reply-to";
+ g_settings_set_boolean (settings, key, FALSE);
+ }
+
+ key = "composer-ignore-list-reply-to";
+ active = gtk_toggle_button_get_active (
+ GTK_TOGGLE_BUTTON (check_always_ignore));
+ g_settings_set_boolean (settings, key, active);
+
+ gtk_widget_destroy (dialog);
+
+ switch (response) {
+ case GTK_RESPONSE_NO:
+ type = E_MAIL_REPLY_TO_FROM;
+ break;
+ case GTK_RESPONSE_OK:
+ type = E_MAIL_REPLY_TO_LIST;
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_DELETE_EVENT:
+ goto exit;
+ default:
+ break;
+ }
+ }
+
+ e_mail_reader_reply_to_message (closure->reader, message, type);
+
+exit:
+ g_object_unref (settings);
+ g_object_unref (message);
+
+ mail_reader_closure_free (closure);
+}
+
+static void
+action_mail_reply_sender_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ GSettings *settings;
+ gboolean ask_list_reply_to;
+ gboolean ask_private_list_reply;
+ gboolean ask;
+ guint32 state;
+ const gchar *key;
+
+ state = e_mail_reader_check_state (reader);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ key = "prompt-on-list-reply-to";
+ ask_list_reply_to = g_settings_get_boolean (settings, key);
+
+ key = "prompt-on-private-list-reply";
+ ask_private_list_reply = g_settings_get_boolean (settings, key);
+
+ g_object_unref (settings);
+
+ ask = (ask_private_list_reply || ask_list_reply_to);
+
+ if (ask && (state & E_MAIL_READER_SELECTION_IS_MAILING_LIST)) {
+ EActivity *activity;
+ GCancellable *cancellable;
+ EMailReaderClosure *closure;
+ CamelFolder *folder;
+ GtkWidget *message_list;
+ const gchar *message_uid;
+
+ message_list = e_mail_reader_get_message_list (reader);
+ message_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ g_return_if_fail (message_uid != NULL);
+
+ activity = e_mail_reader_new_activity (reader);
+ cancellable = e_activity_get_cancellable (activity);
+
+ closure = g_slice_new0 (EMailReaderClosure);
+ closure->activity = activity;
+ closure->reader = g_object_ref (reader);
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ camel_folder_get_message (
+ folder, message_uid, G_PRIORITY_DEFAULT,
+ cancellable, (GAsyncReadyCallback)
+ action_mail_reply_sender_check, closure);
+
+ g_clear_object (&folder);
+
+ return;
+ }
+
+ e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_SENDER);
+}
+
+static void
+action_mail_reply_recipient_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_reply_to_message (reader, NULL, E_MAIL_REPLY_TO_RECIPIENT);
+}
+
+static void
+action_mail_save_as_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_save_messages (reader);
+}
+
+static void
+action_mail_search_folder_from_mailing_list_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_vfolder_from_selected (reader, AUTO_MLIST);
+}
+
+static void
+action_mail_search_folder_from_recipients_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_vfolder_from_selected (reader, AUTO_TO);
+}
+
+static void
+action_mail_search_folder_from_sender_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_vfolder_from_selected (reader, AUTO_FROM);
+}
+
+static void
+action_mail_search_folder_from_subject_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ e_mail_reader_create_vfolder_from_selected (reader, AUTO_SUBJECT);
+}
+
+static void
+action_mail_show_all_headers_cb (GtkToggleAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+ EMailFormatterMode mode;
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ /* Ignore action when viewing message source. */
+ mode = e_mail_display_get_mode (display);
+ if (mode == E_MAIL_FORMATTER_MODE_SOURCE)
+ return;
+ if (mode == E_MAIL_FORMATTER_MODE_RAW)
+ return;
+
+ if (gtk_toggle_action_get_active (action))
+ mode = E_MAIL_FORMATTER_MODE_ALL_HEADERS;
+ else
+ mode = E_MAIL_FORMATTER_MODE_NORMAL;
+
+ e_mail_display_set_mode (display, mode);
+}
+
+static void
+mail_source_retrieved (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EMailReaderClosure *closure;
+ CamelMimeMessage *message;
+ EMailDisplay *display;
+ GError *error = NULL;
+
+ closure = (EMailReaderClosure *) user_data;
+ display = e_mail_reader_get_mail_display (closure->reader);
+
+ message = camel_folder_get_message_finish (
+ CAMEL_FOLDER (source_object), result, &error);
+
+ /* Sanity check. */
+ g_return_if_fail (
+ ((message != NULL) && (error == NULL)) ||
+ ((message == NULL) && (error != NULL)));
+
+ if (message != NULL) {
+ mail_reader_set_display_formatter_for_message (
+ closure->reader, display,
+ closure->message_uid, message,
+ CAMEL_FOLDER (source_object));
+ g_object_unref (message);
+ } else if (error) {
+ if (display) {
+ gchar *status;
+
+ status = g_strdup_printf (
+ "%s<br>%s",
+ _("Failed to retrieve message:"),
+ error->message);
+ e_mail_display_set_status (display, status);
+ g_free (status);
+ }
+
+ g_error_free (error);
+ }
+
+ e_activity_set_state (closure->activity, E_ACTIVITY_COMPLETED);
+
+ mail_reader_closure_free (closure);
+}
+
+static void
+action_mail_show_source_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+ EMailBackend *backend;
+ GtkWidget *browser;
+ CamelFolder *folder;
+ GPtrArray *uids;
+ const gchar *message_uid;
+ gchar *string;
+ EActivity *activity;
+ GCancellable *cancellable;
+ EMailReaderClosure *closure;
+ MessageList *ml;
+
+ backend = e_mail_reader_get_backend (reader);
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids (reader);
+ g_return_if_fail (uids != NULL && uids->len == 1);
+ message_uid = g_ptr_array_index (uids, 0);
+
+ if (!E_IS_MAIL_BROWSER (e_mail_reader_get_window (reader))) {
+ EMailBrowser *mail_browser;
+
+ mail_browser = em_utils_find_message_window (E_MAIL_FORMATTER_MODE_SOURCE, folder, message_uid);
+
+ if (mail_browser) {
+ gtk_window_present (GTK_WINDOW (mail_browser));
+ g_ptr_array_unref (uids);
+ g_clear_object (&folder);
+ return;
+ }
+ }
+
+ browser = e_mail_browser_new (backend, E_MAIL_FORMATTER_MODE_SOURCE);
+ ml = MESSAGE_LIST (e_mail_reader_get_message_list (E_MAIL_READER (browser)));
+
+ message_list_freeze (ml);
+ e_mail_reader_set_folder (E_MAIL_READER (browser), folder);
+ e_mail_reader_set_message (E_MAIL_READER (browser), message_uid);
+ message_list_thaw (ml);
+
+ display = e_mail_reader_get_mail_display (E_MAIL_READER (browser));
+
+ string = g_strdup_printf (_("Retrieving message “%s”"), message_uid);
+ e_mail_display_set_part_list (display, NULL);
+ e_mail_display_set_status (display, string);
+ gtk_widget_show (browser);
+
+ activity = e_mail_reader_new_activity (E_MAIL_READER (browser));
+ e_activity_set_text (activity, string);
+ cancellable = e_activity_get_cancellable (activity);
+ g_free (string);
+
+ closure = g_slice_new0 (EMailReaderClosure);
+ closure->reader = E_MAIL_READER (g_object_ref (browser));
+ closure->activity = g_object_ref (activity);
+ closure->message_uid = g_strdup (message_uid);
+
+ camel_folder_get_message (
+ folder, message_uid, G_PRIORITY_DEFAULT,
+ cancellable, mail_source_retrieved, closure);
+
+ g_object_unref (activity);
+
+ g_ptr_array_unref (uids);
+
+ g_clear_object (&folder);
+}
+
+static void
+action_mail_toggle_important_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+ guint ii;
+
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+ if (!uids)
+ return;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ camel_folder_freeze (folder);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ guint32 flags;
+
+ flags = camel_folder_get_message_flags (
+ folder, uids->pdata[ii]);
+ flags ^= CAMEL_MESSAGE_FLAGGED;
+ if (flags & CAMEL_MESSAGE_FLAGGED)
+ flags &= ~CAMEL_MESSAGE_DELETED;
+
+ camel_folder_set_message_flags (
+ folder, uids->pdata[ii], CAMEL_MESSAGE_FLAGGED |
+ CAMEL_MESSAGE_DELETED, flags);
+ }
+
+ camel_folder_thaw (folder);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+static void
+action_mail_undelete_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ guint32 mask = CAMEL_MESSAGE_DELETED;
+ guint32 set = 0;
+
+ e_mail_reader_mark_selected (reader, mask, set);
+}
+
+static void
+action_mail_zoom_100_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ e_web_view_zoom_100 (E_WEB_VIEW (display));
+}
+
+static void
+action_mail_zoom_in_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ e_web_view_zoom_in (E_WEB_VIEW (display));
+}
+
+static void
+action_mail_zoom_out_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ e_web_view_zoom_out (E_WEB_VIEW (display));
+}
+
+static void
+action_mail_search_web_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailDisplay *display;
+ GtkAction *wv_action;
+
+ display = e_mail_reader_get_mail_display (reader);
+ wv_action = e_web_view_get_action (E_WEB_VIEW (display), "search-web");
+
+ gtk_action_activate (wv_action);
+}
+
+static void
+action_search_folder_recipient_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailBackend *backend;
+ EMailSession *session;
+ EWebView *web_view;
+ CamelURL *curl;
+ const gchar *uri;
+
+ /* This action is defined in EMailDisplay. */
+
+ web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader));
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ curl = camel_url_new (uri, NULL);
+ g_return_if_fail (curl != NULL);
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ if (curl->path != NULL && *curl->path != '\0') {
+ CamelFolder *folder;
+ CamelInternetAddress *inet_addr;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ inet_addr = camel_internet_address_new ();
+ camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
+ vfolder_gui_add_from_address (
+ session, inet_addr, AUTO_TO, folder);
+ g_object_unref (inet_addr);
+
+ g_clear_object (&folder);
+ }
+
+ camel_url_free (curl);
+}
+
+static void
+action_search_folder_sender_cb (GtkAction *action,
+ EMailReader *reader)
+{
+ EMailBackend *backend;
+ EMailSession *session;
+ EWebView *web_view;
+ CamelURL *curl;
+ const gchar *uri;
+
+ /* This action is defined in EMailDisplay. */
+
+ web_view = E_WEB_VIEW (e_mail_reader_get_mail_display (reader));
+
+ uri = e_web_view_get_selected_uri (web_view);
+ g_return_if_fail (uri != NULL);
+
+ curl = camel_url_new (uri, NULL);
+ g_return_if_fail (curl != NULL);
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ if (curl->path != NULL && *curl->path != '\0') {
+ CamelFolder *folder;
+ CamelInternetAddress *inet_addr;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ inet_addr = camel_internet_address_new ();
+ camel_address_decode (CAMEL_ADDRESS (inet_addr), curl->path);
+ vfolder_gui_add_from_address (
+ session, inet_addr, AUTO_FROM, folder);
+ g_object_unref (inet_addr);
+
+ g_clear_object (&folder);
+ }
+
+ camel_url_free (curl);
+}
+
+static GtkActionEntry mail_reader_entries[] = {
+
+ { "mail-add-sender",
+ NULL,
+ N_("A_dd Sender to Address Book"),
+ NULL,
+ N_("Add sender to address book"),
+ G_CALLBACK (action_mail_add_sender_cb) },
+
+ { "mail-archive",
+ "mail-archive",
+ N_("_Archive…"),
+ "<Alt><Control>a",
+ N_("Move selected messages to the Archive folder for the account"),
+ G_CALLBACK (action_mail_archive_cb) },
+
+ { "mail-check-for-junk",
+ "mail-mark-junk",
+ N_("Check for _Junk"),
+ "<Control><Alt>j",
+ N_("Filter the selected messages for junk status"),
+ G_CALLBACK (action_mail_check_for_junk_cb) },
+
+ { "mail-color-assign",
+ NULL,
+ N_("Assign C_olor…"),
+ NULL,
+ N_("Assign color for the selected messages"),
+ G_CALLBACK (action_mail_color_assign_cb) },
+
+ { "mail-color-unset",
+ NULL,
+ N_("Unse_t Color"),
+ NULL,
+ N_("Unset color for the selected messages"),
+ G_CALLBACK (action_mail_color_unset_cb) },
+
+ { "mail-copy",
+ "mail-copy",
+ N_("_Copy to Folder…"),
+ "<Shift><Control>y",
+ N_("Copy selected messages to another folder"),
+ G_CALLBACK (action_mail_copy_cb) },
+
+ { "mail-delete",
+ "user-trash",
+ N_("_Delete Message"),
+ "<Control>d",
+ N_("Mark the selected messages for deletion"),
+ G_CALLBACK (action_mail_delete_cb) },
+
+ { "mail-add-note",
+ "evolution-memos",
+ N_("_Add note…"),
+ NULL,
+ N_("Add a note for the selected message"),
+ G_CALLBACK (action_mail_edit_note_cb) },
+
+ { "mail-delete-note",
+ NULL,
+ N_("Delete no_te"),
+ NULL,
+ N_("Delete the note for the selected message"),
+ G_CALLBACK (action_mail_delete_note_cb) },
+
+ { "mail-edit-note",
+ "evolution-memos",
+ N_("_Edit note…"),
+ NULL,
+ N_("Edit a note for the selected message"),
+ G_CALLBACK (action_mail_edit_note_cb) },
+
+ { "mail-filter-rule-for-mailing-list",
+ NULL,
+ N_("Create a Filter Rule for Mailing _List…"),
+ NULL,
+ N_("Create a rule to filter messages to this mailing list"),
+ G_CALLBACK (action_mail_filter_on_mailing_list_cb) },
+
+ { "mail-filter-rule-for-recipients",
+ NULL,
+ N_("Create a Filter Rule for _Recipients…"),
+ NULL,
+ N_("Create a rule to filter messages to these recipients"),
+ G_CALLBACK (action_mail_filter_on_recipients_cb) },
+
+ { "mail-filter-rule-for-sender",
+ NULL,
+ N_("Create a Filter Rule for Se_nder…"),
+ NULL,
+ N_("Create a rule to filter messages from this sender"),
+ G_CALLBACK (action_mail_filter_on_sender_cb) },
+
+ { "mail-filter-rule-for-subject",
+ NULL,
+ N_("Create a Filter Rule for _Subject…"),
+ NULL,
+ N_("Create a rule to filter messages with this subject"),
+ G_CALLBACK (action_mail_filter_on_subject_cb) },
+
+ { "mail-filters-apply",
+ "stock_mail-filters-apply",
+ N_("A_pply Filters"),
+ "<Control>y",
+ N_("Apply filter rules to the selected messages"),
+ G_CALLBACK (action_mail_filters_apply_cb) },
+
+ { "mail-find",
+ "edit-find",
+ N_("_Find in Message…"),
+ "<Shift><Control>f",
+ N_("Search for text in the body of the displayed message"),
+ G_CALLBACK (action_mail_find_cb) },
+
+ { "mail-flag-clear",
+ NULL,
+ N_("_Clear Flag"),
+ NULL,
+ N_("Remove the follow-up flag from the selected messages"),
+ G_CALLBACK (action_mail_flag_clear_cb) },
+
+ { "mail-flag-completed",
+ NULL,
+ N_("_Flag Completed"),
+ NULL,
+ N_("Set the follow-up flag to completed on the selected messages"),
+ G_CALLBACK (action_mail_flag_completed_cb) },
+
+ { "mail-flag-for-followup",
+ "stock_mail-flag-for-followup",
+ N_("Follow _Up…"),
+ "<Shift><Control>g",
+ N_("Flag the selected messages for follow-up"),
+ G_CALLBACK (action_mail_flag_for_followup_cb) },
+
+ { "mail-forward",
+ "mail-forward",
+ N_("_Forward"),
+ "<Control>f",
+ N_("Forward the selected message to someone"),
+ G_CALLBACK (action_mail_forward_cb) },
+
+ { "mail-forward-attached",
+ NULL,
+ N_("_Attached"),
+ NULL,
+ N_("Forward the selected message to someone as an attachment"),
+ G_CALLBACK (action_mail_forward_attached_cb) },
+
+ { "mail-forward-attached-full",
+ NULL,
+ N_("Forward As _Attached"),
+ NULL,
+ N_("Forward the selected message to someone as an attachment"),
+ G_CALLBACK (action_mail_forward_attached_cb) },
+
+ { "mail-forward-inline",
+ NULL,
+ N_("_Inline"),
+ NULL,
+ N_("Forward the selected message in the body of a new message"),
+ G_CALLBACK (action_mail_forward_inline_cb) },
+
+ { "mail-forward-inline-full",
+ NULL,
+ N_("Forward As _Inline"),
+ NULL,
+ N_("Forward the selected message in the body of a new message"),
+ G_CALLBACK (action_mail_forward_inline_cb) },
+
+ { "mail-forward-quoted",
+ NULL,
+ N_("_Quoted"),
+ NULL,
+ N_("Forward the selected message quoted like a reply"),
+ G_CALLBACK (action_mail_forward_quoted_cb) },
+
+ { "mail-forward-quoted-full",
+ NULL,
+ N_("Forward As _Quoted"),
+ NULL,
+ N_("Forward the selected message quoted like a reply"),
+ G_CALLBACK (action_mail_forward_quoted_cb) },
+
+ { "mail-label-new",
+ NULL,
+ N_("_New Label"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_label_new_cb) },
+
+ { "mail-label-none",
+ NULL,
+ /* Translators: "None" is used in the message label context menu.
+ * It removes all labels from the selected messages. */
+ N_("N_one"),
+ "0",
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_label_none_cb) },
+
+ { "mail-load-images",
+ "image-x-generic",
+ N_("_Load Images"),
+ "<Control>i",
+ N_("Force images in HTML mail to be loaded"),
+ G_CALLBACK (action_mail_load_images_cb) },
+
+ { "mail-mark-ignore-thread-sub",
+ NULL,
+ N_("_Ignore Subthread"),
+ NULL,
+ N_("Mark new mails in a subthread as read automatically"),
+ G_CALLBACK (action_mail_mark_ignore_thread_sub_cb) },
+
+ { "mail-mark-ignore-thread-whole",
+ NULL,
+ N_("_Ignore Thread"),
+ NULL,
+ N_("Mark new mails in this thread as read automatically"),
+ G_CALLBACK (action_mail_mark_ignore_thread_whole_cb) },
+
+ { "mail-mark-important",
+ "mail-mark-important",
+ N_("_Important"),
+ NULL,
+ N_("Mark the selected messages as important"),
+ G_CALLBACK (action_mail_mark_important_cb) },
+
+ { "mail-mark-junk",
+ "mail-mark-junk",
+ N_("_Junk"),
+ "<Control>j",
+ N_("Mark the selected messages as junk"),
+ G_CALLBACK (action_mail_mark_junk_cb) },
+
+ { "mail-mark-notjunk",
+ "mail-mark-notjunk",
+ N_("_Not Junk"),
+ "<Shift><Control>j",
+ N_("Mark the selected messages as not being junk"),
+ G_CALLBACK (action_mail_mark_notjunk_cb) },
+
+ { "mail-mark-read",
+ "mail-mark-read",
+ N_("_Read"),
+ "<Control>k",
+ N_("Mark the selected messages as having been read"),
+ G_CALLBACK (action_mail_mark_read_cb) },
+
+ { "mail-mark-unignore-thread-sub",
+ NULL,
+ N_("Do not _Ignore Subthread"),
+ NULL,
+ N_("Do not mark new mails in a subthread as read automatically"),
+ G_CALLBACK (action_mail_mark_unignore_thread_sub_cb) },
+
+ { "mail-mark-unignore-thread-whole",
+ NULL,
+ N_("Do not _Ignore Thread"),
+ NULL,
+ N_("Do not mark new mails in this thread as read automatically"),
+ G_CALLBACK (action_mail_mark_unignore_thread_whole_cb) },
+
+ { "mail-mark-unimportant",
+ NULL,
+ N_("Uni_mportant"),
+ NULL,
+ N_("Mark the selected messages as unimportant"),
+ G_CALLBACK (action_mail_mark_unimportant_cb) },
+
+ { "mail-mark-unread",
+ "mail-mark-unread",
+ N_("_Unread"),
+ "<Shift><Control>k",
+ N_("Mark the selected messages as not having been read"),
+ G_CALLBACK (action_mail_mark_unread_cb) },
+
+ { "mail-message-edit",
+ NULL,
+ N_("_Edit as New Message…"),
+ NULL,
+ N_("Open the selected messages in the composer for editing"),
+ G_CALLBACK (action_mail_message_edit_cb) },
+
+ { "mail-message-new",
+ "mail-message-new",
+ N_("Compose _New Message"),
+ "<Shift><Control>m",
+ N_("Open a window for composing a mail message"),
+ G_CALLBACK (action_mail_message_new_cb) },
+
+ { "mail-message-open",
+ NULL,
+ N_("_Open in New Window"),
+ "<Control>o",
+ N_("Open the selected messages in a new window"),
+ G_CALLBACK (action_mail_message_open_cb) },
+
+ { "mail-move",
+ "mail-move",
+ N_("_Move to Folder…"),
+ "<Shift><Control>v",
+ N_("Move selected messages to another folder"),
+ G_CALLBACK (action_mail_move_cb) },
+
+ { "mail-next",
+ "go-next",
+ N_("_Next Message"),
+ "<Control>Page_Down",
+ N_("Display the next message"),
+ G_CALLBACK (action_mail_next_cb) },
+
+ { "mail-next-important",
+ NULL,
+ N_("Next _Important Message"),
+ NULL,
+ N_("Display the next important message"),
+ G_CALLBACK (action_mail_next_important_cb) },
+
+ { "mail-next-thread",
+ NULL,
+ N_("Next _Thread"),
+ NULL,
+ N_("Display the next thread"),
+ G_CALLBACK (action_mail_next_thread_cb) },
+
+ { "mail-next-unread",
+ "go-jump",
+ N_("Next _Unread Message"),
+ "<Control>bracketright",
+ N_("Display the next unread message"),
+ G_CALLBACK (action_mail_next_unread_cb) },
+
+ { "mail-previous",
+ "go-previous",
+ N_("_Previous Message"),
+ "<Control>Page_Up",
+ N_("Display the previous message"),
+ G_CALLBACK (action_mail_previous_cb) },
+
+ { "mail-previous-important",
+ NULL,
+ N_("Pr_evious Important Message"),
+ NULL,
+ N_("Display the previous important message"),
+ G_CALLBACK (action_mail_previous_important_cb) },
+
+ { "mail-previous-thread",
+ NULL,
+ N_("Previous T_hread"),
+ NULL,
+ N_("Display the previous thread"),
+ G_CALLBACK (action_mail_previous_thread_cb) },
+
+ { "mail-previous-unread",
+ NULL,
+ N_("P_revious Unread Message"),
+ "<Control>bracketleft",
+ N_("Display the previous unread message"),
+ G_CALLBACK (action_mail_previous_unread_cb) },
+
+ { "mail-print",
+ "document-print",
+ N_("_Print…"),
+ "<Control>p",
+ N_("Print this message"),
+ G_CALLBACK (action_mail_print_cb) },
+
+ { "mail-print-preview",
+ "document-print-preview",
+ N_("Pre_view…"),
+ NULL,
+ N_("Preview the message to be printed"),
+ G_CALLBACK (action_mail_print_preview_cb) },
+
+ { "mail-redirect",
+ NULL,
+ N_("Re_direct"),
+ NULL,
+ N_("Redirect (bounce) the selected message to someone"),
+ G_CALLBACK (action_mail_redirect_cb) },
+
+ { "mail-remove-attachments",
+ "edit-delete",
+ N_("Remo_ve Attachments"),
+ NULL,
+ N_("Remove attachments"),
+ G_CALLBACK (action_mail_remove_attachments_cb) },
+
+ { "mail-remove-duplicates",
+ NULL,
+ N_("Remove Du_plicate Messages"),
+ NULL,
+ N_("Checks selected messages for duplicates"),
+ G_CALLBACK (action_mail_remove_duplicates_cb) },
+
+ { "mail-reply-all",
+ NULL,
+ N_("Reply to _All"),
+ "<Shift><Control>r",
+ N_("Compose a reply to all the recipients of the selected message"),
+ G_CALLBACK (action_mail_reply_all_cb) },
+
+ { "mail-reply-alternative",
+ NULL,
+ N_("Al_ternative Reply…"),
+ "<Alt><Control>r",
+ N_("Choose reply options for the selected message"),
+ G_CALLBACK (action_mail_reply_alternative_cb) },
+
+ { "mail-reply-group",
+ "mail-reply-all",
+ N_("Group Reply"),
+ "<Control>g",
+ N_("Reply to the mailing list, or to all recipients"),
+ G_CALLBACK (action_mail_reply_group_cb) },
+
+ { "mail-reply-list",
+ NULL,
+ N_("Reply to _List"),
+ "<Control>l",
+ N_("Compose a reply to the mailing list of the selected message"),
+ G_CALLBACK (action_mail_reply_list_cb) },
+
+ { "mail-reply-sender",
+ "mail-reply-sender",
+ N_("_Reply to Sender"),
+ "<Control>r",
+ N_("Compose a reply to the sender of the selected message"),
+ G_CALLBACK (action_mail_reply_sender_cb) },
+
+ { "mail-reply-template",
+ NULL,
+ N_("Repl_y with Template"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-save-as",
+ "document-save-as",
+ N_("_Save as mbox…"),
+ "<Control>s",
+ N_("Save selected messages as an mbox file"),
+ G_CALLBACK (action_mail_save_as_cb) },
+
+ { "mail-search-web",
+ NULL,
+ N_("Search _Web…"),
+ NULL,
+ N_("Search the Web with the selected text"),
+ G_CALLBACK (action_mail_search_web_cb) },
+
+ { "mail-show-source",
+ NULL,
+ N_("_Message Source"),
+ "<Control>u",
+ N_("Show the raw email source of the message"),
+ G_CALLBACK (action_mail_show_source_cb) },
+
+ { "mail-toggle-important",
+ NULL,
+ NULL, /* No menu item; key press only */
+ NULL,
+ NULL,
+ G_CALLBACK (action_mail_toggle_important_cb) },
+
+ { "mail-undelete",
+ NULL,
+ N_("_Undelete Message"),
+ "<Shift><Control>d",
+ N_("Undelete the selected messages"),
+ G_CALLBACK (action_mail_undelete_cb) },
+
+ { "mail-zoom-100",
+ "zoom-original",
+ N_("_Normal Size"),
+ "<Control>0",
+ N_("Reset the text to its original size"),
+ G_CALLBACK (action_mail_zoom_100_cb) },
+
+ { "mail-zoom-in",
+ "zoom-in",
+ N_("_Zoom In"),
+ "<Control>plus",
+ N_("Increase the text size"),
+ G_CALLBACK (action_mail_zoom_in_cb) },
+
+ { "mail-zoom-out",
+ "zoom-out",
+ N_("Zoom _Out"),
+ "<Control>minus",
+ N_("Decrease the text size"),
+ G_CALLBACK (action_mail_zoom_out_cb) },
+
+ /*** Menus ***/
+
+ { "mail-create-menu",
+ NULL,
+ N_("Cre_ate"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-encoding-menu",
+ NULL,
+ N_("Ch_aracter Encoding"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-forward-as-menu",
+ NULL,
+ N_("F_orward As"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-label-menu",
+ NULL,
+ N_("_Label"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-reply-group-menu",
+ NULL,
+ N_("_Group Reply"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-goto-menu",
+ NULL,
+ N_("_Go To"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-mark-as-menu",
+ NULL,
+ N_("Mar_k As"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-message-menu",
+ NULL,
+ N_("_Message"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-zoom-menu",
+ NULL,
+ N_("_Zoom"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static GtkActionEntry mail_reader_search_folder_entries[] = {
+
+ { "mail-search-folder-from-mailing-list",
+ NULL,
+ N_("Create a Search Folder from Mailing _List…"),
+ NULL,
+ N_("Create a search folder for this mailing list"),
+ G_CALLBACK (action_mail_search_folder_from_mailing_list_cb) },
+
+ { "mail-search-folder-from-recipients",
+ NULL,
+ N_("Create a Search Folder from Recipien_ts…"),
+ NULL,
+ N_("Create a search folder for these recipients"),
+ G_CALLBACK (action_mail_search_folder_from_recipients_cb) },
+
+ { "mail-search-folder-from-sender",
+ NULL,
+ N_("Create a Search Folder from Sen_der…"),
+ NULL,
+ N_("Create a search folder for this sender"),
+ G_CALLBACK (action_mail_search_folder_from_sender_cb) },
+
+ { "mail-search-folder-from-subject",
+ NULL,
+ N_("Create a Search Folder from S_ubject…"),
+ NULL,
+ N_("Create a search folder for this subject"),
+ G_CALLBACK (action_mail_search_folder_from_subject_cb) },
+};
+
+static EPopupActionEntry mail_reader_popup_entries[] = {
+
+ { "mail-popup-archive",
+ NULL,
+ "mail-archive" },
+
+ { "mail-popup-color-assign",
+ NULL,
+ "mail-color-assign" },
+
+ { "mail-popup-color-unset",
+ NULL,
+ "mail-color-unset" },
+
+ { "mail-popup-copy",
+ NULL,
+ "mail-copy" },
+
+ { "mail-popup-delete",
+ NULL,
+ "mail-delete" },
+
+ { "mail-popup-add-note",
+ NULL,
+ "mail-add-note" },
+
+ { "mail-popup-delete-note",
+ NULL,
+ "mail-delete-note" },
+
+ { "mail-popup-edit-note",
+ NULL,
+ "mail-edit-note" },
+
+ { "mail-popup-flag-clear",
+ NULL,
+ "mail-flag-clear" },
+
+ { "mail-popup-flag-completed",
+ NULL,
+ "mail-flag-completed" },
+
+ { "mail-popup-flag-for-followup",
+ N_("Mark for Follo_w Up…"),
+ "mail-flag-for-followup" },
+
+ { "mail-popup-forward",
+ NULL,
+ "mail-forward" },
+
+ { "mail-popup-mark-ignore-thread-sub",
+ N_("_Ignore Subthread"),
+ "mail-mark-ignore-thread-sub" },
+
+ { "mail-popup-mark-ignore-thread-whole",
+ N_("_Ignore Thread"),
+ "mail-mark-ignore-thread-whole" },
+
+ { "mail-popup-mark-important",
+ N_("Mark as _Important"),
+ "mail-mark-important" },
+
+ { "mail-popup-mark-junk",
+ N_("Mark as _Junk"),
+ "mail-mark-junk" },
+
+ { "mail-popup-mark-notjunk",
+ N_("Mark as _Not Junk"),
+ "mail-mark-notjunk" },
+
+ { "mail-popup-mark-read",
+ N_("Mar_k as Read"),
+ "mail-mark-read" },
+
+ { "mail-popup-mark-unignore-thread-sub",
+ N_("Do not _Ignore Subthread"),
+ "mail-mark-unignore-thread-sub" },
+
+ { "mail-popup-mark-unignore-thread-whole",
+ N_("Do not _Ignore Thread"),
+ "mail-mark-unignore-thread-whole" },
+
+ { "mail-popup-mark-unimportant",
+ N_("Mark as Uni_mportant"),
+ "mail-mark-unimportant" },
+
+ { "mail-popup-mark-unread",
+ N_("Mark as _Unread"),
+ "mail-mark-unread" },
+
+ { "mail-popup-message-edit",
+ NULL,
+ "mail-message-edit" },
+
+ { "mail-popup-move",
+ NULL,
+ "mail-move" },
+
+ { "mail-popup-print",
+ NULL,
+ "mail-print" },
+
+ { "mail-popup-remove-attachments",
+ NULL,
+ "mail-remove-attachments" },
+
+ { "mail-popup-remove-duplicates",
+ NULL,
+ "mail-remove-duplicates" },
+
+ { "mail-popup-reply-all",
+ NULL,
+ "mail-reply-all" },
+
+ { "mail-popup-reply-sender",
+ NULL,
+ "mail-reply-sender" },
+
+ { "mail-popup-reply-template",
+ NULL,
+ "mail-reply-template" },
+
+ { "mail-popup-save-as",
+ NULL,
+ "mail-save-as" },
+
+ { "mail-popup-search-web",
+ NULL,
+ "mail-search-web" },
+
+ { "mail-popup-undelete",
+ NULL,
+ "mail-undelete" }
+};
+
+static GtkToggleActionEntry mail_reader_toggle_entries[] = {
+
+ { "mail-caret-mode",
+ NULL,
+ N_("_Caret Mode"),
+ "F7",
+ N_("Show a blinking cursor in the body of displayed messages"),
+ NULL, /* No callback required */
+ FALSE },
+
+ { "mail-show-all-headers",
+ NULL,
+ N_("All Message _Headers"),
+ NULL,
+ N_("Show messages with all email headers"),
+ G_CALLBACK (action_mail_show_all_headers_cb),
+ FALSE }
+};
+
+static void
+mail_reader_double_click_cb (EMailReader *reader,
+ gint row,
+ ETreePath path,
+ gint col,
+ GdkEvent *event)
+{
+ GtkAction *action;
+
+ /* Ignore double clicks on columns that handle their own state. */
+ if (MESSAGE_LIST_COLUMN_IS_ACTIVE (col))
+ return;
+
+ action = e_mail_reader_get_action (reader, "mail-message-open");
+ gtk_action_activate (action);
+}
+
+static gboolean
+mail_reader_key_press_event_cb (EMailReader *reader,
+ GdkEventKey *event)
+{
+ GtkAction *action;
+ const gchar *action_name;
+
+ if (!gtk_widget_has_focus (GTK_WIDGET (reader))) {
+ EMailDisplay *display;
+
+ display = e_mail_reader_get_mail_display (reader);
+ if (e_web_view_get_need_input (E_WEB_VIEW (display)) &&
+ gtk_widget_has_focus (GTK_WIDGET (display)))
+ return FALSE;
+ }
+
+ if ((event->state & GDK_CONTROL_MASK) != 0)
+ goto ctrl;
+
+ /* <keyval> alone */
+ switch (event->keyval) {
+ case GDK_KEY_Delete:
+ case GDK_KEY_KP_Delete:
+ action_name = "mail-delete";
+ break;
+
+ case GDK_KEY_Return:
+ case GDK_KEY_KP_Enter:
+ case GDK_KEY_ISO_Enter:
+ if (E_IS_MAIL_BROWSER (reader))
+ return FALSE;
+
+ action_name = "mail-message-open";
+ break;
+
+ case GDK_KEY_period:
+ case GDK_KEY_bracketright:
+ action_name = "mail-next-unread";
+ break;
+
+ case GDK_KEY_comma:
+ case GDK_KEY_bracketleft:
+ action_name = "mail-previous-unread";
+ break;
+
+#ifdef HAVE_XFREE
+ case XF86XK_Reply:
+ action_name = "mail-reply-all";
+ break;
+
+ case XF86XK_MailForward:
+ action_name = "mail-forward";
+ break;
+#endif
+
+ case GDK_KEY_exclam:
+ action_name = "mail-toggle-important";
+ break;
+
+ case GDK_KEY_ZoomIn:
+ action_name = "mail-zoom-in";
+ break;
+
+ case GDK_KEY_ZoomOut:
+ action_name = "mail-zoom-out";
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ goto exit;
+
+ctrl:
+
+ /* Ctrl + <keyval> */
+ switch (event->keyval) {
+ case GDK_KEY_period:
+ action_name = "mail-next-unread";
+ break;
+
+ case GDK_KEY_comma:
+ action_name = "mail-previous-unread";
+ break;
+
+ case GDK_KEY_equal:
+ case GDK_KEY_KP_Add:
+ action_name = "mail-zoom-in";
+ break;
+
+ case GDK_KEY_KP_Subtract:
+ action_name = "mail-zoom-out";
+ break;
+
+ default:
+ return FALSE;
+ }
+
+exit:
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_activate (action);
+
+ return TRUE;
+}
+
+static gint
+mail_reader_key_press_cb (EMailReader *reader,
+ gint row,
+ ETreePath path,
+ gint col,
+ GdkEvent *event)
+{
+ return mail_reader_key_press_event_cb (reader, &event->key);
+}
+
+static gboolean
+mail_reader_message_seen_cb (gpointer user_data)
+{
+ EMailReaderClosure *closure = user_data;
+ EMailReader *reader;
+ GtkWidget *message_list;
+ EMailPartList *parts;
+ EMailDisplay *display;
+ CamelMimeMessage *message;
+ const gchar *current_uid;
+ const gchar *message_uid;
+ gboolean uid_is_current = TRUE;
+
+ reader = closure->reader;
+ message_uid = closure->message_uid;
+
+ display = e_mail_reader_get_mail_display (reader);
+ parts = e_mail_display_get_part_list (display);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ g_return_val_if_fail (IS_MESSAGE_LIST (message_list), FALSE);
+
+ /* zero the timeout id now, if it was not rescheduled */
+ if (g_source_get_id (g_main_current_source ()) == MESSAGE_LIST (message_list)->seen_id)
+ MESSAGE_LIST (message_list)->seen_id = 0;
+
+ if (e_tree_is_dragging (E_TREE (message_list)))
+ return FALSE;
+
+ current_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ uid_is_current &= (g_strcmp0 (current_uid, message_uid) == 0);
+
+ if (parts != NULL)
+ message = e_mail_part_list_get_message (parts);
+ else
+ message = NULL;
+
+ if (uid_is_current && message != NULL)
+ g_signal_emit (
+ reader, signals[MESSAGE_SEEN], 0,
+ message_uid, message);
+
+ return FALSE;
+}
+
+static void
+schedule_timeout_mark_seen (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ MessageList *message_list;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+ g_return_if_fail (message_list != NULL);
+
+ if (message_list->cursor_uid) {
+ EMailReaderClosure *timeout_closure;
+
+ if (message_list->seen_id > 0) {
+ g_source_remove (message_list->seen_id);
+ message_list->seen_id = 0;
+ }
+
+ timeout_closure = g_slice_new0 (EMailReaderClosure);
+ timeout_closure->reader = g_object_ref (reader);
+ timeout_closure->message_uid = g_strdup (message_list->cursor_uid);
+
+ MESSAGE_LIST (message_list)->seen_id =
+ e_named_timeout_add_full (
+ G_PRIORITY_DEFAULT, priv->schedule_mark_seen_interval,
+ mail_reader_message_seen_cb,
+ timeout_closure, (GDestroyNotify)
+ mail_reader_closure_free);
+ }
+}
+
+static void
+mail_reader_load_changed_cb (EMailReader *reader,
+ WebKitLoadEvent event,
+ EMailDisplay *display)
+{
+ EMailReaderPrivate *priv;
+
+ if (event != WEBKIT_LOAD_FINISHED)
+ return;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ if (priv->schedule_mark_seen &&
+ E_IS_MAIL_VIEW (reader) &&
+ e_mail_display_get_part_list (display) &&
+ e_mail_view_get_preview_visible (E_MAIL_VIEW (reader))) {
+ if (priv->folder_was_just_selected)
+ priv->folder_was_just_selected = FALSE;
+ else
+ schedule_timeout_mark_seen (reader);
+ }
+}
+
+static void
+mail_reader_remote_content_clicked_cb (EMailReader *reader,
+ const GdkRectangle *position,
+ gpointer user_data)
+{
+ GtkWidget *mail_display = user_data;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+ g_return_if_fail (E_IS_MAIL_DISPLAY (mail_display));
+
+ e_mail_remote_content_popover_run (reader, mail_display, position);
+}
+
+static void
+maybe_schedule_timeout_mark_seen (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ MessageList *message_list;
+ gboolean schedule_timeout;
+ gint timeout_interval = -1;
+ const gchar *message_uid;
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+
+ message_uid = message_list->cursor_uid;
+ if (message_uid == NULL ||
+ e_tree_is_dragging (E_TREE (message_list)))
+ return;
+
+ schedule_timeout =
+ (message_uid != NULL) &&
+ e_mail_reader_utils_get_mark_seen_setting (reader, &timeout_interval);
+
+ if (message_list->seen_id > 0) {
+ g_source_remove (message_list->seen_id);
+ message_list->seen_id = 0;
+ }
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ priv->schedule_mark_seen = schedule_timeout;
+ priv->schedule_mark_seen_interval = timeout_interval;
+}
+
+static gboolean
+discard_timeout_mark_seen_cb (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ MessageList *message_list;
+
+ g_return_val_if_fail (reader != NULL, FALSE);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ priv->schedule_mark_seen = FALSE;
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+ g_return_val_if_fail (message_list != NULL, FALSE);
+
+ if (message_list->seen_id > 0) {
+ g_source_remove (message_list->seen_id);
+ message_list->seen_id = 0;
+ }
+
+ return FALSE;
+}
+
+
+static void
+mail_reader_preview_pane_visible_changed_cb (EMailReader *reader,
+ GParamSpec *param,
+ GtkWidget *widget)
+{
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ if (!gtk_widget_is_visible (widget))
+ discard_timeout_mark_seen_cb (reader);
+}
+
+static void
+mail_reader_remove_followup_alert (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ if (!priv)
+ return;
+
+ if (priv->followup_alert)
+ e_alert_response (priv->followup_alert, GTK_RESPONSE_OK);
+}
+
+static void
+mail_reader_manage_followup_flag (EMailReader *reader,
+ CamelFolder *folder,
+ const gchar *message_uid)
+{
+ EMailReaderPrivate *priv;
+ CamelMessageInfo *info;
+ const gchar *followup, *completed_on, *due_by;
+ time_t date;
+ gchar *date_str = NULL;
+ gboolean alert_added = FALSE;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+ g_return_if_fail (CAMEL_IS_FOLDER (folder));
+ g_return_if_fail (message_uid != NULL);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ if (!priv)
+ return;
+
+ info = camel_folder_get_message_info (folder, message_uid);
+ if (!info)
+ return;
+
+ followup = camel_message_info_get_user_tag (info, "follow-up");
+ if (followup && *followup) {
+ EPreviewPane *preview_pane;
+ const gchar *alert_tag;
+ EAlert *alert;
+
+ completed_on = camel_message_info_get_user_tag (info, "completed-on");
+ due_by = camel_message_info_get_user_tag (info, "due-by");
+
+ if (completed_on && *completed_on) {
+ alert_tag = "mail:follow-up-completed-info";
+ date = camel_header_decode_date (completed_on, NULL);
+ date_str = e_datetime_format_format ("mail", "header", DTFormatKindDateTime, date);
+ } else if (due_by && *due_by) {
+ time_t now;
+
+ alert_tag = "mail:follow-up-dueby-info";
+ date = camel_header_decode_date (due_by, NULL);
+ date_str = e_datetime_format_format ("mail", "header", DTFormatKindDateTime, date);
+
+ now = time (NULL);
+ if (now > date)
+ alert_tag = "mail:follow-up-overdue-error";
+ } else {
+ alert_tag = "mail:follow-up-flag-info";
+ }
+
+ alert = e_alert_new (alert_tag, followup, date_str ? date_str : "???", NULL);
+
+ g_free (date_str);
+
+ preview_pane = e_mail_reader_get_preview_pane (reader);
+ e_alert_sink_submit_alert (E_ALERT_SINK (preview_pane), alert);
+
+ alert_added = TRUE;
+
+ mail_reader_remove_followup_alert (reader);
+ priv->followup_alert = alert;
+ g_object_add_weak_pointer (G_OBJECT (priv->followup_alert), &priv->followup_alert);
+
+ g_object_unref (alert);
+ }
+
+ g_clear_object (&info);
+
+ if (!alert_added)
+ mail_reader_remove_followup_alert (reader);
+}
+
+static void
+mail_reader_reload (EMailReader *reader)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+ EMailDisplay *mail_display;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ if (uids && uids->len == 1)
+ mail_reader_manage_followup_flag (reader, folder, uids->pdata[0]);
+
+ g_clear_object (&folder);
+ if (uids)
+ g_ptr_array_unref (uids);
+
+ mail_display = e_mail_reader_get_mail_display (reader);
+ e_mail_display_reload (mail_display);
+}
+
+static void
+mail_reader_remove_ui (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ GtkWindow *window;
+ GtkUIManager *ui_manager = NULL;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (!priv->main_menu_label_merge_id)
+ return;
+
+ window = e_mail_reader_get_window (reader);
+ g_return_if_fail (window != NULL);
+
+ if (E_IS_SHELL_WINDOW (window))
+ ui_manager = e_shell_window_get_ui_manager (E_SHELL_WINDOW (window));
+ else if (E_IS_MAIL_BROWSER (window))
+ ui_manager = e_mail_browser_get_ui_manager (E_MAIL_BROWSER (window));
+
+ g_return_if_fail (ui_manager != NULL);
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+
+ gtk_ui_manager_remove_ui (ui_manager, priv->main_menu_label_merge_id);
+}
+
+static void
+mail_reader_message_loaded_cb (CamelFolder *folder,
+ GAsyncResult *result,
+ EMailReaderClosure *closure)
+{
+ EMailReader *reader;
+ EMailReaderPrivate *priv;
+ CamelMimeMessage *message = NULL;
+ GtkWidget *message_list;
+ const gchar *message_uid;
+ GError *error = NULL;
+
+ reader = closure->reader;
+ message_uid = closure->message_uid;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ /* If the private struct is NULL, the EMailReader was destroyed
+ * while we were loading the message and we're likely holding the
+ * last reference. Nothing to do but drop the reference.
+ * FIXME Use a GWeakRef instead of this hack. */
+ if (priv == NULL) {
+ mail_reader_closure_free (closure);
+ return;
+ }
+
+ message = camel_folder_get_message_finish (folder, result, &error);
+
+ /* If the user picked a different message in the time it took
+ * to fetch this message, then don't bother rendering it. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ g_clear_error (&error);
+ goto exit;
+ }
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ if (message_list == NULL) {
+ /* For cases where message fetching took so long that
+ * user closed the message window before this was called. */
+ goto exit;
+ }
+
+ if (message != NULL) {
+ CamelMessageInfo *mi;
+
+ mail_reader_manage_followup_flag (reader, folder, message_uid);
+
+ mi = camel_folder_get_message_info (folder, message_uid);
+ if (mi) {
+ if (camel_util_fill_message_info_user_headers (mi, camel_medium_get_headers (CAMEL_MEDIUM (message))))
+ gtk_widget_queue_draw (message_list);
+
+ g_object_unref (mi);
+ }
+
+ g_signal_emit (
+ reader, signals[MESSAGE_LOADED], 0,
+ message_uid, message);
+ }
+
+exit:
+ if (error != NULL) {
+ EPreviewPane *preview_pane;
+ EWebView *web_view;
+
+ preview_pane = e_mail_reader_get_preview_pane (reader);
+ web_view = e_preview_pane_get_web_view (preview_pane);
+
+ if (g_error_matches (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE) &&
+ CAMEL_IS_OFFLINE_FOLDER (folder) &&
+ camel_service_get_connection_status (CAMEL_SERVICE (camel_folder_get_parent_store (folder))) != CAMEL_SERVICE_CONNECTED)
+ e_alert_submit (
+ E_ALERT_SINK (web_view),
+ "mail:no-retrieve-message-offline",
+ NULL);
+ else
+ e_alert_submit (
+ E_ALERT_SINK (web_view),
+ "mail:no-retrieve-message",
+ error->message, NULL);
+ }
+
+ g_clear_error (&error);
+
+ mail_reader_closure_free (closure);
+
+ g_clear_object (&message);
+}
+
+static gboolean
+mail_reader_message_selected_timeout_cb (gpointer user_data)
+{
+ EMailReader *reader;
+ EMailReaderPrivate *priv;
+ EMailDisplay *display;
+ GtkWidget *message_list;
+ const gchar *cursor_uid;
+ const gchar *format_uid;
+ EMailPartList *parts;
+
+ reader = E_MAIL_READER (user_data);
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ message_list = e_mail_reader_get_message_list (reader);
+ display = e_mail_reader_get_mail_display (reader);
+ parts = e_mail_display_get_part_list (display);
+
+ cursor_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ if (parts != NULL)
+ format_uid = e_mail_part_list_get_message_uid (parts);
+ else
+ format_uid = NULL;
+
+ if (MESSAGE_LIST (message_list)->last_sel_single) {
+ GtkWidget *widget;
+ gboolean display_visible;
+ gboolean selected_uid_changed;
+
+ /* Decide whether to download the full message now. */
+ widget = GTK_WIDGET (display);
+ display_visible = gtk_widget_get_mapped (widget);
+
+ selected_uid_changed = (g_strcmp0 (cursor_uid, format_uid) != 0);
+
+ if (display_visible && selected_uid_changed) {
+ EMailReaderClosure *closure;
+ GCancellable *cancellable;
+ CamelFolder *folder;
+ EActivity *activity;
+ gchar *string;
+
+ string = g_strdup_printf (
+ _("Retrieving message “%s”"), cursor_uid);
+ e_mail_display_set_part_list (display, NULL);
+ e_mail_display_set_status (display, string);
+ g_free (string);
+
+ activity = e_mail_reader_new_activity (reader);
+ e_activity_set_text (activity, _("Retrieving message"));
+ cancellable = e_activity_get_cancellable (activity);
+
+ closure = g_slice_new0 (EMailReaderClosure);
+ closure->activity = activity;
+ closure->reader = g_object_ref (reader);
+ closure->message_uid = g_strdup (cursor_uid);
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ camel_folder_get_message (
+ folder, cursor_uid, G_PRIORITY_DEFAULT,
+ cancellable, (GAsyncReadyCallback)
+ mail_reader_message_loaded_cb, closure);
+
+ g_clear_object (&folder);
+
+ if (priv->retrieving_message != NULL)
+ g_object_unref (priv->retrieving_message);
+ priv->retrieving_message = g_object_ref (cancellable);
+ }
+ } else {
+ e_mail_display_set_part_list (display, NULL);
+ }
+
+ priv->message_selected_timeout_id = 0;
+
+ return FALSE;
+}
+
+static void
+mail_reader_message_selected_cb (EMailReader *reader,
+ const gchar *message_uid)
+{
+ EMailReaderPrivate *priv;
+ MessageList *message_list;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ /* Cancel the previous message retrieval activity. */
+ g_cancellable_cancel (priv->retrieving_message);
+
+ /* Cancel the message selected timer. */
+ if (priv->message_selected_timeout_id > 0) {
+ g_source_remove (priv->message_selected_timeout_id);
+ priv->message_selected_timeout_id = 0;
+ }
+
+ if (priv->folder_was_just_selected && message_uid) {
+ if (priv->did_try_to_open_message)
+ priv->folder_was_just_selected = FALSE;
+ else
+ priv->did_try_to_open_message = TRUE;
+ }
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+ if (message_list) {
+ EMailPartList *parts;
+ const gchar *cursor_uid, *format_uid;
+
+ parts = e_mail_display_get_part_list (e_mail_reader_get_mail_display (reader));
+
+ cursor_uid = MESSAGE_LIST (message_list)->cursor_uid;
+ if (parts != NULL)
+ format_uid = e_mail_part_list_get_message_uid (parts);
+ else
+ format_uid = NULL;
+
+ /* It can happen when the message was loaded that quickly that
+ it was delivered before this callback. */
+ if (g_strcmp0 (cursor_uid, format_uid) == 0) {
+ e_mail_reader_changed (reader);
+ return;
+ }
+ }
+
+ /* Cancel the seen timer. */
+ if (message_list != NULL && message_list->seen_id) {
+ g_source_remove (message_list->seen_id);
+ message_list->seen_id = 0;
+ }
+
+ if (message_list_selected_count (message_list) != 1) {
+ EMailDisplay *display;
+
+ display = e_mail_reader_get_mail_display (reader);
+ e_mail_display_set_part_list (display, NULL);
+ e_web_view_clear (E_WEB_VIEW (display));
+
+ } else if (priv->folder_was_just_selected) {
+ /* Skip the timeout if we're restoring the previous message
+ * selection. The timeout is there for when we're scrolling
+ * rapidly through the message list. */
+ mail_reader_message_selected_timeout_cb (reader);
+
+ } else {
+ priv->message_selected_timeout_id = e_named_timeout_add (
+ 100, mail_reader_message_selected_timeout_cb, reader);
+ }
+
+ e_mail_reader_changed (reader);
+}
+
+static void
+mail_reader_message_cursor_change_cb (EMailReader *reader)
+{
+ MessageList *message_list;
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (reader != NULL);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ g_return_if_fail (priv != NULL);
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+ g_return_if_fail (message_list != NULL);
+
+ if (message_list->seen_id == 0 &&
+ E_IS_MAIL_VIEW (reader) &&
+ e_mail_view_get_preview_visible (E_MAIL_VIEW (reader)) &&
+ !priv->avoid_next_mark_as_seen)
+ maybe_schedule_timeout_mark_seen (reader);
+}
+
+static void
+mail_reader_emit_folder_loaded (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ MessageList *message_list;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+
+ if (priv && (message_list_count (message_list) <= 0 ||
+ message_list_selected_count (message_list) <= 0))
+ priv->avoid_next_mark_as_seen = FALSE;
+
+ g_signal_emit (reader, signals[FOLDER_LOADED], 0);
+}
+
+static void
+mail_reader_message_list_built_cb (MessageList *message_list,
+ EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ mail_reader_emit_folder_loaded (reader);
+
+ /* No cursor_uid means that there will not be emitted any
+ "cursor-changed" and "message-selected" signal, thus
+ unset the "just selected folder" flag */
+ if (!message_list->cursor_uid)
+ priv->folder_was_just_selected = FALSE;
+}
+
+static EAlertSink *
+mail_reader_get_alert_sink (EMailReader *reader)
+{
+ EPreviewPane *preview_pane;
+
+ preview_pane = e_mail_reader_get_preview_pane (reader);
+
+ if (!gtk_widget_is_visible (GTK_WIDGET (preview_pane))) {
+ GtkWindow *window;
+
+ window = e_mail_reader_get_window (reader);
+
+ if (E_IS_SHELL_WINDOW (window))
+ return E_ALERT_SINK (window);
+ }
+
+ return E_ALERT_SINK (preview_pane);
+}
+
+static GPtrArray *
+mail_reader_get_selected_uids (EMailReader *reader)
+{
+ GtkWidget *message_list;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ return message_list_get_selected (MESSAGE_LIST (message_list));
+}
+
+static GPtrArray *
+mail_reader_get_selected_uids_with_collapsed_threads (EMailReader *reader)
+{
+ GtkWidget *message_list;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ return message_list_get_selected_with_collapsed_threads (MESSAGE_LIST (message_list));
+}
+
+static CamelFolder *
+mail_reader_ref_folder (EMailReader *reader)
+{
+ GtkWidget *message_list;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ return message_list_ref_folder (MESSAGE_LIST (message_list));
+}
+
+static void
+mail_reader_set_folder (EMailReader *reader,
+ CamelFolder *folder)
+{
+ EMailReaderPrivate *priv;
+ EMailDisplay *display;
+ CamelFolder *previous_folder;
+ GtkWidget *message_list;
+ EMailBackend *backend;
+ EShell *shell;
+ gboolean sync_folder;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ display = e_mail_reader_get_mail_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ previous_folder = e_mail_reader_ref_folder (reader);
+
+ backend = e_mail_reader_get_backend (reader);
+ shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
+
+ /* Only synchronize the real folder if we're online. */
+ sync_folder =
+ (previous_folder != NULL) &&
+ (CAMEL_IS_VEE_FOLDER (previous_folder) ||
+ e_shell_get_online (shell));
+ if (sync_folder)
+ mail_sync_folder (previous_folder, TRUE, NULL, NULL);
+
+ /* Skip the rest if we're already viewing the folder. */
+ if (folder != previous_folder) {
+ e_web_view_clear (E_WEB_VIEW (display));
+
+ priv->folder_was_just_selected = (folder != NULL) && !priv->mark_seen_always;
+ priv->did_try_to_open_message = FALSE;
+
+ /* This is to make sure any post-poned changes in Search
+ * Folders will be propagated on folder selection. */
+ if (CAMEL_IS_VEE_FOLDER (folder))
+ mail_sync_folder (folder, FALSE, NULL, NULL);
+
+ message_list_set_folder (MESSAGE_LIST (message_list), folder);
+
+ mail_reader_emit_folder_loaded (reader);
+ }
+
+ g_clear_object (&previous_folder);
+}
+
+static void
+mail_reader_set_message (EMailReader *reader,
+ const gchar *message_uid)
+{
+ GtkWidget *message_list;
+ EMailReaderPrivate *priv;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ /* For a case when the preview panel had been disabled */
+ priv->folder_was_just_selected = FALSE;
+
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_uid (
+ MESSAGE_LIST (message_list), message_uid, FALSE);
+}
+
+static void
+mail_reader_folder_loaded (EMailReader *reader)
+{
+ guint32 state;
+
+ state = e_mail_reader_check_state (reader);
+ e_mail_reader_update_actions (reader, state);
+}
+
+static void
+mail_reader_message_list_suggest_update_actions_cb (EMailReader *reader)
+{
+ guint32 state;
+
+ state = e_mail_reader_check_state (reader);
+ e_mail_reader_update_actions (reader, state);
+}
+
+static void
+set_mail_display_part_list (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ EMailPartList *part_list;
+ EMailReader *reader;
+ EMailDisplay *display;
+ GError *local_error = NULL;
+
+ reader = E_MAIL_READER (object);
+
+ part_list = e_mail_reader_parse_message_finish (reader, result, &local_error);
+
+ if (local_error) {
+ g_warn_if_fail (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED));
+
+ g_clear_error (&local_error);
+ return;
+ }
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ e_mail_display_set_part_list (display, part_list);
+ e_mail_display_load (display, NULL);
+
+ /* Remove the reference added when parts list was
+ * created, so that only owners are EMailDisplays. */
+ g_object_unref (part_list);
+}
+
+static void
+mail_reader_set_display_formatter_for_message (EMailReader *reader,
+ EMailDisplay *display,
+ const gchar *message_uid,
+ CamelMimeMessage *message,
+ CamelFolder *folder)
+{
+ CamelObjectBag *registry;
+ EMailPartList *parts;
+ EMailReaderPrivate *priv;
+ gchar *mail_uri;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ mail_uri = e_mail_part_build_uri (folder, message_uid, NULL, NULL);
+ registry = e_mail_part_list_get_registry ();
+ parts = camel_object_bag_peek (registry, mail_uri);
+ g_free (mail_uri);
+
+ if (parts == NULL) {
+ if (!priv->retrieving_message)
+ priv->retrieving_message = camel_operation_new ();
+
+ e_mail_reader_parse_message (
+ reader, folder, message_uid, message,
+ priv->retrieving_message,
+ set_mail_display_part_list, NULL);
+ } else {
+ e_mail_display_set_part_list (display, parts);
+ e_mail_display_load (display, NULL);
+ g_object_unref (parts);
+ }
+}
+
+static void
+mail_reader_message_loaded (EMailReader *reader,
+ const gchar *message_uid,
+ CamelMimeMessage *message)
+{
+ EMailReaderPrivate *priv;
+ GtkWidget *message_list;
+ EMailBackend *backend;
+ CamelFolder *folder;
+ EMailDisplay *display;
+ EShellBackend *shell_backend;
+ EShell *shell;
+ EMEvent *event;
+ EMEventTargetMessage *target;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ folder = e_mail_reader_ref_folder (reader);
+ backend = e_mail_reader_get_backend (reader);
+ display = e_mail_reader_get_mail_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ shell_backend = E_SHELL_BACKEND (backend);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ /** @Event: message.reading
+ * @Title: Viewing a message
+ * @Target: EMEventTargetMessage
+ *
+ * message.reading is emitted whenever a user views a message.
+ */
+ event = em_event_peek ();
+ target = em_event_target_new_message (
+ event, folder, message, message_uid, 0, NULL);
+ e_event_emit (
+ (EEvent *) event, "message.reading",
+ (EEventTarget *) target);
+
+ mail_reader_set_display_formatter_for_message (
+ reader, display, message_uid, message, folder);
+
+ /* Reset the shell view icon. */
+ e_shell_event (shell, "mail-icon", (gpointer) "evolution-mail");
+
+ if (MESSAGE_LIST (message_list)->seen_id > 0) {
+ g_source_remove (MESSAGE_LIST (message_list)->seen_id);
+ MESSAGE_LIST (message_list)->seen_id = 0;
+ }
+
+ /* Determine whether to mark the message as read. */
+ if (message != NULL &&
+ !priv->avoid_next_mark_as_seen)
+ maybe_schedule_timeout_mark_seen (reader);
+
+ priv->avoid_next_mark_as_seen = FALSE;
+
+ g_clear_object (&folder);
+}
+
+static void
+mail_reader_message_seen (EMailReader *reader,
+ const gchar *message_uid,
+ CamelMimeMessage *message)
+{
+ CamelFolder *folder;
+ guint32 mask, set;
+
+ mask = CAMEL_MESSAGE_SEEN;
+ set = CAMEL_MESSAGE_SEEN;
+
+ folder = e_mail_reader_ref_folder (reader);
+ camel_folder_set_message_flags (folder, message_uid, mask, set);
+ g_clear_object (&folder);
+}
+
+static void
+mail_reader_show_search_bar (EMailReader *reader)
+{
+ EPreviewPane *preview_pane;
+
+ preview_pane = e_mail_reader_get_preview_pane (reader);
+ e_preview_pane_show_search_bar (preview_pane);
+}
+
+static void
+action_mail_label_cb (GtkToggleAction *action,
+ EMailReader *reader)
+{
+ CamelFolder *folder;
+ GPtrArray *uids;
+ const gchar *tag;
+ gint ii;
+
+ tag = g_object_get_data (G_OBJECT (action), "tag");
+ g_return_if_fail (tag != NULL);
+
+ uids = e_mail_reader_get_selected_uids (reader);
+ if (!uids)
+ return;
+
+ folder = e_mail_reader_ref_folder (reader);
+
+ camel_folder_freeze (folder);
+ for (ii = 0; ii < uids->len; ii++) {
+ if (gtk_toggle_action_get_active (action))
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], tag, TRUE);
+ else {
+ camel_folder_set_message_user_flag (
+ folder, uids->pdata[ii], tag, FALSE);
+ camel_folder_set_message_user_tag (
+ folder, uids->pdata[ii], "label", NULL);
+ }
+ }
+ camel_folder_thaw (folder);
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+}
+
+#define LABEL_UNKNOWN 0
+#define LABEL_EXISTS (1 << 0)
+#define LABEL_NOTEXIST (1 << 1)
+
+static GHashTable *
+mail_reader_gather_labels_info (EMailReader *reader,
+ EMailLabelListStore *label_store,
+ GPtrArray *uids)
+{
+ GHashTable *labels_info;
+ CamelFolder *folder;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gboolean valid;
+ guint ii;
+
+ labels_info = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ model = GTK_TREE_MODEL (label_store);
+ folder = e_mail_reader_ref_folder (reader);
+
+ if (!folder)
+ return labels_info;
+
+ for (ii = 0; ii < uids->len; ii++) {
+ CamelMessageInfo *info;
+
+ info = camel_folder_get_message_info (folder, uids->pdata[ii]);
+ if (!info)
+ continue;
+
+ for (valid = gtk_tree_model_get_iter_first (model, &iter);
+ valid;
+ valid = gtk_tree_model_iter_next (model, &iter)) {
+ gchar *tag;
+ guint value;
+
+ tag = e_mail_label_list_store_get_tag (label_store, &iter);
+ value = GPOINTER_TO_UINT (g_hash_table_lookup (labels_info, tag));
+ if ((!(value & LABEL_EXISTS)) || (!(value & LABEL_NOTEXIST))) {
+ gboolean exists = FALSE, notexist = FALSE;
+
+ /* Check for new-style labels. */
+ if (camel_message_info_get_user_flag (info, tag)) {
+ exists = TRUE;
+ } else {
+ /* Check for old-style labels. */
+ const gchar *old_label = camel_message_info_get_user_tag (info, "label");
+ if (old_label) {
+ gchar *new_label;
+
+ /* Convert old-style labels ("<name>") to "$Label<name>". */
+ new_label = g_alloca (strlen (old_label) + 10);
+ g_stpcpy (g_stpcpy (new_label, "$Label"), old_label);
+
+ if (g_strcmp0 (new_label, tag) == 0)
+ exists = TRUE;
+ else
+ notexist = TRUE;
+ } else {
+ notexist = TRUE;
+ }
+ }
+
+ value = value |
+ (exists ? LABEL_EXISTS : LABEL_UNKNOWN) |
+ (notexist ? LABEL_NOTEXIST : LABEL_UNKNOWN);
+
+ g_hash_table_insert (labels_info, tag, GUINT_TO_POINTER (value));
+
+ /* the hash table took the 'tag' */
+ tag = NULL;
+ }
+
+ g_free (tag);
+ }
+
+ g_clear_object (&info);
+ }
+
+ g_clear_object (&folder);
+
+ return labels_info;
+}
+
+static void
+mail_reader_update_label_action (GtkToggleAction *action,
+ GHashTable *labels_info, /* gchar * ~> guint */
+ const gchar *label_tag)
+{
+ gboolean exists = FALSE;
+ gboolean not_exists = FALSE;
+ gboolean sensitive;
+ guint value;
+
+ /* Figure out the proper label action state for the selected
+ * messages. If all the selected messages have the given label,
+ * make the toggle action active. If all the selected message
+ * DO NOT have the given label, make the toggle action inactive.
+ * If some do and some don't, make the action insensitive. */
+
+ value = GPOINTER_TO_UINT (g_hash_table_lookup (labels_info, label_tag));
+ exists = (value & LABEL_EXISTS) != 0;
+ not_exists = (value & LABEL_NOTEXIST) != 0;
+
+ sensitive = !(exists && not_exists);
+ gtk_toggle_action_set_active (action, exists);
+ gtk_action_set_sensitive (GTK_ACTION (action), sensitive);
+}
+
+static void
+mail_reader_update_labels_menu (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ EMailLabelListStore *label_store;
+ EMailBackend *backend;
+ EMailSession *session;
+ GtkWindow *window;
+ GtkUIManager *ui_manager = NULL;
+ GtkActionGroup *action_group;
+ GtkTreeIter iter;
+ GHashTable *labels_info; /* gchar * ~> guint { LABEL_EXISTS | LABEL_NOTEXIST | LABEL_UNKNOWN } */
+ GPtrArray *uids;
+ const gchar *main_menu_path, *popup_menu_path;
+ gboolean valid;
+ gint ii = 0;
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ window = e_mail_reader_get_window (reader);
+ g_return_if_fail (window != NULL);
+
+ if (E_IS_SHELL_WINDOW (window))
+ ui_manager = e_shell_window_get_ui_manager (E_SHELL_WINDOW (window));
+ else if (E_IS_MAIL_BROWSER (window))
+ ui_manager = e_mail_browser_get_ui_manager (E_MAIL_BROWSER (window));
+
+ g_return_if_fail (ui_manager != NULL);
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+ label_store = e_mail_ui_session_get_label_store (E_MAIL_UI_SESSION (session));
+
+ action_group = e_mail_reader_get_action_group (reader, E_MAIL_READER_ACTION_GROUP_LABELS);
+ main_menu_path = "/main-menu/custom-menus/mail-message-menu/mail-mark-as-menu/mail-label-menu/mail-label-actions";
+ popup_menu_path = "/mail-message-popup/mail-label-menu/mail-label-actions";
+
+ /* Unmerge the previous menu items. */
+ if (priv->main_menu_label_merge_id)
+ gtk_ui_manager_remove_ui (ui_manager, priv->main_menu_label_merge_id);
+ else
+ priv->main_menu_label_merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+
+ if (priv->popup_menu_label_merge_id)
+ gtk_ui_manager_remove_ui (ui_manager, priv->popup_menu_label_merge_id);
+ else
+ priv->popup_menu_label_merge_id = gtk_ui_manager_new_merge_id (ui_manager);
+
+ e_action_group_remove_all_actions (action_group);
+ gtk_ui_manager_ensure_update (ui_manager);
+
+ uids = e_mail_reader_get_selected_uids (reader);
+ labels_info = mail_reader_gather_labels_info (reader, label_store, uids);
+
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (label_store), &iter);
+
+ while (valid) {
+ EMailLabelAction *label_action;
+ GtkAction *action;
+ gchar *action_name;
+ gchar *stock_id;
+ gchar *label;
+ gchar *tag;
+
+ label = e_mail_label_list_store_get_name (label_store, &iter);
+ stock_id = e_mail_label_list_store_get_stock_id (label_store, &iter);
+ tag = e_mail_label_list_store_get_tag (label_store, &iter);
+ action_name = g_strdup_printf ("mail-label-%d", ii);
+
+ /* XXX Add a tooltip! */
+ label_action = e_mail_label_action_new (action_name, label, NULL, stock_id);
+
+ g_object_set_data_full (
+ G_OBJECT (label_action), "tag",
+ tag, (GDestroyNotify) g_free);
+
+ /* Configure the action before we connect to signals. */
+ mail_reader_update_label_action (GTK_TOGGLE_ACTION (label_action), labels_info, tag);
+
+ g_signal_connect (
+ label_action, "toggled",
+ G_CALLBACK (action_mail_label_cb), reader);
+
+ /* The action group takes ownership of the action. */
+ action = GTK_ACTION (label_action);
+
+ if (ii + 1 < 10) {
+ gchar accel[5];
+
+ accel[0] = '1' + ii;
+ accel[1] = '\0';
+
+ gtk_action_group_add_action_with_accel (action_group, action, accel);
+ } else {
+ gtk_action_group_add_action (action_group, action);
+ }
+ g_object_unref (label_action);
+
+ gtk_ui_manager_add_ui (
+ ui_manager, priv->main_menu_label_merge_id, main_menu_path,
+ action_name, action_name, GTK_UI_MANAGER_AUTO, FALSE);
+
+ gtk_ui_manager_add_ui (
+ ui_manager, priv->popup_menu_label_merge_id, popup_menu_path,
+ action_name, action_name, GTK_UI_MANAGER_AUTO, FALSE);
+
+ g_free (label);
+ g_free (stock_id);
+ g_free (action_name);
+
+ valid = gtk_tree_model_iter_next (
+ GTK_TREE_MODEL (label_store), &iter);
+ ii++;
+ }
+
+ g_hash_table_destroy (labels_info);
+ g_ptr_array_unref (uids);
+}
+
+static void
+mail_reader_update_actions (EMailReader *reader,
+ guint32 state)
+{
+ GtkAction *action;
+ const gchar *action_name;
+ gboolean sensitive;
+ EMailDisplay *mail_display;
+
+ /* Be descriptive. */
+ gboolean any_messages_selected;
+ gboolean enable_flag_clear;
+ gboolean enable_flag_completed;
+ gboolean have_enabled_account;
+ gboolean multiple_messages_selected;
+ gboolean selection_has_attachment_messages;
+ gboolean selection_has_deleted_messages;
+ gboolean selection_has_ignore_thread_messages;
+ gboolean selection_has_notignore_thread_messages;
+ gboolean selection_has_important_messages;
+ gboolean selection_has_junk_messages;
+ gboolean selection_has_not_junk_messages;
+ gboolean selection_has_read_messages;
+ gboolean selection_has_undeleted_messages;
+ gboolean selection_has_unimportant_messages;
+ gboolean selection_has_unread_messages;
+ gboolean selection_has_mail_note;
+ gboolean selection_has_color;
+ gboolean selection_is_mailing_list;
+ gboolean single_message_selected;
+ gboolean first_message_selected = FALSE;
+ gboolean last_message_selected = FALSE;
+
+ have_enabled_account =
+ (state & E_MAIL_READER_HAVE_ENABLED_ACCOUNT);
+ single_message_selected =
+ (state & E_MAIL_READER_SELECTION_SINGLE);
+ multiple_messages_selected =
+ (state & E_MAIL_READER_SELECTION_MULTIPLE);
+ /* FIXME Missing CAN_ADD_SENDER */
+ enable_flag_clear =
+ (state & E_MAIL_READER_SELECTION_FLAG_CLEAR);
+ enable_flag_completed =
+ (state & E_MAIL_READER_SELECTION_FLAG_COMPLETED);
+ selection_has_attachment_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_ATTACHMENTS);
+ selection_has_deleted_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_DELETED);
+ selection_has_ignore_thread_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_IGNORE_THREAD);
+ selection_has_notignore_thread_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_NOTIGNORE_THREAD);
+ selection_has_important_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_IMPORTANT);
+ selection_has_junk_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_JUNK);
+ selection_has_not_junk_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_NOT_JUNK);
+ selection_has_read_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_READ);
+ selection_has_undeleted_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_UNDELETED);
+ selection_has_unimportant_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_UNIMPORTANT);
+ selection_has_unread_messages =
+ (state & E_MAIL_READER_SELECTION_HAS_UNREAD);
+ selection_has_mail_note =
+ (state & E_MAIL_READER_SELECTION_HAS_MAIL_NOTE);
+ selection_has_color =
+ (state & E_MAIL_READER_SELECTION_HAS_COLOR);
+ selection_is_mailing_list =
+ (state & E_MAIL_READER_SELECTION_IS_MAILING_LIST);
+
+ any_messages_selected =
+ (single_message_selected || multiple_messages_selected);
+
+ mail_display = e_mail_reader_get_mail_display (reader);
+
+ if (any_messages_selected) {
+ MessageList *message_list;
+ gint row = -1, count = -1;
+ ETreeTableAdapter *etta;
+ ETreePath node = NULL;
+
+ message_list = MESSAGE_LIST (
+ e_mail_reader_get_message_list (reader));
+ etta = e_tree_get_table_adapter (E_TREE (message_list));
+
+ if (message_list->cursor_uid != NULL)
+ node = g_hash_table_lookup (
+ message_list->uid_nodemap,
+ message_list->cursor_uid);
+
+ if (node != NULL) {
+ row = e_tree_table_adapter_row_of_node (etta, node);
+ count = e_table_model_row_count (E_TABLE_MODEL (etta));
+ }
+
+ first_message_selected = row <= 0;
+ last_message_selected = row < 0 || row + 1 >= count;
+ }
+
+ action_name = "mail-add-sender";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-archive";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-check-for-junk";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-color-assign";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-color-unset";
+ sensitive = any_messages_selected && selection_has_color;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-copy";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-create-menu";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ /* If a single message is selected, let the user hit delete to
+ * advance the cursor even if the message is already deleted. */
+ action_name = "mail-delete";
+ sensitive =
+ (single_message_selected ||
+ selection_has_undeleted_messages) &&
+ (state & E_MAIL_READER_FOLDER_IS_VTRASH) == 0;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-add-note";
+ sensitive = single_message_selected && !selection_has_mail_note;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-edit-note";
+ sensitive = single_message_selected && selection_has_mail_note;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-delete-note";
+ sensitive = single_message_selected && selection_has_mail_note;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-filters-apply";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-filter-rule-for-mailing-list";
+ sensitive = single_message_selected && selection_is_mailing_list;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-find";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-flag-clear";
+ sensitive = enable_flag_clear;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-flag-completed";
+ sensitive = enable_flag_completed;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-flag-for-followup";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward";
+ sensitive = have_enabled_account && any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-attached";
+ sensitive = have_enabled_account && any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-attached-full";
+ sensitive = have_enabled_account && any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-as-menu";
+ sensitive = have_enabled_account && any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-inline";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-inline-full";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-quoted";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-forward-quoted-full";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-goto-menu";
+ sensitive = TRUE;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-load-images";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-as-menu";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-ignore-thread-sub";
+ sensitive = selection_has_notignore_thread_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-mark-ignore-thread-whole";
+ sensitive = selection_has_notignore_thread_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-mark-important";
+ sensitive = selection_has_unimportant_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-junk";
+ sensitive = selection_has_not_junk_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-notjunk";
+ sensitive = selection_has_junk_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-read";
+ sensitive = selection_has_unread_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-unignore-thread-sub";
+ sensitive = selection_has_ignore_thread_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-mark-unignore-thread-whole";
+ sensitive = selection_has_ignore_thread_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+ gtk_action_set_visible (action, sensitive);
+
+ action_name = "mail-mark-unimportant";
+ sensitive = selection_has_important_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-mark-unread";
+ sensitive = selection_has_read_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-message-edit";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-message-new";
+ sensitive = have_enabled_account;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-message-open";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-move";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-next";
+ sensitive = any_messages_selected && !last_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-next-important";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-next-thread";
+ sensitive = single_message_selected && !last_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-next-unread";
+ sensitive = TRUE;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-previous";
+ sensitive = any_messages_selected && !first_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-previous-important";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-previous-unread";
+ sensitive = TRUE;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-previous-thread";
+ sensitive = any_messages_selected && !first_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-print";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-print-preview";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-redirect";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-remove-attachments";
+ sensitive = any_messages_selected && selection_has_attachment_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-remove-duplicates";
+ sensitive = multiple_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-reply-all";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-reply-alternative";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-reply-group";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-reply-group-menu";
+ sensitive = have_enabled_account && any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-reply-list";
+ sensitive = have_enabled_account && single_message_selected &&
+ selection_is_mailing_list;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-reply-sender";
+ sensitive = have_enabled_account && single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-save-as";
+ sensitive = any_messages_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-show-source";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-undelete";
+ sensitive = selection_has_deleted_messages;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-zoom-100";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-zoom-in";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action_name = "mail-zoom-out";
+ sensitive = single_message_selected;
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_sensitive (action, sensitive);
+
+ action = e_mail_reader_get_action (reader, "mail-search-web");
+ gtk_action_set_sensitive (action, single_message_selected &&
+ mail_display && e_web_view_has_selection (E_WEB_VIEW (mail_display)));
+
+ mail_reader_update_labels_menu (reader);
+}
+
+static gboolean
+mail_reader_close_on_delete_or_junk (EMailReader *reader)
+{
+ return FALSE;
+}
+
+static void
+mail_reader_init_charset_actions (EMailReader *reader,
+ GtkActionGroup *action_group)
+{
+ GtkRadioAction *default_action;
+ GSList *radio_group;
+
+ radio_group = e_charset_add_radio_actions (
+ action_group, "mail-charset-", NULL,
+ G_CALLBACK (action_mail_charset_cb), reader);
+
+ /* XXX Add a tooltip! */
+ default_action = gtk_radio_action_new (
+ "mail-charset-default", _("Default"), NULL, NULL, -1);
+
+ gtk_radio_action_set_group (default_action, radio_group);
+
+ g_signal_connect (
+ default_action, "changed",
+ G_CALLBACK (action_mail_charset_cb), reader);
+
+ gtk_action_group_add_action (
+ action_group, GTK_ACTION (default_action));
+
+ gtk_radio_action_set_current_value (default_action, -1);
+}
+
+static void
+e_mail_reader_default_init (EMailReaderInterface *iface)
+{
+ quark_private = g_quark_from_static_string ("e-mail-reader-private");
+
+ iface->get_alert_sink = mail_reader_get_alert_sink;
+ iface->get_selected_uids = mail_reader_get_selected_uids;
+ iface->get_selected_uids_with_collapsed_threads = mail_reader_get_selected_uids_with_collapsed_threads;
+ iface->ref_folder = mail_reader_ref_folder;
+ iface->set_folder = mail_reader_set_folder;
+ iface->set_message = mail_reader_set_message;
+ iface->open_selected_mail = e_mail_reader_open_selected;
+ iface->folder_loaded = mail_reader_folder_loaded;
+ iface->message_loaded = mail_reader_message_loaded;
+ iface->message_seen = mail_reader_message_seen;
+ iface->show_search_bar = mail_reader_show_search_bar;
+ iface->update_actions = mail_reader_update_actions;
+ iface->close_on_delete_or_junk = mail_reader_close_on_delete_or_junk;
+ iface->reload = mail_reader_reload;
+ iface->remove_ui = mail_reader_remove_ui;
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_enum (
+ "forward-style",
+ "Forward Style",
+ "How to forward messages",
+ E_TYPE_MAIL_FORWARD_STYLE,
+ E_MAIL_FORWARD_STYLE_ATTACHED,
+ G_PARAM_READWRITE));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_boolean (
+ "group-by-threads",
+ "Group by Threads",
+ "Whether to group messages by threads",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_enum (
+ "reply-style",
+ "Reply Style",
+ "How to reply to messages",
+ E_TYPE_MAIL_REPLY_STYLE,
+ E_MAIL_REPLY_STYLE_QUOTED,
+ G_PARAM_READWRITE));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_boolean (
+ "mark-seen-always",
+ "Mark Seen Always",
+ "Whether to mark unread message seen even after folder change",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_interface_install_property (
+ iface,
+ g_param_spec_boolean (
+ "delete-selects-previous",
+ "Delete Selects Previous",
+ "Whether go to the previous message after message deletion",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ signals[CHANGED] = g_signal_new (
+ "changed",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[COMPOSER_CREATED] = g_signal_new (
+ "composer-created",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EMailReaderInterface, composer_created),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 2,
+ E_TYPE_MSG_COMPOSER,
+ CAMEL_TYPE_MIME_MESSAGE);
+
+ signals[FOLDER_LOADED] = g_signal_new (
+ "folder-loaded",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (EMailReaderInterface, folder_loaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[MESSAGE_LOADED] = g_signal_new (
+ "message-loaded",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EMailReaderInterface, message_loaded),
+ NULL, NULL,
+ e_marshal_VOID__STRING_OBJECT,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ CAMEL_TYPE_MIME_MESSAGE);
+
+ signals[MESSAGE_SEEN] = g_signal_new (
+ "message-seen",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (EMailReaderInterface, message_seen),
+ NULL, NULL,
+ e_marshal_VOID__STRING_OBJECT,
+ G_TYPE_NONE, 2,
+ G_TYPE_STRING,
+ CAMEL_TYPE_MIME_MESSAGE);
+
+ signals[SHOW_SEARCH_BAR] = g_signal_new (
+ "show-search-bar",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMailReaderInterface, show_search_bar),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals[UPDATE_ACTIONS] = g_signal_new (
+ "update-actions",
+ G_OBJECT_CLASS_TYPE (iface),
+ G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET (EMailReaderInterface, update_actions),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__UINT,
+ G_TYPE_NONE, 1,
+ G_TYPE_UINT);
+}
+
+void
+e_mail_reader_init (EMailReader *reader,
+ gboolean init_actions,
+ gboolean connect_signals)
+{
+ GtkActionGroup *action_group;
+ GtkWidget *message_list;
+ GtkAction *action;
+ const gchar *action_name;
+ EMailDisplay *display;
+ EMenuToolAction *menu_tool_action;
+ GSettings *settings;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ message_list = e_mail_reader_get_message_list (reader);
+ display = e_mail_reader_get_mail_display (reader);
+
+ /* Initialize a private struct. */
+ g_object_set_qdata_full (
+ G_OBJECT (reader), quark_private,
+ g_slice_new0 (EMailReaderPrivate),
+ (GDestroyNotify) mail_reader_private_free);
+
+ e_binding_bind_property (
+ reader, "group-by-threads",
+ message_list, "group-by-threads",
+ G_BINDING_SYNC_CREATE);
+
+ if (!init_actions)
+ goto connect_signals;
+
+ /* Add the "standard" EMailReader actions. */
+
+ action_group = e_mail_reader_get_action_group (
+ reader, E_MAIL_READER_ACTION_GROUP_STANDARD);
+
+ gtk_action_group_add_actions (
+ action_group, mail_reader_entries,
+ G_N_ELEMENTS (mail_reader_entries), reader);
+ e_action_group_add_popup_actions (
+ action_group, mail_reader_popup_entries,
+ G_N_ELEMENTS (mail_reader_popup_entries));
+ gtk_action_group_add_toggle_actions (
+ action_group, mail_reader_toggle_entries,
+ G_N_ELEMENTS (mail_reader_toggle_entries), reader);
+
+ mail_reader_init_charset_actions (reader, action_group);
+
+
+ /* The "mail-forward" action is special: it uses a GtkMenuToolButton
+ * for its toolbar item type. So we have to create it separately. */
+
+ menu_tool_action = e_menu_tool_action_new (
+ "toolbar-mail-forward", _("_Forward"),
+ _("Forward the selected message to someone"));
+
+ gtk_action_set_icon_name (GTK_ACTION (menu_tool_action), "mail-forward");
+ gtk_action_set_visible (GTK_ACTION (menu_tool_action), !e_util_get_use_header_bar ());
+
+ e_binding_bind_property (
+ e_mail_reader_get_action (reader, "mail-forward"), "sensitive",
+ menu_tool_action, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_signal_connect (
+ menu_tool_action, "activate",
+ G_CALLBACK (action_mail_forward_cb), reader);
+
+ gtk_action_group_add_action_with_accel (
+ action_group, GTK_ACTION (menu_tool_action), "<Control>f");
+
+ /* Likewise the "mail-reply-group" action. */
+
+ menu_tool_action = e_menu_tool_action_new (
+ /* Translators: "Group Reply" will reply either to a mailing list
+ * (if possible and if that configuration option is enabled), or else
+ * it will reply to all. The word "Group" was chosen because it covers
+ * either of those, without too strongly implying one or the other. */
+ "toolbar-mail-reply-group", _("Group Reply"),
+ _("Reply to the mailing list, or to all recipients"));
+
+ gtk_action_set_icon_name (GTK_ACTION (menu_tool_action), "mail-reply-all");
+ gtk_action_set_visible (GTK_ACTION (menu_tool_action), !e_util_get_use_header_bar ());
+
+ e_binding_bind_property (
+ e_mail_reader_get_action (reader, "mail-reply-group"), "sensitive",
+ menu_tool_action, "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ g_signal_connect (
+ menu_tool_action, "activate",
+ G_CALLBACK (action_mail_reply_group_cb), reader);
+
+ gtk_action_group_add_action_with_accel (
+ action_group, GTK_ACTION (menu_tool_action), "<Control>g");
+
+ /* Add EMailReader actions for Search Folders. The action group
+ * should be made invisible if Search Folders are disabled. */
+
+ action_group = e_mail_reader_get_action_group (
+ reader, E_MAIL_READER_ACTION_GROUP_SEARCH_FOLDERS);
+
+ gtk_action_group_add_actions (
+ action_group, mail_reader_search_folder_entries,
+ G_N_ELEMENTS (mail_reader_search_folder_entries), reader);
+
+ display = e_mail_reader_get_mail_display (reader);
+
+ /* Bind GObject properties to GSettings keys. */
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ action_name = "mail-caret-mode";
+ action = e_mail_reader_get_action (reader, action_name);
+ g_settings_bind (
+ settings, "caret-mode",
+ action, "active", G_SETTINGS_BIND_DEFAULT);
+
+ action_name = "mail-show-all-headers";
+ action = e_mail_reader_get_action (reader, action_name);
+ g_settings_bind (
+ settings, "show-all-headers",
+ action, "active", G_SETTINGS_BIND_DEFAULT);
+
+ /* Mode change when viewing message source is ignored. */
+ if (e_mail_display_get_mode (display) == E_MAIL_FORMATTER_MODE_SOURCE ||
+ e_mail_display_get_mode (display) == E_MAIL_FORMATTER_MODE_RAW) {
+ gtk_action_set_sensitive (action, FALSE);
+ gtk_action_set_visible (action, FALSE);
+ }
+
+ g_object_unref (settings);
+
+ /* Fine tuning. */
+
+ action_name = "mail-delete";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_short_label (action, _("Delete"));
+
+ action_name = "toolbar-mail-forward";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_is_important (action, TRUE);
+
+ action_name = "toolbar-mail-reply-group";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_is_important (action, TRUE);
+
+ action_name = "mail-next";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_short_label (action, _("Next"));
+
+ action_name = "mail-previous";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_short_label (action, _("Previous"));
+
+ action_name = "mail-reply-all";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_is_important (action, TRUE);
+
+ action_name = "mail-reply-sender";
+ action = e_mail_reader_get_action (reader, action_name);
+ gtk_action_set_is_important (action, TRUE);
+ gtk_action_set_short_label (action, _("Reply"));
+
+ action_name = "add-to-address-book";
+ action = e_mail_display_get_action (display, action_name);
+ g_signal_connect (
+ action, "activate",
+ G_CALLBACK (action_add_to_address_book_cb), reader);
+
+ action_name = "send-reply";
+ action = e_mail_display_get_action (display, action_name);
+ g_signal_connect (
+ action, "activate",
+ G_CALLBACK (action_mail_reply_recipient_cb), reader);
+
+ action_name = "search-folder-recipient";
+ action = e_mail_display_get_action (display, action_name);
+ g_signal_connect (
+ action, "activate",
+ G_CALLBACK (action_search_folder_recipient_cb), reader);
+
+ action_name = "search-folder-sender";
+ action = e_mail_display_get_action (display, action_name);
+ g_signal_connect (
+ action, "activate",
+ G_CALLBACK (action_search_folder_sender_cb), reader);
+
+#ifndef G_OS_WIN32
+ /* Lockdown integration. */
+
+ settings = e_util_ref_settings ("org.gnome.desktop.lockdown");
+
+ action_name = "mail-print";
+ action = e_mail_reader_get_action (reader, action_name);
+ g_settings_bind (
+ settings, "disable-printing",
+ action, "visible",
+ G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY |
+ G_SETTINGS_BIND_INVERT_BOOLEAN);
+
+ action_name = "mail-print-preview";
+ action = e_mail_reader_get_action (reader, action_name);
+ g_settings_bind (
+ settings, "disable-printing",
+ action, "visible",
+ G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY |
+ G_SETTINGS_BIND_INVERT_BOOLEAN);
+
+ action_name = "mail-save-as";
+ action = e_mail_reader_get_action (reader, action_name);
+ g_settings_bind (
+ settings, "disable-save-to-disk",
+ action, "visible",
+ G_SETTINGS_BIND_GET |
+ G_SETTINGS_BIND_NO_SENSITIVITY |
+ G_SETTINGS_BIND_INVERT_BOOLEAN);
+
+ g_object_unref (settings);
+#endif
+
+ /* Bind properties. */
+
+ action_name = "mail-caret-mode";
+ action = e_mail_reader_get_action (reader, action_name);
+
+ e_binding_bind_property (
+ action, "active",
+ display, "caret-mode",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+connect_signals:
+
+ if (!connect_signals)
+ return;
+
+ /* Connect signals. */
+ g_signal_connect_swapped (
+ display, "key-press-event",
+ G_CALLBACK (mail_reader_key_press_event_cb), reader);
+
+ g_signal_connect_swapped (
+ display, "load-changed",
+ G_CALLBACK (mail_reader_load_changed_cb), reader);
+
+ g_signal_connect_swapped (
+ display, "remote-content-clicked",
+ G_CALLBACK (mail_reader_remote_content_clicked_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "message-selected",
+ G_CALLBACK (mail_reader_message_selected_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "update-actions",
+ G_CALLBACK (mail_reader_message_list_suggest_update_actions_cb), reader);
+
+ /* re-schedule mark-as-seen,... */
+ g_signal_connect_swapped (
+ message_list, "cursor-change",
+ G_CALLBACK (mail_reader_message_cursor_change_cb), reader);
+
+ /* but do not mark-as-seen if... */
+ g_signal_connect_swapped (
+ message_list, "tree-drag-begin",
+ G_CALLBACK (discard_timeout_mark_seen_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "tree-drag-end",
+ G_CALLBACK (discard_timeout_mark_seen_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "right-click",
+ G_CALLBACK (discard_timeout_mark_seen_cb), reader);
+
+ g_signal_connect_swapped (
+ e_mail_reader_get_preview_pane (reader), "notify::visible",
+ G_CALLBACK (mail_reader_preview_pane_visible_changed_cb), reader);
+
+ g_signal_connect_after (
+ message_list, "message-list-built",
+ G_CALLBACK (mail_reader_message_list_built_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "double-click",
+ G_CALLBACK (mail_reader_double_click_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "key-press",
+ G_CALLBACK (mail_reader_key_press_cb), reader);
+
+ g_signal_connect_swapped (
+ message_list, "selection-change",
+ G_CALLBACK (e_mail_reader_changed), reader);
+}
+
+static void
+mail_reader_ongoing_operation_destroyed (gpointer user_data,
+ GObject *cancellable)
+{
+ EMailReader *reader = user_data;
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ priv->ongoing_operations = g_slist_remove (priv->ongoing_operations, cancellable);
+}
+
+void
+e_mail_reader_dispose (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ EMailDisplay *mail_display;
+ GtkWidget *message_list;
+ GSList *ongoing_operations, *link;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (priv->message_selected_timeout_id > 0) {
+ g_source_remove (priv->message_selected_timeout_id);
+ priv->message_selected_timeout_id = 0;
+ }
+
+ if (priv->retrieving_message)
+ g_cancellable_cancel (priv->retrieving_message);
+
+ ongoing_operations = g_slist_copy_deep (priv->ongoing_operations, (GCopyFunc) g_object_ref, NULL);
+ g_slist_free (priv->ongoing_operations);
+ priv->ongoing_operations = NULL;
+
+ for (link = ongoing_operations; link; link = g_slist_next (link)) {
+ GCancellable *cancellable = link->data;
+
+ g_object_weak_unref (G_OBJECT (cancellable), mail_reader_ongoing_operation_destroyed, reader);
+
+ g_cancellable_cancel (cancellable);
+ }
+
+ g_slist_free_full (ongoing_operations, g_object_unref);
+
+ mail_display = e_mail_reader_get_mail_display (reader);
+ if (mail_display)
+ g_signal_handlers_disconnect_by_data (mail_display, reader);
+
+ message_list = e_mail_reader_get_message_list (reader);
+ if (message_list)
+ g_signal_handlers_disconnect_by_data (message_list, reader);
+}
+
+void
+e_mail_reader_changed (EMailReader *reader)
+{
+ MessageList *message_list;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ g_signal_emit (reader, signals[CHANGED], 0);
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+
+ if (!message_list || message_list_selected_count (message_list) != 1)
+ mail_reader_remove_followup_alert (reader);
+}
+
+guint32
+e_mail_reader_check_state (EMailReader *reader)
+{
+ EShell *shell;
+ GPtrArray *uids;
+ CamelFolder *folder;
+ CamelStore *store = NULL;
+ EMailBackend *backend;
+ ESourceRegistry *registry;
+ EMailSession *mail_session;
+ EMailAccountStore *account_store;
+ const gchar *tag;
+ gboolean can_clear_flags = FALSE;
+ gboolean can_flag_completed = FALSE;
+ gboolean can_flag_for_followup = FALSE;
+ gboolean has_attachments = FALSE;
+ gboolean has_deleted = FALSE;
+ gboolean has_ignore_thread = FALSE;
+ gboolean has_notignore_thread = FALSE;
+ gboolean has_important = FALSE;
+ gboolean has_junk = FALSE;
+ gboolean has_not_junk = FALSE;
+ gboolean has_read = FALSE;
+ gboolean has_undeleted = FALSE;
+ gboolean has_unimportant = FALSE;
+ gboolean has_unread = FALSE;
+ gboolean has_mail_note = FALSE;
+ gboolean has_color = FALSE;
+ gboolean have_enabled_account = FALSE;
+ gboolean drafts_or_outbox = FALSE;
+ gboolean is_mailing_list;
+ gboolean is_junk_folder = FALSE;
+ gboolean is_vtrash_folder = FALSE;
+ guint32 state = 0;
+ guint ii;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);
+
+ backend = e_mail_reader_get_backend (reader);
+ shell = e_shell_backend_get_shell (E_SHELL_BACKEND (backend));
+ registry = e_shell_get_registry (shell);
+ mail_session = e_mail_backend_get_session (backend);
+ account_store = e_mail_ui_session_get_account_store (
+ E_MAIL_UI_SESSION (mail_session));
+
+ folder = e_mail_reader_ref_folder (reader);
+ uids = e_mail_reader_get_selected_uids_with_collapsed_threads (reader);
+
+ if (folder != NULL) {
+ guint32 folder_flags;
+
+ store = camel_folder_get_parent_store (folder);
+ folder_flags = camel_folder_get_flags (folder);
+ is_junk_folder = (folder_flags & CAMEL_FOLDER_IS_JUNK) != 0;
+ is_vtrash_folder = (camel_store_get_flags (store) & CAMEL_STORE_VTRASH) != 0 && (folder_flags & CAMEL_FOLDER_IS_TRASH) != 0;
+ drafts_or_outbox =
+ em_utils_folder_is_drafts (registry, folder) ||
+ em_utils_folder_is_outbox (registry, folder);
+ }
+
+ /* Initialize this flag based on whether there are any
+ * messages selected. We will update it in the loop. */
+ is_mailing_list = (uids->len > 0);
+
+ for (ii = 0; ii < uids->len; ii++) {
+ CamelMessageInfo *info;
+ const gchar *string;
+ guint32 flags;
+
+ info = camel_folder_get_message_info (
+ folder, uids->pdata[ii]);
+ if (info == NULL)
+ continue;
+
+ flags = camel_message_info_get_flags (info);
+
+ if (flags & CAMEL_MESSAGE_SEEN)
+ has_read = TRUE;
+ else
+ has_unread = TRUE;
+
+ if (flags & CAMEL_MESSAGE_ATTACHMENTS)
+ has_attachments = TRUE;
+
+ if (drafts_or_outbox) {
+ has_junk = FALSE;
+ has_not_junk = FALSE;
+ } else {
+ guint32 bitmask;
+
+ /* XXX Strictly speaking, this logic is correct.
+ * Problem is there's nothing in the message
+ * list that indicates whether a message is
+ * already marked "Not Junk". So the user may
+ * think the "Not Junk" button is enabling and
+ * disabling itself randomly as he reads mail. */
+
+ if (flags & CAMEL_MESSAGE_JUNK)
+ has_junk = TRUE;
+ if (flags & CAMEL_MESSAGE_NOTJUNK)
+ has_not_junk = TRUE;
+
+ bitmask = CAMEL_MESSAGE_JUNK | CAMEL_MESSAGE_NOTJUNK;
+
+ /* If neither junk flag is set, the
+ * message can be marked either way. */
+ if ((flags & bitmask) == 0) {
+ has_junk = TRUE;
+ has_not_junk = TRUE;
+ }
+ }
+
+ if (flags & CAMEL_MESSAGE_DELETED)
+ has_deleted = TRUE;
+ else
+ has_undeleted = TRUE;
+
+ if (flags & CAMEL_MESSAGE_FLAGGED)
+ has_important = TRUE;
+ else
+ has_unimportant = TRUE;
+
+ tag = camel_message_info_get_user_tag (info, "follow-up");
+ if (tag != NULL && *tag != '\0') {
+ can_clear_flags = TRUE;
+ tag = camel_message_info_get_user_tag (
+ info, "completed-on");
+ if (tag == NULL || *tag == '\0')
+ can_flag_completed = TRUE;
+ } else
+ can_flag_for_followup = TRUE;
+
+ string = camel_message_info_get_mlist (info);
+ is_mailing_list &= (string != NULL && *string != '\0');
+
+ has_ignore_thread = has_ignore_thread || camel_message_info_get_user_flag (info, "ignore-thread");
+ has_notignore_thread = has_notignore_thread || !camel_message_info_get_user_flag (info, "ignore-thread");
+ has_mail_note = has_mail_note || camel_message_info_get_user_flag (info, E_MAIL_NOTES_USER_FLAG);
+ has_color = has_color || camel_message_info_get_user_tag (info, "color") != NULL;
+
+ g_clear_object (&info);
+ }
+
+ have_enabled_account =
+ e_mail_account_store_have_enabled_service (
+ account_store, CAMEL_TYPE_STORE);
+
+ if (have_enabled_account)
+ state |= E_MAIL_READER_HAVE_ENABLED_ACCOUNT;
+ if (uids->len == 1)
+ state |= E_MAIL_READER_SELECTION_SINGLE;
+ if (uids->len > 1)
+ state |= E_MAIL_READER_SELECTION_MULTIPLE;
+ if (!drafts_or_outbox && uids->len == 1)
+ state |= E_MAIL_READER_SELECTION_CAN_ADD_SENDER;
+ if (can_clear_flags)
+ state |= E_MAIL_READER_SELECTION_FLAG_CLEAR;
+ if (can_flag_completed)
+ state |= E_MAIL_READER_SELECTION_FLAG_COMPLETED;
+ if (can_flag_for_followup)
+ state |= E_MAIL_READER_SELECTION_FLAG_FOLLOWUP;
+ if (has_attachments)
+ state |= E_MAIL_READER_SELECTION_HAS_ATTACHMENTS;
+ if (has_deleted)
+ state |= E_MAIL_READER_SELECTION_HAS_DELETED;
+ if (has_ignore_thread)
+ state |= E_MAIL_READER_SELECTION_HAS_IGNORE_THREAD;
+ if (has_notignore_thread)
+ state |= E_MAIL_READER_SELECTION_HAS_NOTIGNORE_THREAD;
+ if (has_important)
+ state |= E_MAIL_READER_SELECTION_HAS_IMPORTANT;
+ if (has_junk)
+ state |= E_MAIL_READER_SELECTION_HAS_JUNK;
+ if (has_not_junk)
+ state |= E_MAIL_READER_SELECTION_HAS_NOT_JUNK;
+ if (has_read)
+ state |= E_MAIL_READER_SELECTION_HAS_READ;
+ if (has_undeleted)
+ state |= E_MAIL_READER_SELECTION_HAS_UNDELETED;
+ if (has_unimportant)
+ state |= E_MAIL_READER_SELECTION_HAS_UNIMPORTANT;
+ if (has_unread)
+ state |= E_MAIL_READER_SELECTION_HAS_UNREAD;
+ if (is_mailing_list)
+ state |= E_MAIL_READER_SELECTION_IS_MAILING_LIST;
+ if (is_junk_folder)
+ state |= E_MAIL_READER_FOLDER_IS_JUNK;
+ if (is_vtrash_folder)
+ state |= E_MAIL_READER_FOLDER_IS_VTRASH;
+ if (has_mail_note)
+ state |= E_MAIL_READER_SELECTION_HAS_MAIL_NOTE;
+ if (has_color)
+ state |= E_MAIL_READER_SELECTION_HAS_COLOR;
+
+ if (!(state & E_MAIL_READER_SELECTION_SINGLE)) {
+ GPtrArray *real_selected_uids;
+
+ real_selected_uids = e_mail_reader_get_selected_uids (reader);
+
+ if (real_selected_uids && real_selected_uids->len == 1) {
+ state |= E_MAIL_READER_SELECTION_SINGLE;
+ }
+
+ if (real_selected_uids)
+ g_ptr_array_unref (real_selected_uids);
+ }
+
+ g_clear_object (&folder);
+ g_ptr_array_unref (uids);
+
+ return state;
+}
+
+EActivity *
+e_mail_reader_new_activity (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ EActivity *activity;
+ EMailBackend *backend;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ activity = e_activity_new ();
+
+ alert_sink = e_mail_reader_get_alert_sink (reader);
+ e_activity_set_alert_sink (activity, alert_sink);
+
+ cancellable = camel_operation_new ();
+
+ priv->ongoing_operations = g_slist_prepend (priv->ongoing_operations, cancellable);
+ g_object_weak_ref (G_OBJECT (cancellable), mail_reader_ongoing_operation_destroyed, reader);
+
+ e_activity_set_cancellable (activity, cancellable);
+ g_object_unref (cancellable);
+
+ backend = e_mail_reader_get_backend (reader);
+ e_shell_backend_add_activity (E_SHELL_BACKEND (backend), activity);
+
+ return activity;
+}
+
+void
+e_mail_reader_update_actions (EMailReader *reader,
+ guint32 state)
+{
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ g_signal_emit (reader, signals[UPDATE_ACTIONS], 0, state);
+}
+
+GtkAction *
+e_mail_reader_get_action (EMailReader *reader,
+ const gchar *action_name)
+{
+ GtkAction *action = NULL;
+ gint ii;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+ g_return_val_if_fail (action_name != NULL, NULL);
+
+ for (ii = 0; ii < E_MAIL_READER_NUM_ACTION_GROUPS; ii++) {
+ GtkActionGroup *group;
+
+ group = e_mail_reader_get_action_group (reader, ii);
+ action = gtk_action_group_get_action (group, action_name);
+
+ if (action != NULL)
+ break;
+ }
+
+ if (action == NULL)
+ g_critical (
+ "%s: action '%s' not found", G_STRFUNC, action_name);
+
+ return action;
+}
+
+GtkActionGroup *
+e_mail_reader_get_action_group (EMailReader *reader,
+ EMailReaderActionGroup group)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_action_group != NULL, NULL);
+
+ return iface->get_action_group (reader, group);
+}
+
+EAlertSink *
+e_mail_reader_get_alert_sink (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_alert_sink != NULL, NULL);
+
+ return iface->get_alert_sink (reader);
+}
+
+EMailBackend *
+e_mail_reader_get_backend (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_backend != NULL, NULL);
+
+ return iface->get_backend (reader);
+}
+
+EMailDisplay *
+e_mail_reader_get_mail_display (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_mail_display != NULL, NULL);
+
+ return iface->get_mail_display (reader);
+}
+
+gboolean
+e_mail_reader_get_hide_deleted (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_hide_deleted != NULL, FALSE);
+
+ return iface->get_hide_deleted (reader);
+}
+
+GtkWidget *
+e_mail_reader_get_message_list (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_message_list != NULL, NULL);
+
+ return iface->get_message_list (reader);
+}
+
+static void
+e_mail_reader_popup_menu_deactivate_cb (GtkMenu *popup_menu,
+ EMailReader *reader)
+{
+ g_return_if_fail (GTK_IS_MENU (popup_menu));
+
+ g_signal_handlers_disconnect_by_func (popup_menu, e_mail_reader_popup_menu_deactivate_cb, reader);
+ gtk_menu_detach (popup_menu);
+}
+
+GtkMenu *
+e_mail_reader_get_popup_menu (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+ GtkMenu *menu;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_popup_menu != NULL, NULL);
+
+ menu = iface->get_popup_menu (reader);
+ if (!gtk_menu_get_attach_widget (GTK_MENU (menu))) {
+ gtk_menu_attach_to_widget (GTK_MENU (menu),
+ GTK_WIDGET (reader),
+ NULL);
+ g_signal_connect (
+ menu, "deactivate",
+ G_CALLBACK (e_mail_reader_popup_menu_deactivate_cb), reader);
+ }
+
+ return menu;
+}
+
+EPreviewPane *
+e_mail_reader_get_preview_pane (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_preview_pane != NULL, NULL);
+
+ return iface->get_preview_pane (reader);
+}
+
+GPtrArray *
+e_mail_reader_get_selected_uids (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_selected_uids != NULL, NULL);
+
+ return iface->get_selected_uids (reader);
+}
+
+GPtrArray *
+e_mail_reader_get_selected_uids_with_collapsed_threads (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_selected_uids_with_collapsed_threads != NULL, NULL);
+
+ return iface->get_selected_uids_with_collapsed_threads (reader);
+}
+
+GtkWindow *
+e_mail_reader_get_window (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->get_window != NULL, NULL);
+
+ return iface->get_window (reader);
+}
+
+gboolean
+e_mail_reader_close_on_delete_or_junk (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+
+ return iface->close_on_delete_or_junk != NULL &&
+ iface->close_on_delete_or_junk (reader);
+}
+
+CamelFolder *
+e_mail_reader_ref_folder (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), NULL);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->ref_folder != NULL, NULL);
+
+ return iface->ref_folder (reader);
+}
+
+void
+e_mail_reader_set_folder (EMailReader *reader,
+ CamelFolder *folder)
+{
+ EMailReaderInterface *iface;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_if_fail (iface->set_folder != NULL);
+
+ iface->set_folder (reader, folder);
+}
+
+void
+e_mail_reader_set_message (EMailReader *reader,
+ const gchar *message_uid)
+{
+ EMailReaderInterface *iface;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_if_fail (iface->set_message != NULL);
+
+ iface->set_message (reader, message_uid);
+}
+
+guint
+e_mail_reader_open_selected_mail (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_val_if_fail (iface->open_selected_mail != NULL, 0);
+
+ return iface->open_selected_mail (reader);
+}
+
+EMailForwardStyle
+e_mail_reader_get_forward_style (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ return priv->forward_style;
+}
+
+void
+e_mail_reader_set_forward_style (EMailReader *reader,
+ EMailForwardStyle style)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (priv->forward_style == style)
+ return;
+
+ priv->forward_style = style;
+
+ g_object_notify (G_OBJECT (reader), "forward-style");
+}
+
+gboolean
+e_mail_reader_get_group_by_threads (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ return priv->group_by_threads;
+}
+
+void
+e_mail_reader_set_group_by_threads (EMailReader *reader,
+ gboolean group_by_threads)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (priv->group_by_threads == group_by_threads)
+ return;
+
+ priv->group_by_threads = group_by_threads;
+
+ g_object_notify (G_OBJECT (reader), "group-by-threads");
+}
+
+EMailReplyStyle
+e_mail_reader_get_reply_style (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), 0);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ return priv->reply_style;
+}
+
+void
+e_mail_reader_set_reply_style (EMailReader *reader,
+ EMailReplyStyle style)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (priv->reply_style == style)
+ return;
+
+ priv->reply_style = style;
+
+ g_object_notify (G_OBJECT (reader), "reply-style");
+}
+
+gboolean
+e_mail_reader_get_mark_seen_always (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ return priv->mark_seen_always;
+}
+
+void
+e_mail_reader_set_mark_seen_always (EMailReader *reader,
+ gboolean mark_seen_always)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (priv->mark_seen_always == mark_seen_always)
+ return;
+
+ priv->mark_seen_always = mark_seen_always;
+
+ g_object_notify (G_OBJECT (reader), "mark-seen-always");
+}
+
+gboolean
+e_mail_reader_get_delete_selects_previous (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_val_if_fail (E_IS_MAIL_READER (reader), FALSE);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ return priv->delete_selects_previous;
+}
+
+void
+e_mail_reader_set_delete_selects_previous (EMailReader *reader,
+ gboolean delete_selects_previous)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+
+ if (priv->delete_selects_previous == delete_selects_previous)
+ return;
+
+ priv->delete_selects_previous = delete_selects_previous;
+
+ g_object_notify (G_OBJECT (reader), "delete-selects-previous");
+}
+
+void
+e_mail_reader_create_charset_menu (EMailReader *reader,
+ GtkUIManager *ui_manager,
+ guint merge_id)
+{
+ GtkAction *action;
+ const gchar *action_name;
+ const gchar *path;
+ GSList *list;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+ g_return_if_fail (GTK_IS_UI_MANAGER (ui_manager));
+
+ action_name = "mail-charset-default";
+ action = e_mail_reader_get_action (reader, action_name);
+ g_return_if_fail (action != NULL);
+
+ list = gtk_radio_action_get_group (GTK_RADIO_ACTION (action));
+ list = g_slist_copy (list);
+ list = g_slist_remove (list, action);
+ list = g_slist_sort (list, (GCompareFunc) e_action_compare_by_label);
+
+ path = "/main-menu/view-menu/mail-message-view-actions/mail-encoding-menu";
+
+ while (list != NULL) {
+ action = list->data;
+
+ gtk_ui_manager_add_ui (
+ ui_manager, merge_id, path,
+ gtk_action_get_name (action),
+ gtk_action_get_name (action),
+ GTK_UI_MANAGER_AUTO, FALSE);
+
+ list = g_slist_delete_link (list, list);
+ }
+
+ gtk_ui_manager_ensure_update (ui_manager);
+}
+
+void
+e_mail_reader_show_search_bar (EMailReader *reader)
+{
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ g_signal_emit (reader, signals[SHOW_SEARCH_BAR], 0);
+}
+
+void
+e_mail_reader_avoid_next_mark_as_seen (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+ MessageList *message_list;
+
+ g_return_if_fail (reader != NULL);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ g_return_if_fail (priv != NULL);
+
+ message_list = MESSAGE_LIST (e_mail_reader_get_message_list (reader));
+ g_return_if_fail (message_list != NULL);
+
+ priv->avoid_next_mark_as_seen = TRUE;
+}
+
+void
+e_mail_reader_unset_folder_just_selected (EMailReader *reader)
+{
+ EMailReaderPrivate *priv;
+
+ g_return_if_fail (reader != NULL);
+
+ priv = E_MAIL_READER_GET_PRIVATE (reader);
+ g_return_if_fail (priv != NULL);
+
+ priv->folder_was_just_selected = FALSE;
+}
+
+/**
+ * e_mail_reader_composer_created:
+ * @reader: an #EMailReader
+ * @composer: an #EMsgComposer
+ * @message: the source #CamelMimeMessage, or %NULL
+ *
+ * Emits an #EMailReader::composer-created signal to indicate the @composer
+ * window was created in response to a user action on @reader. Examples of
+ * such actions include replying, forwarding, and composing a new message.
+ * If applicable, the source @message (i.e. the message being replied to or
+ * forwarded) should be included.
+ **/
+void
+e_mail_reader_composer_created (EMailReader *reader,
+ EMsgComposer *composer,
+ CamelMimeMessage *message)
+{
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+ g_return_if_fail (E_IS_MSG_COMPOSER (composer));
+
+ if (message != NULL)
+ g_return_if_fail (CAMEL_IS_MIME_MESSAGE (message));
+
+ g_signal_emit (
+ reader, signals[COMPOSER_CREATED], 0, composer, message);
+}
+
+void
+e_mail_reader_reload (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_if_fail (iface->reload != NULL);
+
+ iface->reload (reader);
+}
+
+void
+e_mail_reader_remove_ui (EMailReader *reader)
+{
+ EMailReaderInterface *iface;
+
+ g_return_if_fail (E_IS_MAIL_READER (reader));
+
+ iface = E_MAIL_READER_GET_INTERFACE (reader);
+ g_return_if_fail (iface->remove_ui != NULL);
+
+ iface->remove_ui (reader);
+}
+
+/**
+ * e_mail_reader_create_reply_menu:
+ * @reader: A #EMailReader
+ *
+ * Get reply menu
+ *
+ * Returns: (transfer full): A new #GtkMenu
+ *
+ * Since: 3.46
+ **/
+GtkWidget *
+e_mail_reader_create_reply_menu (EMailReader *reader)
+{
+ GtkWindow *window;
+ GtkWidget *menu;
+ GtkAction *action;
+ GtkAccelGroup *accel_group;
+ GtkUIManager *ui_manager;
+
+ menu = gtk_menu_new ();
+
+ window = e_mail_reader_get_window (reader);
+ g_return_val_if_fail (window != NULL, menu);
+
+ if (E_IS_SHELL_WINDOW (window))
+ ui_manager = e_shell_window_get_ui_manager (E_SHELL_WINDOW (window));
+ else if (E_IS_MAIL_BROWSER (window))
+ ui_manager = e_mail_browser_get_ui_manager (E_MAIL_BROWSER (window));
+ else
+ return menu;
+
+ accel_group = gtk_ui_manager_get_accel_group (ui_manager);
+
+ action = e_mail_reader_get_action (reader, "mail-reply-all");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ action = e_mail_reader_get_action (reader, "mail-reply-list");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ action = e_mail_reader_get_action (reader, "mail-reply-alternative");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
+
+/**
+ * e_mail_reader_create_forward_menu:
+ * @reader: A #EMailReader
+ *
+ * Get forward menu
+ *
+ * Returns: (transfer full): A new #GtkMenu
+ *
+ * Since: 3.46
+ **/
+GtkWidget *
+e_mail_reader_create_forward_menu (EMailReader *reader)
+{
+ GtkWindow *window;
+ GtkWidget *menu;
+ GtkAction *action;
+ GtkAccelGroup *accel_group;
+ GtkUIManager *ui_manager;
+
+ menu = gtk_menu_new ();
+
+ window = e_mail_reader_get_window (reader);
+ g_return_val_if_fail (window != NULL, menu);
+
+ if (E_IS_SHELL_WINDOW (window))
+ ui_manager = e_shell_window_get_ui_manager (E_SHELL_WINDOW (window));
+ else if (E_IS_MAIL_BROWSER (window))
+ ui_manager = e_mail_browser_get_ui_manager (E_MAIL_BROWSER (window));
+ else
+ return menu;
+
+ accel_group = gtk_ui_manager_get_accel_group (ui_manager);
+
+ action = e_mail_reader_get_action (reader, "mail-forward-attached-full");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ action = e_mail_reader_get_action (reader, "mail-forward-inline-full");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ action = e_mail_reader_get_action (reader, "mail-forward-quoted-full");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ action = e_mail_reader_get_action (reader, "mail-redirect");
+ gtk_action_set_accel_group (action, accel_group);
+ gtk_menu_shell_append (
+ GTK_MENU_SHELL (menu),
+ gtk_action_create_menu_item (action));
+
+ gtk_widget_show_all (menu);
+
+ return menu;
+}
diff -urN a/src/modules/addressbook/e-book-shell-backend.c b/src/modules/addressbook/e-book-shell-backend.c
--- a/src/modules/addressbook/e-book-shell-backend.c 2025-06-07 14:20:57.363397606 -0700
+++ b/src/modules/addressbook/e-book-shell-backend.c 2025-06-07 14:27:24.055673052 -0700
@@ -276,14 +276,14 @@
{ "contact-new",
"contact-new",
NC_("New", "_Contact"),
- "<Shift><Control>c",
+ "<Shift><Super>c",
N_("Create a new contact"),
G_CALLBACK (action_contact_new_cb) },
{ "contact-new-list",
"stock_contact-list",
NC_("New", "Contact _List"),
- "<Shift><Control>l",
+ "<Shift><Super>l",
N_("Create a new contact list"),
G_CALLBACK (action_contact_new_cb) }
};
diff -urN a/src/modules/addressbook/e-book-shell-view-actions.c b/src/modules/addressbook/e-book-shell-view-actions.c
--- a/src/modules/addressbook/e-book-shell-view-actions.c 2025-06-07 14:20:57.363397606 -0700
+++ b/src/modules/addressbook/e-book-shell-view-actions.c 2025-06-07 14:27:24.059673092 -0700
@@ -1090,21 +1090,21 @@
{ "contact-copy",
NULL,
N_("_Copy Contact To…"),
- "<Control><Shift>y",
+ "<Super><Shift>y",
N_("Copy selected contacts to another address book"),
G_CALLBACK (action_contact_copy_cb) },
{ "contact-delete",
"edit-delete",
N_("_Delete Contact"),
- "<Control>d",
+ "<Super>d",
N_("Delete selected contacts"),
G_CALLBACK (action_contact_delete_cb) },
{ "contact-find",
"edit-find",
N_("_Find in Contact…"),
- "<Shift><Control>f",
+ "<Shift><Super>f",
N_("Search for text in the displayed contact"),
G_CALLBACK (action_contact_find_cb) },
@@ -1118,7 +1118,7 @@
{ "contact-move",
NULL,
N_("_Move Contact To…"),
- "<Control><Shift>v",
+ "<Super><Shift>v",
N_("Move selected contacts to another address book"),
G_CALLBACK (action_contact_move_cb) },
@@ -1139,7 +1139,7 @@
{ "contact-open",
NULL,
N_("_Open Contact"),
- "<Control>o",
+ "<Super>o",
N_("View the current contact"),
G_CALLBACK (action_contact_open_cb) },
@@ -1223,7 +1223,7 @@
{ "contact-preview",
NULL,
N_("Contact _Preview"),
- "<Control>m",
+ "<Super>m",
N_("Show contact preview window"),
G_CALLBACK (action_contact_preview_cb),
TRUE },
@@ -1317,7 +1317,7 @@
{ "address-book-print",
"document-print",
N_("_Print…"),
- "<Control>p",
+ "<Super>p",
N_("Print all shown contacts"),
G_CALLBACK (action_address_book_print_cb) },
diff -urN a/src/modules/calendar/e-cal-shell-backend.c b/src/modules/calendar/e-cal-shell-backend.c
--- a/src/modules/calendar/e-cal-shell-backend.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-cal-shell-backend.c 2025-06-07 14:27:24.059673092 -0700
@@ -116,7 +116,7 @@
{ "event-new",
"appointment-new",
NC_("New", "_Appointment"),
- "<Shift><Control>a",
+ "<Shift><Super>a",
N_("Create a new appointment"),
G_CALLBACK (action_event_new_cb) },
@@ -130,7 +130,7 @@
{ "event-meeting-new",
"stock_people",
NC_("New", "M_eeting"),
- "<Shift><Control>e",
+ "<Shift><Super>e",
N_("Create a new meeting request"),
G_CALLBACK (action_event_new_cb) }
};
diff -urN a/src/modules/calendar/e-cal-shell-view-actions.c b/src/modules/calendar/e-cal-shell-view-actions.c
--- a/src/modules/calendar/e-cal-shell-view-actions.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-cal-shell-view-actions.c 2025-06-07 14:27:24.059673092 -0700
@@ -1304,7 +1304,7 @@
{ "calendar-copy",
"edit-copy",
N_("_Copy…"),
- "<Control>c",
+ "<Super>c",
NULL, /* XXX Add a tooltip! */
G_CALLBACK (action_calendar_copy_cb) },
@@ -1332,14 +1332,14 @@
{ "calendar-go-today",
"go-today",
N_("Select _Today"),
- "<Control>t",
+ "<Super>t",
N_("Select today"),
G_CALLBACK (action_calendar_go_today_cb) },
{ "calendar-jump-to",
"go-jump",
N_("Select _Date"),
- "<Control>g",
+ "<Super>g",
N_("Select a specific date"),
G_CALLBACK (action_calendar_jump_to_cb) },
@@ -1367,7 +1367,7 @@
{ "calendar-purge",
NULL,
N_("Purg_e"),
- "<Control>e",
+ "<Super>e",
N_("Purge old appointments and meetings"),
G_CALLBACK (action_calendar_purge_cb) },
@@ -1395,14 +1395,14 @@
{ "calendar-search-next",
"go-next",
N_("Find _Next"),
- "<Control><Shift>n",
+ "<Super><Shift>n",
N_("Find next occurrence of the current search string"),
G_CALLBACK (action_calendar_search_next_cb) },
{ "calendar-search-prev",
"go-previous",
N_("Find _Previous"),
- "<Control><Shift>p",
+ "<Super><Shift>p",
N_("Find previous occurrence of the current search string"),
G_CALLBACK (action_calendar_search_prev_cb) },
@@ -1444,7 +1444,7 @@
{ "event-delete",
"edit-delete",
N_("_Delete Appointment"),
- "<Control>d",
+ "<Super>d",
N_("Delete selected appointments"),
G_CALLBACK (action_event_delete_cb) },
@@ -1570,7 +1570,7 @@
{ "event-open",
"document-open",
N_("_Open Appointment"),
- "<Control>o",
+ "<Super>o",
N_("View the current appointment"),
G_CALLBACK (action_event_open_cb) },
@@ -1605,7 +1605,7 @@
{ "quit-calendar",
"window-close",
N_("Quit"),
- "<Control>w",
+ "<Super>w",
NULL, /* XXX Add a tooltip! */
G_CALLBACK (quit_calendar_cb) },
@@ -1771,35 +1771,35 @@
{ "calendar-view-day",
"view-calendar-day",
N_("Day"),
- "<Control>y",
+ "<Super>y",
N_("Show one day"),
E_CAL_VIEW_KIND_DAY },
{ "calendar-view-list",
"view-calendar-list",
N_("List"),
- "<Control>l",
+ "<Super>l",
N_("Show as list"),
E_CAL_VIEW_KIND_LIST },
{ "calendar-view-month",
"view-calendar-month",
N_("Month"),
- "<Control>m",
+ "<Super>m",
N_("Show one month"),
E_CAL_VIEW_KIND_MONTH },
{ "calendar-view-week",
"view-calendar-week",
N_("Week"),
- "<Control>k",
+ "<Super>k",
N_("Show one week"),
E_CAL_VIEW_KIND_WEEK },
{ "calendar-view-workweek",
"view-calendar-workweek",
N_("Work Week"),
- "<Control>j",
+ "<Super>j",
N_("Show one work week"),
E_CAL_VIEW_KIND_WORKWEEK },
@@ -1912,7 +1912,7 @@
{ "calendar-print",
"document-print",
N_("Print…"),
- "<Control>p",
+ "<Super>p",
N_("Print this calendar"),
G_CALLBACK (action_calendar_print_cb) },
diff -urN a/src/modules/calendar/e-cal-shell-view-memopad.c b/src/modules/calendar/e-cal-shell-view-memopad.c
--- a/src/modules/calendar/e-cal-shell-view-memopad.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-cal-shell-view-memopad.c 2025-06-07 14:27:24.059673092 -0700
@@ -250,7 +250,7 @@
{ "calendar-memopad-open",
"document-open",
N_("_Open Memo"),
- "<Control>o",
+ "<Super>o",
N_("View the selected memo"),
G_CALLBACK (action_calendar_memopad_open_cb) },
diff -urN a/src/modules/calendar/e-cal-shell-view-taskpad.c b/src/modules/calendar/e-cal-shell-view-taskpad.c
--- a/src/modules/calendar/e-cal-shell-view-taskpad.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-cal-shell-view-taskpad.c 2025-06-07 14:27:24.059673092 -0700
@@ -344,7 +344,7 @@
{ "calendar-taskpad-open",
"document-open",
N_("_Open Task"),
- "<Control>o",
+ "<Super>o",
N_("View the selected task"),
G_CALLBACK (action_calendar_taskpad_open_cb) },
diff -urN a/src/modules/calendar/e-memo-shell-backend.c b/src/modules/calendar/e-memo-shell-backend.c
--- a/src/modules/calendar/e-memo-shell-backend.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-memo-shell-backend.c 2025-06-07 14:27:24.059673092 -0700
@@ -75,14 +75,14 @@
{ "memo-new",
"stock_insert-note",
NC_("New", "Mem_o"),
- "<Shift><Control>o",
+ "<Shift><Super>o",
N_("Create a new memo"),
G_CALLBACK (action_memo_new_cb) },
{ "memo-shared-new",
"stock_insert-note",
NC_("New", "_Shared Memo"),
- "<Shift><Control>u",
+ "<Shift><Super>u",
N_("Create a new shared memo"),
G_CALLBACK (action_memo_new_cb) }
};
diff -urN a/src/modules/calendar/e-memo-shell-view-actions.c b/src/modules/calendar/e-memo-shell-view-actions.c
--- a/src/modules/calendar/e-memo-shell-view-actions.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-memo-shell-view-actions.c 2025-06-07 14:27:24.059673092 -0700
@@ -575,21 +575,21 @@
{ "memo-find",
"edit-find",
N_("_Find in Memo…"),
- "<Shift><Control>f",
+ "<Shift><Super>f",
N_("Search for text in the displayed memo"),
G_CALLBACK (action_memo_find_cb) },
{ "memo-forward",
"mail-forward",
N_("_Forward as iCalendar…"),
- "<Control>f",
+ "<Super>f",
NULL, /* XXX Add a tooltip! */
G_CALLBACK (action_memo_forward_cb) },
{ "memo-list-copy",
"edit-copy",
N_("_Copy…"),
- "<Control>c",
+ "<Super>c",
NULL, /* XXX Add a tooltip! */
G_CALLBACK (action_memo_list_copy_cb) },
@@ -666,7 +666,7 @@
{ "memo-open",
"document-open",
N_("_Open Memo"),
- "<Control>o",
+ "<Super>o",
N_("View the selected memo"),
G_CALLBACK (action_memo_open_cb) },
@@ -743,7 +743,7 @@
{ "memo-preview",
NULL,
N_("Memo _Preview"),
- "<Control>m",
+ "<Super>m",
N_("Show memo preview pane"),
G_CALLBACK (action_memo_preview_cb),
TRUE }
@@ -829,7 +829,7 @@
{ "memo-list-print",
"document-print",
N_("Print…"),
- "<Control>p",
+ "<Super>p",
N_("Print the list of memos"),
G_CALLBACK (action_memo_list_print_cb) },
diff -urN a/src/modules/calendar/e-task-shell-backend.c b/src/modules/calendar/e-task-shell-backend.c
--- a/src/modules/calendar/e-task-shell-backend.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-task-shell-backend.c 2025-06-07 14:27:24.059673092 -0700
@@ -73,14 +73,14 @@
{ "task-new",
"stock_task",
NC_("New", "_Task"),
- "<Shift><Control>t",
+ "<Shift><Super>t",
N_("Create a new task"),
G_CALLBACK (action_task_new_cb) },
{ "task-assigned-new",
"stock_task-assigned-to",
NC_("New", "Assigne_d Task"),
- "<Shift><Control>i",
+ "<Shift><Super>i",
N_("Create a new assigned task"),
G_CALLBACK (action_task_new_cb) }
};
diff -urN a/src/modules/calendar/e-task-shell-view-actions.c b/src/modules/calendar/e-task-shell-view-actions.c
--- a/src/modules/calendar/e-task-shell-view-actions.c 2025-06-07 14:20:57.371397702 -0700
+++ b/src/modules/calendar/e-task-shell-view-actions.c 2025-06-07 14:27:24.063673134 -0700
@@ -701,21 +701,21 @@
{ "task-find",
"edit-find",
N_("_Find in Task…"),
- "<Shift><Control>f",
+ "<Shift><Super>f",
N_("Search for text in the displayed task"),
G_CALLBACK (action_task_find_cb) },
{ "task-forward",
"mail-forward",
N_("_Forward as iCalendar…"),
- "<Control>f",
+ "<Super>f",
NULL, /* XXX Add a tooltip! */
G_CALLBACK (action_task_forward_cb) },
{ "task-list-copy",
"edit-copy",
N_("_Copy…"),
- "<Control>c",
+ "<Super>c",
NULL, /* XXX Add a tooltip! */
G_CALLBACK (action_task_list_copy_cb) },
@@ -785,7 +785,7 @@
{ "task-mark-complete",
NULL,
N_("_Mark as Complete"),
- "<Control>k",
+ "<Super>k",
N_("Mark selected tasks as complete"),
G_CALLBACK (action_task_mark_complete_cb) },
@@ -806,7 +806,7 @@
{ "task-open",
"document-open",
N_("_Open Task"),
- "<Control>o",
+ "<Super>o",
N_("View the selected task"),
G_CALLBACK (action_task_open_cb) },
@@ -820,7 +820,7 @@
{ "task-purge",
NULL,
N_("Purg_e"),
- "<Control>e",
+ "<Super>e",
N_("Delete completed tasks"),
G_CALLBACK (action_task_purge_cb) },
@@ -909,7 +909,7 @@
{ "task-preview",
NULL,
N_("Task _Preview"),
- "<Control>m",
+ "<Super>m",
N_("Show task preview pane"),
G_CALLBACK (action_task_preview_cb),
TRUE }
@@ -1058,7 +1058,7 @@
{ "task-list-print",
"document-print",
N_("Print…"),
- "<Control>p",
+ "<Super>p",
N_("Print the list of tasks"),
G_CALLBACK (action_task_list_print_cb) },
diff -urN a/src/modules/mail/e-mail-shell-backend.c b/src/modules/mail/e-mail-shell-backend.c
--- a/src/modules/mail/e-mail-shell-backend.c 2025-06-07 14:20:57.379397797 -0700
+++ b/src/modules/mail/e-mail-shell-backend.c 2025-06-07 14:27:24.063673134 -0700
@@ -421,7 +421,7 @@
{ "mail-message-new",
"mail-message-new",
NC_("New", "_Mail Message"),
- "<Shift><Control>m",
+ "<Shift><Super>m",
N_("Compose a new mail message"),
G_CALLBACK (action_mail_message_new_cb) }
};
diff -urN a/src/modules/mail/e-mail-shell-view-actions.c b/src/modules/mail/e-mail-shell-view-actions.c
--- a/src/modules/mail/e-mail-shell-view-actions.c 2025-06-07 14:20:57.379397797 -0700
+++ b/src/modules/mail/e-mail-shell-view-actions.c 2025-06-07 14:27:24.063673134 -0700
@@ -1634,14 +1634,14 @@
{ "mail-folder-expunge",
NULL,
N_("E_xpunge"),
- "<Control>e",
+ "<Super>e",
N_("Permanently remove all deleted messages from this folder"),
G_CALLBACK (action_mail_folder_expunge_cb) },
{ "mail-folder-mark-all-as-read",
"mail-mark-read",
N_("Mar_k All Messages as Read"),
- "<Control>slash",
+ "<Super>slash",
N_("Mark all messages in the folder as read"),
G_CALLBACK (action_mail_folder_mark_all_as_read_cb) },
@@ -1684,14 +1684,14 @@
{ "mail-folder-select-thread",
NULL,
N_("Select Message _Thread"),
- "<Control>h",
+ "<Super>h",
N_("Select all messages in the same thread as the selected message"),
G_CALLBACK (action_mail_folder_select_thread_cb) },
{ "mail-folder-select-subthread",
NULL,
N_("Select Message S_ubthread"),
- "<Shift><Control>h",
+ "<Shift><Super>h",
N_("Select all replies to the currently selected message"),
G_CALLBACK (action_mail_folder_select_subthread_cb) },
@@ -1712,7 +1712,7 @@
{ "mail-goto-folder",
NULL,
N_("Go to _Folder"),
- "<Control>g",
+ "<Super>g",
N_("Opens a dialog to select a folder to go to"),
G_CALLBACK (action_mail_goto_folder_cb) },
@@ -1789,7 +1789,7 @@
{ "mail-threads-collapse-all",
NULL,
N_("Collapse All _Threads"),
- "<Shift><Control>b",
+ "<Shift><Super>b",
N_("Collapse all message threads"),
G_CALLBACK (action_mail_threads_collapse_all_cb) },
@@ -1916,7 +1916,7 @@
{ "mail-preview",
NULL,
N_("Show Message _Preview"),
- "<Control>m",
+ "<Super>m",
N_("Show message preview pane"),
NULL, /* Handled by property bindings */
TRUE },
@@ -1948,7 +1948,7 @@
{ "mail-threads-group-by",
NULL,
N_("_Group By Threads"),
- "<Control>t",
+ "<Super>t",
N_("Threaded message list"),
NULL, /* Handled by property bindings */
FALSE },
diff -urN a/src/modules/mail/e-mail-shell-view-actions.c.orig b/src/modules/mail/e-mail-shell-view-actions.c.orig
--- a/src/modules/mail/e-mail-shell-view-actions.c.orig 1969-12-31 16:00:00.000000000 -0800
+++ b/src/modules/mail/e-mail-shell-view-actions.c.orig 2025-06-07 14:20:57.379397797 -0700
@@ -0,0 +1,2455 @@
+/*
+ * e-mail-shell-view-actions.c
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * 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 Lesser General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
+ *
+ */
+
+#include "evolution-config.h"
+
+#include "mail/e-mail-folder-sort-order-dialog.h"
+
+#include "e-mail-shell-view-private.h"
+
+static void
+mail_shell_view_folder_created_cb (EMailFolderCreateDialog *dialog,
+ CamelStore *store,
+ const gchar *folder_name,
+ GWeakRef *folder_tree_weak_ref)
+{
+ EMFolderTree *folder_tree;
+
+ folder_tree = g_weak_ref_get (folder_tree_weak_ref);
+
+ if (folder_tree != NULL) {
+ gchar *folder_uri;
+
+ /* Select the newly created folder. */
+ folder_uri = e_mail_folder_uri_build (store, folder_name);
+ em_folder_tree_set_selected (folder_tree, folder_uri, FALSE);
+ g_free (folder_uri);
+
+ g_object_unref (folder_tree);
+ }
+}
+
+static void
+action_mail_account_disable_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EMailBackend *backend;
+ EMailSession *session;
+ EMailAccountStore *account_store;
+ EMFolderTree *folder_tree;
+ CamelStore *store;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+ account_store = e_mail_ui_session_get_account_store (
+ E_MAIL_UI_SESSION (session));
+
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ store = em_folder_tree_ref_selected_store (folder_tree);
+ g_return_if_fail (store != NULL);
+
+ e_mail_account_store_disable_service (
+ account_store,
+ GTK_WINDOW (shell_window),
+ CAMEL_SERVICE (store));
+
+ e_shell_view_update_actions (shell_view);
+
+ g_object_unref (store);
+}
+
+static void
+action_mail_account_properties_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShell *shell;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ ESourceRegistry *registry;
+ ESource *source;
+ EMFolderTree *folder_tree;
+ CamelService *service;
+ CamelStore *store;
+ const gchar *uid;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell = e_shell_backend_get_shell (shell_backend);
+
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ store = em_folder_tree_ref_selected_store (folder_tree);
+ g_return_if_fail (store != NULL);
+
+ service = CAMEL_SERVICE (store);
+ uid = camel_service_get_uid (service);
+ registry = e_shell_get_registry (shell);
+ source = e_source_registry_ref_source (registry, uid);
+ g_return_if_fail (source != NULL);
+
+ e_mail_shell_backend_edit_account (
+ E_MAIL_SHELL_BACKEND (shell_backend),
+ GTK_WINDOW (shell_window), source);
+
+ g_object_unref (source);
+ g_object_unref (store);
+}
+
+static void
+account_refresh_folder_info_received_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CamelStore *store;
+ CamelFolderInfo *info;
+ EActivity *activity;
+ GError *error = NULL;
+
+ store = CAMEL_STORE (source);
+ activity = E_ACTIVITY (user_data);
+
+ info = camel_store_get_folder_info_finish (store, result, &error);
+
+ /* Provider takes care of notifications of new/removed
+ * folders, thus it's enough to free the returned list. */
+ camel_folder_info_free (info);
+
+ if (e_activity_handle_cancellation (activity, error)) {
+ g_error_free (error);
+
+ } else if (error != NULL) {
+ g_warning ("%s: %s", G_STRFUNC, error->message);
+ g_error_free (error);
+ }
+
+ g_clear_object (&activity);
+}
+
+static void
+action_mail_account_refresh_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ EMailView *mail_view;
+ EActivity *activity;
+ ESourceRegistry *registry;
+ ESource *source;
+ EShell *shell;
+ CamelStore *store;
+ GCancellable *cancellable;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ store = em_folder_tree_ref_selected_store (folder_tree);
+ g_return_if_fail (store != NULL);
+
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+ activity = e_mail_reader_new_activity (E_MAIL_READER (mail_view));
+ cancellable = e_activity_get_cancellable (activity);
+
+ shell = e_shell_backend_get_shell (e_shell_view_get_shell_backend (E_SHELL_VIEW (mail_shell_view)));
+ registry = e_shell_get_registry (shell);
+ source = e_source_registry_ref_source (registry, camel_service_get_uid (CAMEL_SERVICE (store)));
+ g_return_if_fail (source != NULL);
+
+ e_shell_allow_auth_prompt_for (shell, source);
+
+ camel_store_get_folder_info (
+ store, NULL,
+ CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_REFRESH,
+ G_PRIORITY_DEFAULT, cancellable,
+ account_refresh_folder_info_received_cb, activity);
+
+ g_clear_object (&source);
+ g_clear_object (&store);
+}
+
+static void
+action_mail_create_search_folder_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailReader *reader;
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellSearchbar *searchbar;
+ EFilterRule *search_rule;
+ EMVFolderRule *vfolder_rule;
+ EMailBackend *backend;
+ EMailSession *session;
+ EMailView *mail_view;
+ CamelFolder *folder;
+ const gchar *search_text;
+ gchar *folder_uri;
+ gchar *rule_name;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+ searchbar = e_mail_shell_content_get_searchbar (mail_shell_content);
+
+ search_rule = e_shell_view_get_search_rule (shell_view);
+ g_return_if_fail (search_rule != NULL);
+
+ search_text = e_shell_searchbar_get_search_text (searchbar);
+ if (search_text == NULL || *search_text == '\0')
+ search_text = "''";
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ search_rule = vfolder_clone_rule (session, search_rule);
+ g_return_if_fail (search_rule != NULL);
+
+ rule_name = g_strdup_printf ("%s %s", search_rule->name, search_text);
+ e_filter_rule_set_source (search_rule, E_FILTER_SOURCE_INCOMING);
+ e_filter_rule_set_name (search_rule, rule_name);
+ g_free (rule_name);
+
+ reader = E_MAIL_READER (mail_view);
+ folder = e_mail_reader_ref_folder (reader);
+ folder_uri = e_mail_folder_uri_from_folder (folder);
+
+ vfolder_rule = EM_VFOLDER_RULE (search_rule);
+ em_vfolder_rule_add_source (vfolder_rule, folder_uri);
+ vfolder_gui_add_rule (vfolder_rule);
+
+ g_clear_object (&folder);
+ g_free (folder_uri);
+}
+
+static void
+action_mail_attachment_bar_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailDisplay *mail_display;
+ EAttachmentView *attachment_view;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ mail_display = e_mail_reader_get_mail_display (E_MAIL_READER (mail_shell_view->priv->mail_shell_content));
+ attachment_view = e_mail_display_get_attachment_view (mail_display);
+ if (gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action))) {
+ EAttachmentStore *store;
+ guint num_attachments;
+
+ store = e_attachment_bar_get_store (E_ATTACHMENT_BAR (attachment_view));
+ num_attachments = e_attachment_store_get_num_attachments (store);
+ gtk_widget_set_visible (GTK_WIDGET (attachment_view), num_attachments > 0);
+ } else {
+ gtk_widget_hide (GTK_WIDGET (attachment_view));
+ }
+}
+
+static void
+action_mail_to_do_bar_cb (GtkAction *action,
+ EShellView *shell_view)
+{
+ EShellContent *shell_content;
+ GtkWidget *to_do_pane;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (shell_view));
+
+ shell_content = e_shell_view_get_shell_content (shell_view);
+ to_do_pane = e_mail_shell_content_get_to_do_pane (E_MAIL_SHELL_CONTENT (shell_content));
+
+ gtk_widget_set_visible (to_do_pane, gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
+}
+
+static void
+action_mail_download_finished_cb (CamelStore *store,
+ GAsyncResult *result,
+ EActivity *activity)
+{
+ EAlertSink *alert_sink;
+ GError *error = NULL;
+
+ alert_sink = e_activity_get_alert_sink (activity);
+
+ e_mail_store_prepare_for_offline_finish (store, result, &error);
+
+ if (e_activity_handle_cancellation (activity, error)) {
+ g_error_free (error);
+
+ } else if (error != NULL) {
+ e_alert_submit (
+ alert_sink, "mail:prepare-for-offline",
+ camel_service_get_display_name (CAMEL_SERVICE (store)),
+ error->message, NULL);
+ g_error_free (error);
+ }
+
+ g_object_unref (activity);
+}
+
+static void
+action_mail_download_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailView *mail_view;
+ EMailReader *reader;
+ EMailBackend *backend;
+ EMailSession *session;
+ GList *list, *link;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ reader = E_MAIL_READER (mail_view);
+ backend = e_mail_reader_get_backend (reader);
+ session = e_mail_backend_get_session (backend);
+
+ list = camel_session_list_services (CAMEL_SESSION (session));
+
+ for (link = list; link != NULL; link = g_list_next (link)) {
+ EActivity *activity;
+ CamelService *service;
+ GCancellable *cancellable;
+
+ service = CAMEL_SERVICE (link->data);
+
+ if (!CAMEL_IS_STORE (service))
+ continue;
+
+ activity = e_mail_reader_new_activity (reader);
+ cancellable = e_activity_get_cancellable (activity);
+
+ e_mail_store_prepare_for_offline (
+ CAMEL_STORE (service), G_PRIORITY_DEFAULT,
+ cancellable, (GAsyncReadyCallback)
+ action_mail_download_finished_cb, activity);
+ }
+
+ g_list_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+action_mail_flush_outbox_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellBackend *shell_backend;
+ EShellView *shell_view;
+ EMailBackend *backend;
+ EMailSession *session;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ mail_send_immediately (session);
+}
+
+static void
+action_mail_folder_copy_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMFolderTree *folder_tree;
+ EMailSession *session;
+ gchar *selected_uri;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ selected_uri = em_folder_tree_get_selected_uri (folder_tree);
+ session = em_folder_tree_get_session (folder_tree);
+ g_return_if_fail (selected_uri != NULL);
+
+ em_folder_utils_copy_folder (
+ GTK_WINDOW (shell_window),
+ session,
+ E_ALERT_SINK (shell_content),
+ selected_uri, FALSE);
+
+ g_free (selected_uri);
+}
+
+static void
+action_mail_folder_delete_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMailView *mail_view;
+ EMFolderTree *folder_tree;
+ CamelStore *selected_store = NULL;
+ gchar *selected_folder_name = NULL;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ em_folder_tree_get_selected (
+ folder_tree, &selected_store, &selected_folder_name);
+ g_return_if_fail (CAMEL_IS_STORE (selected_store));
+ g_return_if_fail (selected_folder_name != NULL);
+
+ e_mail_reader_delete_folder_name (
+ E_MAIL_READER (mail_view),
+ selected_store, selected_folder_name);
+
+ g_object_unref (selected_store);
+ g_free (selected_folder_name);
+}
+
+static void
+action_mail_folder_edit_sort_order_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailView *mail_view;
+ EMFolderTree *folder_tree;
+ CamelStore *store;
+ GtkWidget *dialog;
+ GtkWindow *window;
+ gchar *selected_uri;
+
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_view->priv->mail_shell_sidebar);
+ store = em_folder_tree_ref_selected_store (folder_tree);
+
+ g_return_if_fail (store != NULL);
+
+ selected_uri = em_folder_tree_get_selected_uri (folder_tree);
+
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_view->priv->mail_shell_content);
+ window = e_mail_reader_get_window (E_MAIL_READER (mail_view));
+
+ dialog = e_mail_folder_sort_order_dialog_new (window, store, selected_uri);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+ g_object_unref (store);
+ g_free (selected_uri);
+}
+
+static void
+action_mail_folder_expunge_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMailView *mail_view;
+ EMFolderTree *folder_tree;
+ CamelStore *selected_store = NULL;
+ gchar *selected_folder_name = NULL;
+
+ /* This handles both the "folder-expunge" and "account-expunge"
+ * actions. */
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ /* Get the folder from the folder tree, not the message list.
+ * This correctly handles the use case of right-clicking on
+ * the "Trash" folder and selecting "Empty Trash" without
+ * actually selecting the folder. In that case the message
+ * list would not contain the correct folder to expunge. */
+ em_folder_tree_get_selected (
+ folder_tree, &selected_store, &selected_folder_name);
+ g_return_if_fail (CAMEL_IS_STORE (selected_store));
+ g_return_if_fail (selected_folder_name != NULL);
+
+ e_mail_reader_expunge_folder_name (
+ E_MAIL_READER (mail_view),
+ selected_store, selected_folder_name);
+
+ g_object_unref (selected_store);
+ g_free (selected_folder_name);
+}
+
+static void
+action_mail_folder_empty_junk_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMailView *mail_view;
+ EMFolderTree *folder_tree;
+ CamelStore *selected_store = NULL;
+ gchar *selected_folder_name = NULL;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ /* Get the folder from the folder tree, not the message list.
+ * This correctly handles the use case of right-clicking on
+ * the "Junk" folder and selecting "Empty Junk" without
+ * actually selecting the folder. In that case the message
+ * list would not contain the correct folder. */
+ em_folder_tree_get_selected (folder_tree, &selected_store, &selected_folder_name);
+ g_return_if_fail (CAMEL_IS_STORE (selected_store));
+ g_return_if_fail (selected_folder_name != NULL);
+
+ e_mail_reader_empty_junk_folder_name (E_MAIL_READER (mail_view), selected_store, selected_folder_name);
+
+ g_object_unref (selected_store);
+ g_free (selected_folder_name);
+}
+
+typedef struct _AsyncContext {
+ EActivity *activity;
+ EMailShellView *mail_shell_view;
+ gboolean can_subfolders;
+ GQueue folder_names;
+} AsyncContext;
+
+static void
+async_context_free (AsyncContext *context)
+{
+ if (context->activity != NULL)
+ g_object_unref (context->activity);
+
+ if (context->mail_shell_view != NULL)
+ g_object_unref (context->mail_shell_view);
+
+ /* This should be empty already, unless an error occurred... */
+ while (!g_queue_is_empty (&context->folder_names))
+ g_free (g_queue_pop_head (&context->folder_names));
+
+ g_slice_free (AsyncContext, context);
+}
+
+static void
+mark_all_read_thread (GSimpleAsyncResult *simple,
+ GObject *object,
+ GCancellable *cancellable)
+{
+ AsyncContext *context;
+ CamelStore *store;
+ CamelFolder *folder;
+ GPtrArray *uids;
+ gint ii;
+ GError *error = NULL;
+
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+ store = CAMEL_STORE (object);
+
+ while (!g_queue_is_empty (&context->folder_names) && !error) {
+ gchar *folder_name;
+
+ folder_name = g_queue_pop_head (&context->folder_names);
+ folder = camel_store_get_folder_sync (
+ store, folder_name, 0, cancellable, &error);
+ g_free (folder_name);
+
+ if (folder == NULL)
+ break;
+
+ camel_folder_freeze (folder);
+
+ uids = camel_folder_get_uids (folder);
+
+ for (ii = 0; ii < uids->len; ii++)
+ camel_folder_set_message_flags (
+ folder, uids->pdata[ii],
+ CAMEL_MESSAGE_SEEN,
+ CAMEL_MESSAGE_SEEN);
+
+ camel_folder_thaw (folder);
+
+ /* Save changes to the server immediately. */
+ camel_folder_synchronize_sync (folder, FALSE, cancellable, &error);
+
+ camel_folder_free_uids (folder, uids);
+ g_object_unref (folder);
+ }
+
+ if (error != NULL)
+ g_simple_async_result_take_error (simple, error);
+}
+
+static void
+mark_all_read_done_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *simple;
+ AsyncContext *context;
+ GError *local_error = NULL;
+
+ g_return_if_fail (
+ g_simple_async_result_is_valid (
+ result, source, mark_all_read_thread));
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ context = g_simple_async_result_get_op_res_gpointer (simple);
+
+ if (g_simple_async_result_propagate_error (simple, &local_error) &&
+ local_error &&
+ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
+ EAlertSink *alert_sink;
+
+ alert_sink = e_activity_get_alert_sink (context->activity);
+
+ e_alert_submit (
+ alert_sink, "mail:mark-all-read",
+ local_error->message, NULL);
+ }
+
+ g_clear_error (&local_error);
+
+ e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
+}
+
+static void
+mark_all_read_collect_folder_names (GQueue *folder_names,
+ CamelFolderInfo *folder_info)
+{
+ while (folder_info != NULL) {
+ if (folder_info->child != NULL)
+ mark_all_read_collect_folder_names (
+ folder_names, folder_info->child);
+
+ g_queue_push_tail (
+ folder_names, g_strdup (folder_info->full_name));
+
+ folder_info = folder_info->next;
+ }
+}
+
+enum {
+ MARK_ALL_READ_CANCEL,
+ MARK_ALL_READ_CURRENT_ONLY,
+ MARK_ALL_READ_WITH_SUBFOLDERS
+};
+
+static gint
+mark_all_read_prompt_user (EMailShellView *mail_shell_view,
+ gboolean with_subfolders)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ GtkWindow *parent;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ parent = GTK_WINDOW (shell_window);
+
+ if (with_subfolders) {
+ GSettings *settings;
+ GdkDisplay *display;
+ GdkKeymap *keymap;
+
+ display = gtk_widget_get_display (GTK_WIDGET (e_shell_view_get_shell_window (E_SHELL_VIEW (mail_shell_view))));
+ keymap = gdk_keymap_get_for_display (display);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ if ((gdk_keymap_get_modifier_state (keymap) & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK)) != GDK_SHIFT_MASK &&
+ !g_settings_get_boolean (settings, "prompt-on-mark-all-read")) {
+ g_object_unref (settings);
+ return MARK_ALL_READ_CURRENT_ONLY;
+ }
+
+ switch (e_alert_run_dialog_for_args (parent,
+ "mail:ask-mark-all-read-sub", NULL)) {
+ case GTK_RESPONSE_YES:
+ g_object_unref (settings);
+ return MARK_ALL_READ_WITH_SUBFOLDERS;
+ case GTK_RESPONSE_NO:
+ g_object_unref (settings);
+ return MARK_ALL_READ_CURRENT_ONLY;
+ case GTK_RESPONSE_ACCEPT:
+ g_settings_set_boolean (settings, "prompt-on-mark-all-read", FALSE);
+ g_object_unref (settings);
+ return MARK_ALL_READ_CURRENT_ONLY;
+ default:
+ break;
+ }
+
+ g_object_unref (settings);
+ } else if (e_util_prompt_user (parent,
+ "org.gnome.evolution.mail",
+ "prompt-on-mark-all-read",
+ "mail:ask-mark-all-read", NULL))
+ return MARK_ALL_READ_CURRENT_ONLY;
+
+ return MARK_ALL_READ_CANCEL;
+}
+
+static gboolean
+mark_all_read_child_has_unread (CamelFolderInfo *folder_info)
+{
+ gboolean any_has = FALSE;
+
+ if (!folder_info)
+ return FALSE;
+
+ while (!any_has && folder_info) {
+ any_has = folder_info->unread > 0 || mark_all_read_child_has_unread (folder_info->child);
+
+ folder_info = folder_info->next;
+ }
+
+ return any_has;
+}
+
+static void
+mark_all_read_got_folder_info (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ CamelStore *store = CAMEL_STORE (source);
+ AsyncContext *context = user_data;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+ GSimpleAsyncResult *simple;
+ CamelFolderInfo *folder_info;
+ gint response;
+ GError *error = NULL;
+
+ alert_sink = e_activity_get_alert_sink (context->activity);
+ cancellable = e_activity_get_cancellable (context->activity);
+
+ folder_info = camel_store_get_folder_info_finish (
+ store, result, &error);
+
+ if (e_activity_handle_cancellation (context->activity, error)) {
+ g_warn_if_fail (folder_info == NULL);
+ async_context_free (context);
+ g_error_free (error);
+ return;
+
+ } else if (error != NULL) {
+ g_warn_if_fail (folder_info == NULL);
+ e_alert_submit (
+ alert_sink, "mail:mark-all-read",
+ error->message, NULL);
+ async_context_free (context);
+ g_error_free (error);
+ return;
+ }
+
+ if (!folder_info) {
+ /* Otherwise the operation is stuck and the Evolution cannot be quit */
+ g_warn_if_fail (folder_info != NULL);
+ e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
+ async_context_free (context);
+ return;
+ }
+
+ response = mark_all_read_prompt_user (
+ context->mail_shell_view,
+ context->can_subfolders && mark_all_read_child_has_unread (folder_info->child));
+
+ if (response == MARK_ALL_READ_CURRENT_ONLY)
+ g_queue_push_tail (
+ &context->folder_names,
+ g_strdup (folder_info->full_name));
+
+ if (response == MARK_ALL_READ_WITH_SUBFOLDERS)
+ mark_all_read_collect_folder_names (
+ &context->folder_names, folder_info);
+
+ camel_folder_info_free (folder_info);
+
+ if (g_queue_is_empty (&context->folder_names)) {
+ e_activity_set_state (context->activity, E_ACTIVITY_COMPLETED);
+ async_context_free (context);
+ return;
+ }
+
+ simple = g_simple_async_result_new (
+ source, mark_all_read_done_cb,
+ context, mark_all_read_thread);
+
+ g_simple_async_result_set_op_res_gpointer (
+ simple, context, (GDestroyNotify) async_context_free);
+
+ g_simple_async_result_run_in_thread (
+ simple, mark_all_read_thread,
+ G_PRIORITY_DEFAULT, cancellable);
+
+ g_object_unref (simple);
+}
+
+static void
+e_mail_shell_view_actions_mark_all_read (EMailShellView *mail_shell_view,
+ CamelStore *store,
+ const gchar *folder_name,
+ gboolean can_subfolders)
+{
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ EAlertSink *alert_sink;
+ GCancellable *cancellable;
+ AsyncContext *context;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+ g_return_if_fail (CAMEL_IS_STORE (store));
+ g_return_if_fail (folder_name != NULL);
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ context = g_slice_new0 (AsyncContext);
+ context->mail_shell_view = g_object_ref (mail_shell_view);
+ context->can_subfolders = can_subfolders;
+ context->activity = e_activity_new ();
+ g_queue_init (&context->folder_names);
+
+ alert_sink = E_ALERT_SINK (shell_content);
+ e_activity_set_alert_sink (context->activity, alert_sink);
+
+ cancellable = camel_operation_new ();
+ e_activity_set_cancellable (context->activity, cancellable);
+
+ camel_operation_push_message (
+ cancellable, _("Marking messages as read…"));
+
+ e_shell_backend_add_activity (shell_backend, context->activity);
+
+ camel_store_get_folder_info (
+ store, folder_name,
+ can_subfolders ? CAMEL_STORE_FOLDER_INFO_RECURSIVE : 0,
+ G_PRIORITY_DEFAULT, cancellable,
+ mark_all_read_got_folder_info, context);
+
+ g_object_unref (cancellable);
+}
+
+static void
+action_mail_folder_mark_all_as_read_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailReader *reader;
+ EMailView *mail_view;
+ CamelFolder *folder;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ reader = E_MAIL_READER (mail_view);
+
+ folder = e_mail_reader_ref_folder (reader);
+ g_return_if_fail (folder != NULL);
+
+ if (camel_folder_get_folder_summary (folder) != NULL &&
+ camel_folder_summary_get_unread_count (camel_folder_get_folder_summary (folder)) == 0) {
+ g_object_unref (folder);
+ return;
+ }
+
+ e_mail_shell_view_actions_mark_all_read (
+ mail_shell_view,
+ camel_folder_get_parent_store (folder),
+ camel_folder_get_full_name (folder),
+ FALSE);
+
+ g_object_unref (folder);
+}
+
+static void
+action_mail_popup_folder_mark_all_as_read_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ CamelStore *store = NULL;
+ gchar *folder_name = NULL;
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ em_folder_tree_get_selected (folder_tree, &store, &folder_name);
+
+ /* This action should only be activatable if a folder is selected. */
+ g_return_if_fail (store != NULL && folder_name != NULL);
+
+ e_mail_shell_view_actions_mark_all_read (
+ mail_shell_view, store, folder_name, TRUE);
+
+ g_object_unref (store);
+ g_free (folder_name);
+}
+
+static void
+action_mail_folder_move_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMFolderTree *folder_tree;
+ EMailSession *session;
+ gchar *selected_uri;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ selected_uri = em_folder_tree_get_selected_uri (folder_tree);
+ session = em_folder_tree_get_session (folder_tree);
+ g_return_if_fail (selected_uri != NULL);
+
+ em_folder_utils_copy_folder (
+ GTK_WINDOW (shell_window),
+ session,
+ E_ALERT_SINK (shell_content),
+ selected_uri, TRUE);
+
+ g_free (selected_uri);
+}
+
+static void
+action_mail_folder_new_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EMailSession *session;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ GtkWidget *dialog;
+ CamelStore *store = NULL;
+ gchar *folder_name = NULL;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ session = em_folder_tree_get_session (folder_tree);
+
+ dialog = e_mail_folder_create_dialog_new (
+ GTK_WINDOW (shell_window),
+ E_MAIL_UI_SESSION (session));
+
+ g_signal_connect_data (
+ dialog, "folder-created",
+ G_CALLBACK (mail_shell_view_folder_created_cb),
+ e_weak_ref_new (folder_tree),
+ (GClosureNotify) e_weak_ref_free, 0);
+
+ if (em_folder_tree_get_selected (folder_tree, &store, &folder_name)) {
+ em_folder_selector_set_selected (
+ EM_FOLDER_SELECTOR (dialog), store, folder_name);
+ g_object_unref (store);
+ g_free (folder_name);
+ }
+
+ gtk_widget_show (GTK_WIDGET (dialog));
+}
+
+static void
+action_mail_folder_properties_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellContent *shell_content;
+ EMFolderTree *folder_tree;
+ CamelStore *store;
+ gchar *folder_name;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ if (!em_folder_tree_get_selected (folder_tree, &store, &folder_name))
+ g_return_if_reached ();
+
+ em_folder_properties_show (
+ store, folder_name,
+ E_ALERT_SINK (shell_content),
+ GTK_WINDOW (shell_window));
+
+ g_object_unref (store);
+ g_free (folder_name);
+}
+
+static void
+action_mail_folder_refresh_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMailView *mail_view;
+ EMFolderTree *folder_tree;
+ CamelStore *selected_store = NULL;
+ gchar *selected_folder_name = NULL;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ em_folder_tree_get_selected (
+ folder_tree, &selected_store, &selected_folder_name);
+ g_return_if_fail (CAMEL_IS_STORE (selected_store));
+ g_return_if_fail (selected_folder_name != NULL);
+
+ e_mail_reader_refresh_folder_name (
+ E_MAIL_READER (mail_view),
+ selected_store, selected_folder_name);
+
+ g_object_unref (selected_store);
+ g_free (selected_folder_name);
+}
+
+static void
+action_mail_folder_rename_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ e_mail_shell_view_rename_folder (mail_shell_view);
+}
+
+static void
+action_mail_folder_select_thread_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GtkWidget *message_list;
+ EMailReader *reader;
+ EMailView *mail_view;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ reader = E_MAIL_READER (mail_view);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_thread (MESSAGE_LIST (message_list));
+}
+
+static void
+action_mail_folder_select_subthread_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GtkWidget *message_list;
+ EMailReader *reader;
+ EMailView *mail_view;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ reader = E_MAIL_READER (mail_view);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_select_subthread (MESSAGE_LIST (message_list));
+}
+
+static gboolean
+ask_can_unsubscribe_folder (GtkWindow *parent,
+ CamelFolder *folder)
+{
+ gchar *full_display_name;
+ gboolean res;
+
+ g_return_val_if_fail (CAMEL_IS_FOLDER (folder), FALSE);
+
+ full_display_name = e_mail_folder_to_full_display_name (folder, NULL);
+
+ res = GTK_RESPONSE_YES == e_alert_run_dialog_for_args (parent,
+ "mail:ask-unsubscribe-folder", full_display_name ? full_display_name : camel_folder_get_full_name (folder), NULL);
+
+ g_free (full_display_name);
+
+ return res;
+}
+
+typedef struct _GetFolderData {
+ EMailShellView *mail_shell_view;
+ EActivity *activity;
+ CamelStore *store;
+ gchar *folder_name;
+} GetFolderData;
+
+static void
+get_folder_data_free (GetFolderData *gfd)
+{
+ if (gfd) {
+ g_clear_object (&gfd->mail_shell_view);
+ g_clear_object (&gfd->activity);
+ g_clear_object (&gfd->store);
+ g_free (gfd->folder_name);
+ g_slice_free (GetFolderData, gfd);
+ }
+}
+
+static void
+mail_folder_unsubscribe_got_folder_cb (GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GetFolderData *gfd = user_data;
+ CamelFolder *folder;
+ GError *local_error = NULL;
+
+ g_return_if_fail (gfd != NULL);
+
+ folder = camel_store_get_folder_finish (CAMEL_STORE (source_object), result, &local_error);
+
+ if (e_activity_handle_cancellation (gfd->activity, local_error)) {
+ g_error_free (local_error);
+
+ } else if (local_error != NULL) {
+ e_alert_submit (
+ e_activity_get_alert_sink (gfd->activity), "mail:folder-open",
+ local_error->message, NULL);
+ g_error_free (local_error);
+
+ } else {
+ EShellWindow *shell_window;
+ EMailView *mail_view;
+
+ e_activity_set_state (gfd->activity, E_ACTIVITY_COMPLETED);
+
+ shell_window = e_shell_view_get_shell_window (E_SHELL_VIEW (gfd->mail_shell_view));
+ mail_view = e_mail_shell_content_get_mail_view (gfd->mail_shell_view->priv->mail_shell_content);
+
+ if (ask_can_unsubscribe_folder (GTK_WINDOW (shell_window), folder))
+ e_mail_reader_unsubscribe_folder_name (E_MAIL_READER (mail_view), gfd->store, gfd->folder_name);
+ }
+
+ get_folder_data_free (gfd);
+ g_clear_object (&folder);
+}
+
+static void
+action_mail_folder_unsubscribe_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMailView *mail_view;
+ EMFolderTree *folder_tree;
+ CamelStore *selected_store = NULL;
+ gchar *selected_folder_name = NULL;
+ GetFolderData *gfd;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ em_folder_tree_get_selected (folder_tree, &selected_store, &selected_folder_name);
+ g_return_if_fail (CAMEL_IS_STORE (selected_store));
+ g_return_if_fail (selected_folder_name != NULL);
+
+ gfd = g_slice_new0 (GetFolderData);
+ gfd->mail_shell_view = g_object_ref (mail_shell_view);
+ gfd->activity = e_mail_reader_new_activity (E_MAIL_READER (mail_view));
+ gfd->store = selected_store;
+ gfd->folder_name = selected_folder_name;
+
+ camel_store_get_folder (gfd->store, gfd->folder_name, 0, G_PRIORITY_DEFAULT,
+ e_activity_get_cancellable (gfd->activity),
+ mail_folder_unsubscribe_got_folder_cb, gfd);
+}
+
+static void
+action_mail_global_expunge_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellBackend *shell_backend;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMailBackend *backend;
+ EMailSession *session;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ em_utils_empty_trash (
+ GTK_WIDGET (shell_window), session);
+}
+
+static void
+action_mail_goto_folder_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ CamelFolder *folder;
+ EMailReader *reader;
+ EMailView *mail_view;
+ EMFolderSelector *selector;
+ EMFolderTree *folder_tree;
+ EMFolderTreeModel *model;
+ GtkWidget *dialog;
+ GtkWindow *window;
+ const gchar *uri;
+
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_view->priv->mail_shell_content);
+ reader = E_MAIL_READER (mail_view);
+
+ folder = e_mail_reader_ref_folder (reader);
+ window = e_mail_reader_get_window (reader);
+
+ model = em_folder_tree_model_get_default ();
+
+ dialog = em_folder_selector_new (window, model);
+
+ gtk_window_set_title (GTK_WINDOW (dialog), _("Go to Folder"));
+
+ selector = EM_FOLDER_SELECTOR (dialog);
+ em_folder_selector_set_can_create (selector, FALSE);
+ em_folder_selector_set_default_button_label (selector, _("_Select"));
+
+ folder_tree = em_folder_selector_get_folder_tree (selector);
+ gtk_tree_view_expand_all (GTK_TREE_VIEW (folder_tree));
+ em_folder_selector_maybe_collapse_archive_folders (selector);
+
+ if (folder) {
+ gchar *uri = e_mail_folder_uri_from_folder (folder);
+
+ if (uri) {
+ em_folder_tree_set_selected (folder_tree, uri, FALSE);
+ g_free (uri);
+ }
+ }
+
+ if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) {
+ uri = em_folder_selector_get_selected_uri (selector);
+
+ if (uri != NULL) {
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_view->priv->mail_shell_sidebar);
+ em_folder_tree_set_selected (folder_tree, uri, FALSE);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+
+ g_clear_object (&folder);
+}
+
+static void
+action_mail_send_receive_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EMailBackend *backend;
+ EMailSession *session;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ mail_send_receive (GTK_WINDOW (shell_window), session);
+}
+
+static void
+action_mail_send_receive_receive_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EMailBackend *backend;
+ EMailSession *session;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ mail_receive (GTK_WINDOW (shell_window), session);
+}
+
+static void
+action_mail_send_receive_send_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+ EMailBackend *backend;
+ EMailSession *session;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ mail_send_immediately (session);
+}
+
+static void
+mail_shell_view_magic_spacebar (EMailShellView *mail_shell_view,
+ gboolean move_forward)
+{
+ EMailShellContent *mail_shell_content;
+ EMailShellSidebar *mail_shell_sidebar;
+ EMFolderTree *folder_tree;
+ EMailReader *reader;
+ EMailView *mail_view;
+ GtkWidget *message_list;
+ EMailDisplay *display;
+ GSettings *settings;
+ gboolean magic_spacebar;
+
+ /* This implements the so-called "Magic Backspace". */
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+
+ reader = E_MAIL_READER (mail_view);
+ display = e_mail_reader_get_mail_display (reader);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+ magic_spacebar = g_settings_get_boolean (settings, "magic-spacebar");
+ g_object_unref (settings);
+
+ if (!e_mail_display_process_magic_spacebar (display, move_forward)) {
+ guint32 direction = move_forward ? MESSAGE_LIST_SELECT_NEXT : MESSAGE_LIST_SELECT_PREVIOUS;
+ gboolean selected;
+
+ if (!magic_spacebar)
+ return;
+
+ if (message_list_select (MESSAGE_LIST (message_list),
+ direction | MESSAGE_LIST_SELECT_WRAP |
+ MESSAGE_LIST_SELECT_INCLUDE_COLLAPSED,
+ 0, CAMEL_MESSAGE_SEEN))
+ return;
+
+ if (move_forward)
+ selected = em_folder_tree_select_next_path (folder_tree, TRUE);
+ else
+ selected = em_folder_tree_select_prev_path (folder_tree, TRUE);
+
+ if (selected)
+ message_list_set_regen_selects_unread (MESSAGE_LIST (message_list), TRUE);
+
+ gtk_widget_grab_focus (message_list);
+ }
+}
+
+static void
+action_mail_smart_backward_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ mail_shell_view_magic_spacebar (mail_shell_view, FALSE);
+}
+
+static void
+action_mail_smart_forward_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ mail_shell_view_magic_spacebar (mail_shell_view, TRUE);
+}
+
+static void
+action_mail_stop_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellBackend *shell_backend;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ e_shell_backend_cancel_all (shell_backend);
+}
+
+static void
+action_mail_threads_collapse_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GtkWidget *message_list;
+ EMailReader *reader;
+ EMailView *mail_view;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ reader = E_MAIL_READER (mail_view);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_set_threaded_collapse_all (MESSAGE_LIST (message_list));
+}
+
+static void
+action_mail_threads_expand_all_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GtkWidget *message_list;
+ EMailReader *reader;
+ EMailView *mail_view;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ reader = E_MAIL_READER (mail_view);
+ message_list = e_mail_reader_get_message_list (reader);
+
+ message_list_set_threaded_expand_all (MESSAGE_LIST (message_list));
+}
+
+static void
+action_mail_tools_filters_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellBackend *shell_backend;
+ EShellContent *shell_content;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMailBackend *backend;
+ EMailSession *session;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell_content = e_shell_view_get_shell_content (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+
+ em_utils_edit_filters (
+ session,
+ E_ALERT_SINK (shell_content),
+ GTK_WINDOW (shell_window));
+}
+
+static void
+action_mail_tools_search_folders_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ vfolder_edit (
+ E_MAIL_BACKEND (shell_backend),
+ GTK_WINDOW (shell_window));
+}
+
+static void
+action_mail_tools_subscriptions_cb (GtkAction *action,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellSidebar *mail_shell_sidebar;
+ EShellWindow *shell_window;
+ EShellView *shell_view;
+ EMailSession *session;
+ EMFolderTree *folder_tree;
+ GtkWidget *dialog;
+ CamelStore *store;
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+
+ mail_shell_sidebar = mail_shell_view->priv->mail_shell_sidebar;
+ folder_tree = e_mail_shell_sidebar_get_folder_tree (mail_shell_sidebar);
+ session = em_folder_tree_get_session (folder_tree);
+
+ /* The subscription editor's initial store can be NULL. */
+ store = em_folder_tree_ref_selected_store (folder_tree);
+
+ dialog = em_subscription_editor_new (
+ GTK_WINDOW (shell_window), session, store);
+
+ if (store != NULL)
+ g_object_unref (store);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+ gtk_widget_destroy (dialog);
+}
+
+static void
+action_mail_view_cb (GtkRadioAction *action,
+ GtkRadioAction *current,
+ EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ GtkOrientation orientation;
+ EMailView *mail_view;
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+
+ switch (gtk_radio_action_get_current_value (action)) {
+ case 0:
+ orientation = GTK_ORIENTATION_VERTICAL;
+ break;
+ case 1:
+ orientation = GTK_ORIENTATION_HORIZONTAL;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ e_mail_view_set_orientation (mail_view, orientation);
+}
+
+static GtkActionEntry mail_entries[] = {
+
+ { "mail-account-disable",
+ NULL,
+ N_("_Disable Account"),
+ NULL,
+ N_("Disable this account"),
+ G_CALLBACK (action_mail_account_disable_cb) },
+
+ { "mail-account-expunge",
+ NULL,
+ N_("_Empty Trash"),
+ NULL,
+ N_("Permanently remove all the deleted messages from all folders"),
+ G_CALLBACK (action_mail_folder_expunge_cb) },
+
+ { "mail-account-empty-junk",
+ NULL,
+ N_("Empty _Junk"),
+ NULL,
+ N_("Delete all Junk messages from all folders"),
+ G_CALLBACK (action_mail_folder_empty_junk_cb) },
+
+ { "mail-account-properties",
+ "document-properties",
+ N_("_Properties"),
+ NULL,
+ N_("Edit properties of this account"),
+ G_CALLBACK (action_mail_account_properties_cb) },
+
+ { "mail-account-refresh",
+ "view-refresh",
+ N_("_Refresh"),
+ NULL,
+ N_("Refresh list of folders of this account"),
+ G_CALLBACK (action_mail_account_refresh_cb) },
+
+ { "mail-download",
+ NULL,
+ N_("_Download Messages for Offline Usage"),
+ NULL,
+ N_("Download messages of accounts and folders marked for offline usage"),
+ G_CALLBACK (action_mail_download_cb) },
+
+ { "mail-flush-outbox",
+ "mail-send",
+ N_("Fl_ush Outbox"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_flush_outbox_cb) },
+
+ { "mail-folder-copy",
+ "folder-copy",
+ N_("_Copy Folder To…"),
+ NULL,
+ N_("Copy the selected folder into another folder"),
+ G_CALLBACK (action_mail_folder_copy_cb) },
+
+ { "mail-folder-delete",
+ "edit-delete",
+ N_("_Delete"),
+ NULL,
+ N_("Permanently remove this folder"),
+ G_CALLBACK (action_mail_folder_delete_cb) },
+
+ { "mail-folder-edit-sort-order",
+ NULL,
+ N_("Edit Sort _Order…"),
+ NULL,
+ N_("Change sort order of the folders in the folder tree"),
+ G_CALLBACK (action_mail_folder_edit_sort_order_cb) },
+
+ { "mail-folder-expunge",
+ NULL,
+ N_("E_xpunge"),
+ "<Control>e",
+ N_("Permanently remove all deleted messages from this folder"),
+ G_CALLBACK (action_mail_folder_expunge_cb) },
+
+ { "mail-folder-mark-all-as-read",
+ "mail-mark-read",
+ N_("Mar_k All Messages as Read"),
+ "<Control>slash",
+ N_("Mark all messages in the folder as read"),
+ G_CALLBACK (action_mail_folder_mark_all_as_read_cb) },
+
+ { "mail-folder-move",
+ "folder-move",
+ N_("_Move Folder To…"),
+ NULL,
+ N_("Move the selected folder into another folder"),
+ G_CALLBACK (action_mail_folder_move_cb) },
+
+ { "mail-folder-new",
+ "folder-new",
+ /* Translators: An action caption to create a new mail folder */
+ N_("_New…"),
+ NULL,
+ N_("Create a new folder for storing mail"),
+ G_CALLBACK (action_mail_folder_new_cb) },
+
+ { "mail-folder-properties",
+ "document-properties",
+ N_("_Properties"),
+ NULL,
+ N_("Change the properties of this folder"),
+ G_CALLBACK (action_mail_folder_properties_cb) },
+
+ { "mail-folder-refresh",
+ "view-refresh",
+ N_("_Refresh"),
+ "F5",
+ N_("Refresh the folder"),
+ G_CALLBACK (action_mail_folder_refresh_cb) },
+
+ { "mail-folder-rename",
+ NULL,
+ N_("_Rename…"),
+ "F2",
+ N_("Change the name of this folder"),
+ G_CALLBACK (action_mail_folder_rename_cb) },
+
+ { "mail-folder-select-thread",
+ NULL,
+ N_("Select Message _Thread"),
+ "<Control>h",
+ N_("Select all messages in the same thread as the selected message"),
+ G_CALLBACK (action_mail_folder_select_thread_cb) },
+
+ { "mail-folder-select-subthread",
+ NULL,
+ N_("Select Message S_ubthread"),
+ "<Shift><Control>h",
+ N_("Select all replies to the currently selected message"),
+ G_CALLBACK (action_mail_folder_select_subthread_cb) },
+
+ { "mail-folder-unsubscribe",
+ NULL,
+ N_("_Unsubscribe"),
+ NULL,
+ N_("Unsubscribe from the selected folder"),
+ G_CALLBACK (action_mail_folder_unsubscribe_cb) },
+
+ { "mail-global-expunge",
+ NULL,
+ N_("Empty _Trash"),
+ NULL,
+ N_("Permanently remove all the deleted messages from all accounts"),
+ G_CALLBACK (action_mail_global_expunge_cb) },
+
+ { "mail-goto-folder",
+ NULL,
+ N_("Go to _Folder"),
+ "<Control>g",
+ N_("Opens a dialog to select a folder to go to"),
+ G_CALLBACK (action_mail_goto_folder_cb) },
+
+ /* This is the same as "mail-tools-subscriptions" but only
+ * appears in the sidebar context menu when right-clicking
+ * on a store that supports folder subscriptions. No need
+ * for a special callback because Folder->Subscriptions...
+ * already tries to open the "Folder Subscriptions" dialog
+ * according to the highlighted item in the sidebar, which
+ * is exactly the behavior we want here. */
+ { "mail-manage-subscriptions",
+ NULL,
+ N_("_Manage Subscriptions"),
+ NULL,
+ N_("Subscribe or unsubscribe to folders on remote servers"),
+ G_CALLBACK (action_mail_tools_subscriptions_cb) },
+
+ { "mail-popup-folder-mark-all-as-read",
+ "mail-mark-read",
+ N_("Mar_k All Messages as Read"),
+ NULL,
+ N_("Mark all messages in the folder as read"),
+ G_CALLBACK (action_mail_popup_folder_mark_all_as_read_cb) },
+
+ { "mail-send-receive",
+ "mail-send-receive",
+ N_("Send / _Receive"),
+ "F12",
+ N_("Send queued items and retrieve new items"),
+ G_CALLBACK (action_mail_send_receive_cb) },
+
+ { "mail-send-receive-receive-all",
+ NULL,
+ N_("R_eceive All"),
+ NULL,
+ N_("Receive new items from all accounts"),
+ G_CALLBACK (action_mail_send_receive_receive_all_cb) },
+
+ { "mail-send-receive-send-all",
+ "mail-send",
+ N_("_Send All"),
+ NULL,
+ N_("Send queued items in all accounts"),
+ G_CALLBACK (action_mail_send_receive_send_all_cb) },
+
+ { "mail-send-receive-submenu",
+ "mail-send-receive",
+ N_("Send / _Receive"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-smart-backward",
+ "go-up", /* In case a user adds it to the UI */
+ NULL, /* No menu item; key press only */
+ "BackSpace",
+ NULL,
+ G_CALLBACK (action_mail_smart_backward_cb) },
+
+ { "mail-smart-forward",
+ "go-down", /* In case a user adds it to the UI */
+ NULL, /* No menu item; key press only */
+ "space",
+ NULL,
+ G_CALLBACK (action_mail_smart_forward_cb) },
+
+ { "mail-stop",
+ "process-stop",
+ N_("Cancel"),
+ NULL,
+ N_("Cancel the current mail operation"),
+ G_CALLBACK (action_mail_stop_cb) },
+
+ { "mail-threads-collapse-all",
+ NULL,
+ N_("Collapse All _Threads"),
+ "<Shift><Control>b",
+ N_("Collapse all message threads"),
+ G_CALLBACK (action_mail_threads_collapse_all_cb) },
+
+ { "mail-threads-expand-all",
+ NULL,
+ N_("E_xpand All Threads"),
+ NULL,
+ N_("Expand all message threads"),
+ G_CALLBACK (action_mail_threads_expand_all_cb) },
+
+ { "mail-tools-filters",
+ NULL,
+ N_("_Message Filters"),
+ NULL,
+ N_("Create or edit rules for filtering new mail"),
+ G_CALLBACK (action_mail_tools_filters_cb) },
+
+ { "mail-tools-subscriptions",
+ NULL,
+ N_("_Subscriptions…"),
+ NULL,
+ N_("Subscribe or unsubscribe to folders on remote servers"),
+ G_CALLBACK (action_mail_tools_subscriptions_cb) },
+
+ /*** Menus ***/
+
+ { "mail-folder-menu",
+ NULL,
+ N_("F_older"),
+ NULL,
+ NULL,
+ NULL },
+
+ { "mail-preview-menu",
+ NULL,
+ N_("_Preview"),
+ NULL,
+ NULL,
+ NULL }
+};
+
+static GtkActionEntry search_folder_entries[] = {
+
+ { "mail-create-search-folder",
+ NULL,
+ N_("C_reate Search Folder From Search…"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ G_CALLBACK (action_mail_create_search_folder_cb) },
+
+ { "mail-tools-search-folders",
+ NULL,
+ N_("Search F_olders"),
+ NULL,
+ N_("Create or edit search folder definitions"),
+ G_CALLBACK (action_mail_tools_search_folders_cb) },
+};
+
+static EPopupActionEntry mail_popup_entries[] = {
+
+ { "mail-popup-account-disable",
+ NULL,
+ "mail-account-disable" },
+
+ { "mail-popup-account-expunge",
+ NULL,
+ "mail-account-expunge" },
+
+ { "mail-popup-account-empty-junk",
+ NULL,
+ "mail-account-empty-junk" },
+
+ { "mail-popup-account-refresh",
+ NULL,
+ "mail-account-refresh" },
+
+ { "mail-popup-account-properties",
+ NULL,
+ "mail-account-properties" },
+
+ { "mail-popup-flush-outbox",
+ NULL,
+ "mail-flush-outbox" },
+
+ { "mail-popup-folder-copy",
+ NULL,
+ "mail-folder-copy" },
+
+ { "mail-popup-folder-delete",
+ NULL,
+ "mail-folder-delete" },
+
+ { "mail-popup-folder-move",
+ NULL,
+ "mail-folder-move" },
+
+ { "mail-popup-folder-new",
+ N_("_New Folder…"),
+ "mail-folder-new" },
+
+ { "mail-popup-folder-properties",
+ NULL,
+ "mail-folder-properties" },
+
+ { "mail-popup-folder-refresh",
+ NULL,
+ "mail-folder-refresh" },
+
+ { "mail-popup-folder-rename",
+ NULL,
+ "mail-folder-rename" },
+
+ { "mail-popup-folder-unsubscribe",
+ NULL,
+ "mail-folder-unsubscribe" },
+
+ { "mail-popup-manage-subscriptions",
+ NULL,
+ "mail-manage-subscriptions" }
+};
+
+static GtkToggleActionEntry mail_toggle_entries[] = {
+
+ { "mail-preview",
+ NULL,
+ N_("Show Message _Preview"),
+ "<Control>m",
+ N_("Show message preview pane"),
+ NULL, /* Handled by property bindings */
+ TRUE },
+
+ { "mail-attachment-bar",
+ NULL,
+ N_("Show _Attachment Bar"),
+ NULL,
+ N_("Show Attachment Bar below the message preview pane when the message has attachments"),
+ G_CALLBACK (action_mail_attachment_bar_cb),
+ TRUE },
+
+ { "mail-show-deleted",
+ NULL,
+ N_("Show _Deleted Messages"),
+ NULL,
+ N_("Show deleted messages with a line through them"),
+ NULL, /* Handled by property bindings */
+ FALSE },
+
+ { "mail-show-junk",
+ NULL,
+ N_("Show _Junk Messages"),
+ NULL,
+ N_("Show junk messages with a red line through them"),
+ NULL, /* Handled by property bindings */
+ FALSE },
+
+ { "mail-threads-group-by",
+ NULL,
+ N_("_Group By Threads"),
+ "<Control>t",
+ N_("Threaded message list"),
+ NULL, /* Handled by property bindings */
+ FALSE },
+
+ { "mail-to-do-bar",
+ NULL,
+ N_("Show To _Do Bar"),
+ NULL,
+ N_("Show To Do bar with appointments and tasks"),
+ G_CALLBACK (action_mail_to_do_bar_cb),
+ TRUE },
+
+ { "mail-vfolder-unmatched-enable",
+ NULL,
+ N_("_Unmatched Folder Enabled"),
+ NULL,
+ N_("Toggles whether Unmatched search folder is enabled"),
+ NULL }
+};
+
+static GtkRadioActionEntry mail_view_entries[] = {
+
+ /* This action represents the initial active mail view.
+ * It should not be visible in the UI, nor should it be
+ * possible to switch to it from another shell view. */
+ { "mail-view-initial",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ -1 },
+
+ { "mail-view-classic",
+ NULL,
+ N_("_Classic View"),
+ NULL,
+ N_("Show message preview below the message list"),
+ 0 },
+
+ { "mail-view-vertical",
+ NULL,
+ N_("_Vertical View"),
+ NULL,
+ N_("Show message preview alongside the message list"),
+ 1 }
+};
+
+static GtkRadioActionEntry mail_filter_entries[] = {
+
+ { "mail-filter-all-messages",
+ NULL,
+ N_("All Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_ALL_MESSAGES },
+
+ { "mail-filter-important-messages",
+ "emblem-important",
+ N_("Important Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_IMPORTANT_MESSAGES },
+
+ { "mail-filter-last-5-days-messages",
+ NULL,
+ N_("Last 5 Days Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_LAST_5_DAYS_MESSAGES },
+
+ { "mail-filter-messages-not-junk",
+ "mail-mark-notjunk",
+ N_("Messages Not Junk"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_MESSAGES_NOT_JUNK },
+
+ { "mail-filter-messages-with-attachments",
+ "mail-attachment",
+ N_("Messages with Attachments"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_MESSAGES_WITH_ATTACHMENTS },
+
+ { "mail-filter-messages-with-notes",
+ "evolution-memos",
+ N_("Messages with Notes"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_MESSAGES_WITH_NOTES },
+
+ { "mail-filter-no-label",
+ NULL,
+ N_("No Label"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_NO_LABEL },
+
+ { "mail-filter-read-messages",
+ "mail-read",
+ N_("Read Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_READ_MESSAGES },
+
+ { "mail-filter-unread-messages",
+ "mail-unread",
+ N_("Unread Messages"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_UNREAD_MESSAGES },
+
+ { "mail-filter-message-thread",
+ NULL,
+ N_("Message Thread"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_FILTER_MESSAGE_THREAD }
+
+};
+
+static GtkRadioActionEntry mail_search_entries[] = {
+
+ { "mail-search-advanced-hidden",
+ NULL,
+ N_("Advanced Search"),
+ NULL,
+ NULL,
+ MAIL_SEARCH_ADVANCED },
+
+ { "mail-search-body-contains",
+ NULL,
+ N_("Body contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_BODY_CONTAINS },
+
+ { "mail-search-free-form-expr",
+ NULL,
+ N_("Free form expression"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_FREE_FORM_EXPR },
+
+ { "mail-search-message-contains",
+ NULL,
+ N_("Message contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_MESSAGE_CONTAINS },
+
+ { "mail-search-recipients-contain",
+ NULL,
+ N_("Recipients contain"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_RECIPIENTS_CONTAIN },
+
+ { "mail-search-sender-contains",
+ NULL,
+ N_("Sender contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SENDER_CONTAINS },
+
+ { "mail-search-subject-contains",
+ NULL,
+ N_("Subject contains"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SUBJECT_CONTAINS },
+
+ { "mail-search-subject-or-addresses-contain",
+ NULL,
+ N_("Subject or Addresses contain"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SEARCH_SUBJECT_OR_ADDRESSES_CONTAIN }
+};
+
+static GtkRadioActionEntry mail_scope_entries[] = {
+
+ { "mail-scope-all-accounts",
+ NULL,
+ N_("All Accounts"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_ALL_ACCOUNTS },
+
+ { "mail-scope-current-account",
+ NULL,
+ N_("Current Account"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_CURRENT_ACCOUNT },
+
+ { "mail-scope-current-folder",
+ NULL,
+ N_("Current Folder"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_CURRENT_FOLDER },
+
+ { "mail-scope-current-folder-and-subfolders",
+ NULL,
+ N_("Current Folder and Subfolders"),
+ NULL,
+ NULL, /* XXX Add a tooltip! */
+ MAIL_SCOPE_CURRENT_FOLDER_AND_SUBFOLDERS }
+};
+
+void
+e_mail_shell_view_actions_init (EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EShell *shell;
+ EShellSearchbar *searchbar;
+ EActionComboBox *combo_box;
+ EMailView *mail_view;
+ GtkActionGroup *action_group;
+ GtkAction *action;
+ GSettings *settings;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+ shell = e_shell_window_get_shell (shell_window);
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ mail_view = e_mail_shell_content_get_mail_view (mail_shell_content);
+ searchbar = e_mail_shell_content_get_searchbar (mail_shell_content);
+
+ /* Mail Actions */
+ action_group = ACTION_GROUP (MAIL);
+ gtk_action_group_add_actions (
+ action_group, mail_entries,
+ G_N_ELEMENTS (mail_entries), mail_shell_view);
+ gtk_action_group_add_toggle_actions (
+ action_group, mail_toggle_entries,
+ G_N_ELEMENTS (mail_toggle_entries), mail_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, mail_view_entries,
+ G_N_ELEMENTS (mail_view_entries), -1,
+ G_CALLBACK (action_mail_view_cb), mail_shell_view);
+ gtk_action_group_add_radio_actions (
+ action_group, mail_search_entries,
+ G_N_ELEMENTS (mail_search_entries),
+ -1, NULL, NULL);
+ gtk_action_group_add_radio_actions (
+ action_group, mail_scope_entries,
+ G_N_ELEMENTS (mail_scope_entries),
+ MAIL_SCOPE_CURRENT_FOLDER, NULL, NULL);
+ e_action_group_add_popup_actions (
+ action_group, mail_popup_entries,
+ G_N_ELEMENTS (mail_popup_entries));
+
+ /* WebKitGTK does not support print preview, thus hide the option from the menu;
+ maybe it'll be supported in the future */
+ action = ACTION (MAIL_PRINT_PREVIEW);
+ gtk_action_set_visible (action, FALSE);
+
+ /* Search Folder Actions */
+ action_group = ACTION_GROUP (SEARCH_FOLDERS);
+ gtk_action_group_add_actions (
+ action_group, search_folder_entries,
+ G_N_ELEMENTS (search_folder_entries), mail_shell_view);
+
+ action = ACTION (MAIL_SCOPE_ALL_ACCOUNTS);
+ combo_box = e_shell_searchbar_get_scope_combo_box (searchbar);
+ e_action_combo_box_set_action (combo_box, GTK_RADIO_ACTION (action));
+ e_shell_searchbar_set_scope_visible (searchbar, TRUE);
+
+ /* Advanced Search Action */
+ action = ACTION (MAIL_SEARCH_ADVANCED_HIDDEN);
+ gtk_action_set_visible (action, FALSE);
+ e_shell_searchbar_set_search_option (
+ searchbar, GTK_RADIO_ACTION (action));
+
+ g_object_set (ACTION (MAIL_SEND_RECEIVE), "is-important", TRUE, NULL);
+
+ /* Bind GObject properties for GSettings keys. */
+
+ settings = e_util_ref_settings ("org.gnome.evolution.mail");
+
+ g_settings_bind (
+ settings, "show-deleted",
+ ACTION (MAIL_SHOW_DELETED), "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "show-junk",
+ ACTION (MAIL_SHOW_JUNK), "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "layout",
+ ACTION (MAIL_VIEW_VERTICAL), "current-value",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "enable-unmatched",
+ ACTION (MAIL_VFOLDER_UNMATCHED_ENABLE), "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ g_settings_bind (
+ settings, "show-attachment-bar",
+ ACTION (MAIL_ATTACHMENT_BAR), "active",
+ G_SETTINGS_BIND_DEFAULT);
+
+ if (e_shell_window_is_main_instance (shell_window)) {
+ g_settings_bind (
+ settings, "show-to-do-bar",
+ ACTION (MAIL_TO_DO_BAR), "active",
+ G_SETTINGS_BIND_DEFAULT);
+ } else {
+ g_settings_bind (
+ settings, "show-to-do-bar-sub",
+ ACTION (MAIL_TO_DO_BAR), "active",
+ G_SETTINGS_BIND_DEFAULT);
+ }
+
+ g_object_unref (settings);
+
+ /* Fine tuning. */
+
+ e_binding_bind_property (
+ ACTION (MAIL_THREADS_GROUP_BY), "active",
+ ACTION (MAIL_FOLDER_SELECT_THREAD), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_THREADS_GROUP_BY), "active",
+ ACTION (MAIL_FOLDER_SELECT_SUBTHREAD), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_THREADS_GROUP_BY), "active",
+ ACTION (MAIL_THREADS_COLLAPSE_ALL), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_THREADS_GROUP_BY), "active",
+ ACTION (MAIL_THREADS_EXPAND_ALL), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_PREVIEW), "active",
+ mail_view, "preview-visible",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_THREADS_GROUP_BY), "active",
+ mail_shell_content, "group-by-threads",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_PREVIEW), "active",
+ ACTION (MAIL_VIEW_CLASSIC), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_PREVIEW), "active",
+ ACTION (MAIL_VIEW_VERTICAL), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_SHOW_DELETED), "active",
+ mail_view, "show-deleted",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ ACTION (MAIL_SHOW_JUNK), "active",
+ mail_view, "show-junk",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ shell_backend, "busy",
+ ACTION (MAIL_STOP), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ /* Keep the sensitivity of "Create Search Folder from Search"
+ * in sync with "Save Search" so that its only selectable when
+ * showing search results. */
+ e_binding_bind_property (
+ ACTION (SEARCH_SAVE), "sensitive",
+ ACTION (MAIL_CREATE_SEARCH_FOLDER), "sensitive",
+ G_BINDING_SYNC_CREATE);
+
+ e_binding_bind_property (
+ shell, "online",
+ ACTION (MAIL_DOWNLOAD), "sensitive",
+ G_BINDING_SYNC_CREATE);
+}
+
+void
+e_mail_shell_view_update_search_filter (EMailShellView *mail_shell_view)
+{
+ EMailShellContent *mail_shell_content;
+ EShellView *shell_view;
+ EShellWindow *shell_window;
+ EShellBackend *shell_backend;
+ EShellSearchbar *searchbar;
+ EMailLabelListStore *label_store;
+ EMailBackend *backend;
+ EMailSession *session;
+ EActionComboBox *combo_box;
+ GtkActionGroup *action_group;
+ GtkRadioAction *radio_action;
+ GtkTreeIter iter;
+ GList *list;
+ GSList *group;
+ gboolean valid;
+ gint ii = 0;
+
+ g_return_if_fail (E_IS_MAIL_SHELL_VIEW (mail_shell_view));
+
+ shell_view = E_SHELL_VIEW (mail_shell_view);
+ shell_window = e_shell_view_get_shell_window (shell_view);
+ shell_backend = e_shell_view_get_shell_backend (shell_view);
+
+ backend = E_MAIL_BACKEND (shell_backend);
+ session = e_mail_backend_get_session (backend);
+ label_store = e_mail_ui_session_get_label_store (
+ E_MAIL_UI_SESSION (session));
+
+ action_group = ACTION_GROUP (MAIL_FILTER);
+ e_action_group_remove_all_actions (action_group);
+
+ /* Add the standard filter actions. No callback is needed
+ * because changes in the EActionComboBox are detected and
+ * handled by EShellSearchbar. */
+ gtk_action_group_add_radio_actions (
+ action_group, mail_filter_entries,
+ G_N_ELEMENTS (mail_filter_entries),
+ MAIL_FILTER_ALL_MESSAGES, NULL, NULL);
+
+ /* Retrieve the radio group from an action we just added. */
+ list = gtk_action_group_list_actions (action_group);
+ radio_action = GTK_RADIO_ACTION (list->data);
+ group = gtk_radio_action_get_group (radio_action);
+ g_list_free (list);
+
+ valid = gtk_tree_model_get_iter_first (
+ GTK_TREE_MODEL (label_store), &iter);
+
+ while (valid) {
+ GtkAction *action;
+ gchar *action_name;
+ gchar *stock_id;
+ gchar *label;
+
+ label = e_mail_label_list_store_get_name (
+ label_store, &iter);
+ stock_id = e_mail_label_list_store_get_stock_id (
+ label_store, &iter);
+
+ action_name = g_strdup_printf ("mail-filter-label-%d", ii);
+ radio_action = gtk_radio_action_new (
+ action_name, label, NULL, stock_id, ii);
+ g_free (action_name);
+
+ gtk_radio_action_set_group (radio_action, group);
+ group = gtk_radio_action_get_group (radio_action);
+
+ /* The action group takes ownership of the action. */
+ action = GTK_ACTION (radio_action);
+ gtk_action_group_add_action (action_group, action);
+ g_object_unref (radio_action);
+
+ g_free (label);
+ g_free (stock_id);
+
+ valid = gtk_tree_model_iter_next (
+ GTK_TREE_MODEL (label_store), &iter);
+ ii++;
+ }
+
+ mail_shell_content = mail_shell_view->priv->mail_shell_content;
+ searchbar = e_mail_shell_content_get_searchbar (mail_shell_content);
+ combo_box = e_shell_searchbar_get_filter_combo_box (searchbar);
+
+ e_shell_view_block_execute_search (shell_view);
+
+ /* Use any action in the group; doesn't matter which. */
+ e_action_combo_box_set_action (combo_box, radio_action);
+
+ ii = MAIL_FILTER_MESSAGES_NOT_JUNK;
+ e_action_combo_box_add_separator_after (combo_box, ii);
+
+ ii = MAIL_FILTER_READ_MESSAGES;
+ e_action_combo_box_add_separator_after (combo_box, ii);
+
+ e_shell_view_unblock_execute_search (shell_view);
+}
+
diff -urN a/src/modules/webkit-inspector/evolution-webkit-inspector.c b/src/modules/webkit-inspector/evolution-webkit-inspector.c
--- a/src/modules/webkit-inspector/evolution-webkit-inspector.c 2025-06-07 14:20:30.299070278 -0700
+++ b/src/modules/webkit-inspector/evolution-webkit-inspector.c 2025-06-07 14:27:24.063673134 -0700
@@ -31,7 +31,7 @@
(G_TYPE_CHECK_INSTANCE_CAST \
((obj), E_TYPE_WEBKIT_INSPECTOR, EWebKitInspector))
-/* <Control>+<Shift>+I or <Control>+<Shift>+D */
+/* <Super>+<Shift>+I or <Super>+<Shift>+D */
#define WEBKIT_INSPECTOR_MOD (GDK_CONTROL_MASK | GDK_SHIFT_MASK)
#define WEBKIT_INSPECTOR_KEY1 (GDK_KEY_I)
#define WEBKIT_INSPECTOR_KEY2 (GDK_KEY_D)
diff -urN a/src/plugins/external-editor/external-editor.c b/src/plugins/external-editor/external-editor.c
--- a/src/plugins/external-editor/external-editor.c 2025-06-07 14:20:57.387397894 -0700
+++ b/src/plugins/external-editor/external-editor.c 2025-06-07 14:27:24.063673134 -0700
@@ -487,7 +487,7 @@
{ "ExternalEditor",
NULL,
N_("Compose in External Editor"),
- "<Shift><Control>e",
+ "<Shift><Super>e",
N_("Compose in External Editor"),
G_CALLBACK (launch_editor) }
};
diff -urN a/src/plugins/templates/templates.c b/src/plugins/templates/templates.c
--- a/src/plugins/templates/templates.c 2025-06-07 14:20:57.391397942 -0700
+++ b/src/plugins/templates/templates.c 2025-06-07 14:27:24.063673134 -0700
@@ -906,7 +906,7 @@
{ "template",
"document-save",
N_("Save as _Template"),
- "<Shift><Control>t",
+ "<Shift><Super>t",
N_("Save as Template"),
G_CALLBACK (action_template_cb) }
};
diff -urN a/src/shell/e-shell-window-actions.c b/src/shell/e-shell-window-actions.c
--- a/src/shell/e-shell-window-actions.c 2025-06-07 14:20:57.395397990 -0700
+++ b/src/shell/e-shell-window-actions.c 2025-06-07 14:27:24.063673134 -0700
@@ -921,21 +921,21 @@
{ "close",
"window-close",
N_("_Close Window"),
- "<Control>w",
+ "<Super>w",
N_("Close this window"),
G_CALLBACK (action_close_cb) },
{ "close-window-menu",
"window-close",
N_("_Close"),
- "<Control>w",
+ "<Super>w",
N_("Close this window"),
G_CALLBACK (action_close_cb) },
{ "close-window",
"window-close",
N_("_Close Window"),
- "<Control>w",
+ "<Super>w",
N_("Close this window"),
G_CALLBACK (action_close_cb) },
@@ -949,14 +949,14 @@
{ "copy-clipboard",
"edit-copy",
N_("_Copy"),
- "<Control>c",
+ "<Super>c",
N_("Copy the selection"),
NULL }, /* Handled by EFocusTracker */
{ "cut-clipboard",
"edit-cut",
N_("Cu_t"),
- "<Control>x",
+ "<Super>x",
N_("Cut the selection"),
NULL }, /* Handled by EFocusTracker */
@@ -977,14 +977,14 @@
{ "new-window",
"window-new",
N_("New _Window"),
- "<Control><Shift>w",
+ "<Super><Shift>w",
N_("Create a new window displaying this view"),
G_CALLBACK (action_new_window_cb) },
{ "paste-clipboard",
"edit-paste",
N_("_Paste"),
- "<Control>v",
+ "<Super>v",
N_("Paste the clipboard"),
NULL }, /* Handled by EFocusTracker */
@@ -998,14 +998,14 @@
{ "preferences",
"preferences-system",
N_("_Preferences"),
- "<Control><Shift>s",
+ "<Super><Shift>s",
N_("Configure Evolution"),
G_CALLBACK (action_preferences_cb) },
{ "quit",
"application-exit",
N_("_Quit"),
- "<Control>q",
+ "<Super>q",
N_("Exit the program"),
G_CALLBACK (action_quit_cb) },
@@ -1026,7 +1026,7 @@
{ "search-clear",
"edit-clear",
N_("_Clear"),
- "<Control><Shift>q",
+ "<Super><Shift>q",
N_("Clear the current search parameters"),
G_CALLBACK (action_search_clear_cb) },
@@ -1040,7 +1040,7 @@
{ "search-options",
"edit-find",
N_("_Find"),
- "<Control>f",
+ "<Super>f",
N_("Click here to change the search type"),
G_CALLBACK (action_search_options_cb) },
@@ -1061,14 +1061,14 @@
{ "select-all",
"edit-select-all",
N_("Select _All"),
- "<Control>a",
+ "<Super>a",
N_("Select all text"),
NULL }, /* Handled by EFocusTracker */
{ "shortcuts",
NULL,
N_("_Keyboard Shortcuts"),
- "<Control><Shift>question",
+ "<Super><Shift>question",
N_("Show keyboard shortcuts"),
G_CALLBACK (action_shortcuts_cb) },
@@ -1717,7 +1717,7 @@
/* The first nine views have accelerators Ctrl+(1-9). */
if (ii < 10)
- accelerator = g_strdup_printf ("<Control>%d", ii);
+ accelerator = g_strdup_printf ("<Super>%d", ii);
else
accelerator = g_strdup ("");