Bug 463402 - Remove E-Mail notification dialogs - Part 3: dialogs;r=philipp
authorMakeMyDay <makemyday@gmx-topmail.de>
Sun, 11 Mar 2018 10:40:15 +0100
changeset 31277 f5138ae6a7280bdfab3eea5ee5db9fedd63504e9
parent 31276 5ae749bd1ed8dbb33e1995c60fdc13152c066752
child 31278 f4e55986c49672657d09a882f4f58aa356712f2b
push id383
push userclokep@gmail.com
push dateMon, 07 May 2018 21:52:48 +0000
reviewersphilipp
bugs463402
Bug 463402 - Remove E-Mail notification dialogs - Part 3: dialogs;r=philipp - added a toolbar to summary dialog if not read-only to trigger status updates with or without notifying - displaying the calendar name in summary dialog (without an option to change the calendar) - dynamic replacement of toolbar and menu items depending on existnace of attendees in event dialog/tab
calendar/base/content/dialogs/calendar-summary-dialog.js
calendar/base/content/dialogs/calendar-summary-dialog.xul
calendar/base/themes/common/dialogs/calendar-event-dialog.css
calendar/lightning/content/lightning-item-iframe.js
calendar/lightning/content/lightning-item-panel.js
calendar/lightning/content/messenger-overlay-sidebar.js
calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
--- a/calendar/base/content/dialogs/calendar-summary-dialog.js
+++ b/calendar/base/content/dialogs/calendar-summary-dialog.js
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* exported onLoad, onAccept, onCancel, updatePartStat, browseDocument,
- *          sendMailToOrganizer, openAttachment
+/* exported onLoad, onUnload, onAccept, onCancel, updatePartStat, browseDocument,
+ *          sendMailToOrganizer, openAttachment, reply
  */
 
 ChromeUtils.import("resource://calendar/modules/calUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calItipUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calAlarmUtils.jsm");
 ChromeUtils.import("resource://calendar/modules/calRecurrenceUtils.jsm");
 
 /**
@@ -46,17 +46,17 @@ function onLoad() {
     if (cal.item.isEvent(item)) {
         setDialogId(document.documentElement, "calendar-event-summary-dialog");
     } else if (cal.item.isToDo(item)) {
         setDialogId(document.documentElement, "calendar-task-summary-dialog");
     }
 
     window.attendees = item.getAttendees({});
 
-    let calendar = cal.wrapInstance(item.calendar, Components.interfaces.calISchedulingSupport);
+    let calendar = cal.wrapInstance(item.calendar, Ci.calISchedulingSupport);
     window.readOnly = !(cal.acl.isCalendarWritable(calendar) &&
                         (cal.acl.userCanModifyItem(item) ||
                          (calendar &&
                           item.calendar.isInvitation(item) &&
                           cal.userCanRespondToInvitation(item))));
     if (!window.readOnly && calendar) {
         let attendee = calendar.getInvitedAttendee(item);
         if (attendee) {
@@ -68,32 +68,26 @@ function onLoad() {
 
             window.attendee = attendee.clone();
             // Since we don't have API to update an attendee in place, remove
             // and add again. Also, this is needed if the attendee doesn't exist
             // (i.e REPLY on a mailing list)
             item.removeAttendee(attendee);
             item.addAttendee(window.attendee);
 
-            // make partstat NEEDS-ACTION only available as a option to change to,
-            // if the user hasn't ever made a decision prior to opening the dialog
-            let partStat = window.attendee.participationStatus || "NEEDS-ACTION";
-            if (partStat == "NEEDS-ACTION" && cal.item.isEvent(item)) {
-                document.getElementById("item-participation-needs-action").removeAttribute("hidden");
-            }
+            window.responseMode = "USER";
         }
     }
 
     document.getElementById("item-title").value = item.title;
 
+    document.getElementById("item-calendar").value = calendar.name;
     document.getElementById("item-start-row").Item = item;
     document.getElementById("item-end-row").Item = item;
 
-    updateInvitationStatus();
-
     // show reminder if this item is *not* readonly.
     // this case happens for example if this is an invitation.
     let argCalendar = window.arguments[0].calendarEvent.calendar;
     let supportsReminders =
         (argCalendar.getProperty("capabilities.alarms.oninvitations.supported") !== false);
     if (!window.readOnly && supportsReminders) {
         document.getElementById("reminder-row").removeAttribute("hidden");
         loadReminders(window.calendarItem.getAlarms({}));
@@ -203,82 +197,154 @@ function onLoad() {
     }
     // If this item is read only we remove the 'cancel' button as users
     // can't modify anything, thus we go ahead with an 'ok' button only.
     if (window.readOnly) {
         document.documentElement.getButton("cancel").setAttribute("collapsed", "true");
         document.documentElement.getButton("accept").focus();
     }
 
+    // disbale default controls
+    let accept = document.documentElement.getButton("accept");
+    let cancel = document.documentElement.getButton("cancel");
+    accept.setAttribute("collapsed", "true");
+    cancel.setAttribute("collapsed", "true");
+    cancel.parentNode.setAttribute("collapsed", "true");
+
+    updateToolbar();
+
+    if (typeof ToolbarIconColor !== "undefined") {
+        ToolbarIconColor.init();
+    }
+
     window.focus();
     opener.setCursor("auto");
 }
 
+function onUnload() {
+    if (typeof ToolbarIconColor !== "undefined") {
+        ToolbarIconColor.uninit();
+    }
+}
+
 /**
  * Saves any changed information to the item.
  *
  * @return      Returns true if the dialog
  */
 function onAccept() {
     dispose();
     if (window.readOnly) {
         return true;
     }
+    // let's make sure we have a response mode defined
+    let resp = window.responseMode || "USER";
+    let respMode = { responseMode: Ci.calIItipItem[resp] };
+
     let args = window.arguments[0];
     let oldItem = args.calendarEvent;
     let newItem = window.calendarItem;
     let calendar = newItem.calendar;
     saveReminder(newItem);
     adaptScheduleAgent(newItem);
-    args.onOk(newItem, calendar, oldItem);
+    args.onOk(newItem, calendar, oldItem, null, respMode);
     window.calendarItem = newItem;
     return true;
 }
 
 /**
  * Called when closing the dialog and any changes should be thrown away.
  */
 function onCancel() {
     dispose();
     return true;
 }
 
 /**
- * Sets the dialog's invitation status dropdown to the value specified by the
- * user's invitation status.
+ * Updates the user's partstat, sends a notification if requested and closes the
+ * dialog
+ *
+ * @param {string}  aResponse  a literal of one of the response modes defined
+ *                               in calIItipItem (like 'NONE')
+ * @param {string}  aPartStat  (optional) a partstat as per RfC5545
  */
-function updateInvitationStatus() {
-    if (!window.readOnly) {
-        if (window.attendee) {
-            let invitationRow = document.getElementById("invitation-row");
-            invitationRow.removeAttribute("hidden");
-            let statusElement = document.getElementById("item-participation");
-            statusElement.value = window.attendee.participationStatus || "NEEDS-ACTION";
-        }
-    }
-}
-
-/**
- * When the summary dialog is showing an invitation, this function updates the
- * user's invitation status from the value chosen in the dialog.
- */
-function updatePartStat() {
-    let statusElement = document.getElementById("item-participation");
-    if (window.attendee) {
-        let item = window.arguments[0];
-        let aclEntry = item.calendar.aclEntry;
+function reply(aResponse, aPartStat=null) {
+    if (aPartStat && window.attendee) {
+        let aclEntry = window.calendarItem.calendar.aclEntry;
         if (aclEntry) {
             let userAddresses = aclEntry.getUserAddresses({});
             if (userAddresses.length > 0 &&
                 !cal.attendeeMatchesAddresses(window.attendee, userAddresses)) {
                 window.attendee.setProperty("SENT-BY", "mailto:" + userAddresses[0]);
             }
         }
+        window.attendee.participationStatus = aPartStat;
+        updateToolbar();
+    }
+    saveAndClose(aResponse);
+}
 
-        window.attendee.participationStatus = statusElement.value;
+/**
+ * Stores the event in the calendar and closes the dialog
+ *
+ * @param {string}  aResponse  a literal of one of the response modes defined
+ *                               in calIItipItem (like 'NONE')
+ */
+function saveAndClose(aResponse="NONE") {
+    // we use NONE as default since we don't want to send out notifications if
+    // the user just updates the reminder settings
+    window.responseMode = aResponse;
+    document.documentElement.acceptDialog();
+}
+
+function updateToolbar() {
+    if (window.readOnly) {
+        document.getElementById("summary-toolbar").setAttribute("hidden", "true");
+        return;
+    }
+
+    let replyButtons = document.getElementsByAttribute("type", "menu-button");
+    for (let element of replyButtons) {
+        element.removeAttribute("hidden");
+        if (window.attendee) {
+            // we disable the control which represents the current partstat
+            let status = window.attendee.participationStatus || "NEEDS-ACTION";
+            if (element.getAttribute("value") == status) {
+                element.setAttribute("disabled", "true");
+            } else {
+                element.removeAttribute("disabled");
+            }
+        }
+    }
+
+    let notificationBox = document.getElementById("status-notification");
+    if (window.attendee) {
+        // we display a notification about the users partstat
+        let partStat = window.attendee.participationStatus || "NEEDS-ACTION";
+        let type = cal.item.isEvent(window.calendarItem) ? "event" : "task";
+
+        let msgStr = {
+            ACCEPTED: type + "Accepted",
+            COMPLETED: "taskCompleted",
+            DECLINED: type + "Declined",
+            DELEGATED: type + "Delegated",
+            TENTATIVE:  type + "Tentative"
+        };
+        // this needs to be noted differently to get accepted the '-' in the key
+        msgStr["NEEDS-ACTION"] = type + "NeedsAction";
+        msgStr["IN-PROGRESS"] = "taskInProgress";
+
+        let msg = cal.calGetString("calendar-event-dialog", msgStr[partStat]);
+
+        notificationBox.appendNotification(msg,
+                                           "statusNotification",
+                                           null,
+                                           notificationBox.PRIORITY_INFO_MEDIUM);
+    } else {
+        notificationBox.removeAllNotifications();
     }
 }
 
 /**
  * Updates the dialog w.r.t recurrence, i.e shows a text describing the item's
  * recurrence)
  */
 function updateRepeatDetails() {
@@ -391,13 +457,13 @@ function openAttachment(aAttachmentId) {
     if (!aAttachmentId) {
         return;
     }
     let args = window.arguments[0];
     let item = args.calendarEvent;
     let attachments = item.getAttachments({})
                           .filter(aAttachment => aAttachment.hashId == aAttachmentId);
     if (attachments.length && attachments[0].uri && attachments[0].uri.spec != "about:blank") {
-        let externalLoader = Components.classes["@mozilla.org/uriloader/external-protocol-service;1"]
-                                       .getService(Components.interfaces.nsIExternalProtocolService);
+        let externalLoader = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+                             .getService(Ci.nsIExternalProtocolService);
         externalLoader.loadURI(attachments[0].uri);
     }
 }
--- a/calendar/base/content/dialogs/calendar-summary-dialog.xul
+++ b/calendar/base/content/dialogs/calendar-summary-dialog.xul
@@ -6,16 +6,18 @@
 
 <?xml-stylesheet type="text/css" href="chrome://global/skin/global.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar-common/skin/calendar-alarms.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar-common/skin/calendar-attendees.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/skin/calendar-event-dialog.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar-common/skin/dialogs/calendar-event-dialog.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/datetimepickers/datetimepickers.css"?>
 <?xml-stylesheet type="text/css" href="chrome://calendar/content/calendar-bindings.css"?>
+<?xml-stylesheet type="text/css" href="chrome://messenger/skin/primaryToolbar.css"?>
+<?xml-stylesheet type="text/css" href="chrome://messenger/skin/messenger.css"?>
 
 <!DOCTYPE dialog [
   <!ENTITY % globalDTD SYSTEM "chrome://calendar/locale/global.dtd" >
   <!ENTITY % calendarDTD SYSTEM "chrome://calendar/locale/calendar.dtd" >
   <!ENTITY % dialogDTD SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd" >
   <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
   %globalDTD;
   %calendarDTD;
@@ -23,16 +25,17 @@
   %brandDTD;
 ]>
 
 <!-- Dialog id is changed during excution to allow different Window-icons
      on this dialog. document.loadOverlay() will not work on this one. -->
 <dialog id="calendar-summary-dialog"
         windowtype="Calendar:EventSummaryDialog"
         onload="onLoad()"
+        onunload="onUnload()"
         ondialogaccept="return onAccept();"
         ondialogcancel="return onCancel();"
         onresize="rearrangeAttendees();"
         persist="screenX screenY width height"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
   <!-- Javascript includes -->
   <script type="application/javascript"
@@ -41,30 +44,112 @@
           src="chrome://calendar/content/calendar-dialog-utils.js"/>
   <script type="application/javascript"
           src="chrome://calendar/content/calendar-ui-utils.js"/>
   <script type="application/javascript"
           src="chrome://calendar/content/calendar-item-editing.js"/>
   <script type="application/javascript"
           src="chrome://calendar/content/calApplicationUtils.js"/>
 
+  <toolbox id="summary-toolbox"
+           class="mail-toolbox"
+           mode="full"
+           defaultmode="full"
+           iconsize="small"
+           defaulticonsize="small"
+           labelalign="end"
+           defaultlabelalign="end">
+    <toolbar id="summary-toolbar"
+             toolboxid="summary-toolbox"
+             class="chromeclass-toolbar"
+             customizable="false"
+             labelalign="end"
+             defaultlabelalign="end">
+      <toolbarbutton id="saveandcloseButton"
+                     tooltiptext="&summary.dialog.saveclose.tooltiptext;"
+                     label="&summary.dialog.saveclose.label;"
+                     oncommand="saveAndClose();"
+                     class="cal-event-toolbarbutton toolbarbutton-1 saveandcloseButton"/>
+      <toolbarbutton id="acceptButton"
+                     tooltiptext="&summary.dialog.accept.tooltiptext;"
+                     label="&summary.dialog.accept.label;"
+                     oncommand="if (event.target.id == this.id) reply('AUTO', 'ACCEPTED');"
+                     value="ACCEPTED"
+                     type="menu-button"
+                     class="cal-event-toolbarbutton toolbarbutton-1 replyButton">
+        <menupopup id="acceptDropdown">
+          <menuitem id="acceptButton_Send"
+                    tooltiptext="&summary.dialog.send.tooltiptext;"
+                    label="&summary.dialog.send.label;"
+                    oncommand="reply('AUTO', 'ACCEPTED');"/>
+          <menuitem id="acceptButton_DontSend"
+                    tooltiptext="&summary.dialog.dontsend.tooltiptext;"
+                    label="&summary.dialog.dontsend.label;"
+                    oncommand="reply('NONE', 'ACCEPTED');"/>
+        </menupopup>
+      </toolbarbutton>
+      <toolbarbutton id="tentativeButton"
+                     tooltiptext="&summary.dialog.tentative.tooltiptext;"
+                     label="&summary.dialog.tentative.label;"
+                     oncommand="if (event.target.id == this.id) reply('AUTO', 'TENTATIVE');"
+                     value="TENTATIVE"
+                     type="menu-button"
+                     class="cal-event-toolbarbutton toolbarbutton-1 replyButton">
+        <menupopup id="tentativeDropdown">
+          <menuitem id="tenatativeButton_Send"
+                    tooltiptext="&summary.dialog.send.tooltiptext;"
+                    label="&summary.dialog.send.label;"
+                    oncommand="reply('AUTO', 'TENTATIVE');"/>
+          <menuitem id="tenativeButton_DontSend"
+                    tooltiptext="&summary.dialog.dontsend.tooltiptext;"
+                    label="&summary.dialog.dontsend.label;"
+                    oncommand="reply('NONE', 'TENTATIVE');"/>
+        </menupopup>
+      </toolbarbutton>
+      <toolbarbutton id="declineButton"
+                     tooltiptext="&summary.dialog.decline.tooltiptext;"
+                     label="&summary.dialog.decline.label;"
+                     oncommand="if (event.target.id == this.id) reply('AUTO', 'DECLINED');"
+                     type="menu-button"
+                     value="DECLINED"
+                     class="cal-event-toolbarbutton toolbarbutton-1 replyButton">
+        <menupopup id="declineDropdown">
+          <menuitem id="declineButton_Send"
+                    tooltiptext="&summary.dialog.send.tooltiptext;"
+                    label="&summary.dialog.send.label;"
+                    oncommand="reply('AUTO', 'DECLINED');"/>
+          <menuitem id="declineButton_DontSend"
+                    tooltiptext="&summary.dialog.dontsend.tooltiptext;"
+                    label="&summary.dialog.dontsend.label;"
+                    oncommand="reply('NONE', 'DECLINED');"/>
+        </menupopup>
+      </toolbarbutton>
+    </toolbar>
+  </toolbox>
+
+  <notificationbox id="status-notification"/>
+
   <!-- General -->
   <box id="item-general-box" orient="vertical">
     <calendar-caption label="&read.only.general.label;"/>
     <box orient="horizontal">
       <grid flex="1">
         <columns>
           <column/>
           <column flex="1"/>
         </columns>
         <rows>
           <row align="center">
             <label value="&read.only.title.label;"/>
             <textbox id="item-title" class="selectable-label plain" readonly="true"/>
           </row>
+          <row align="center">
+            <label value="&read.only.calendar.label;"/>
+            <textbox id="item-calendar" class="selectable-label plain" readonly="true"/>
+          </row>
           <row class="item-date-row"
                id="item-start-row"
                mode="start"
                taskStartLabel="&read.only.task.start.label;"
                eventStartLabel="&read.only.event.start.label;"
                align="center"/>
           <row class="item-date-row"
                id="item-end-row"
@@ -103,30 +188,16 @@
             <label value="&newevent.status.confirmed.label;" hidden="true" status="CONFIRMED"/>
             <label value="&newevent.eventStatus.cancelled.label;" hidden="true" status="CANCELLED"/>
             <label value="&newevent.todoStatus.cancelled.label;" hidden="true" status="CANCELLED"/>
             <label value="&newevent.status.needsaction.label;" hidden="true" status="NEEDS-ACTION"/>
             <label value="&newevent.status.inprogress.label;" hidden="true" status="IN-PROCESS"/>
             <label value="&newevent.status.completed.label;" hidden="true" status="COMPLETED"/>
           </row>
           <separator id="item-main-separator" flex="1" class="groove" hidden="true"/>
-          <row id="invitation-row" hidden="true" align="center">
-            <label value="&read.only.reply.label;" control="item-participation"/>
-            <hbox pack="start">
-              <menulist id="item-participation" oncommand="updatePartStat()">
-                <menupopup>
-                  <menuitem label="&read.only.accept.label;" value="ACCEPTED"/>
-                  <menuitem label="&read.only.tentative.label;" value="TENTATIVE"/>
-                  <menuitem label="&read.only.decline.label;" value="DECLINED"/>
-                  <menuitem label="&read.only.needs.action.label;" value="NEEDS-ACTION"
-                            hidden="true" id="item-participation-needs-action"/>
-                </menupopup>
-              </menulist>
-            </hbox>
-          </row>
           <row id="reminder-row" hidden="true" align="center">
             <label value="&read.only.reminder.label;" control="item-alarm"/>
               <hbox id="event-grid-alarm-picker-box"
                     align="center">
                 <menulist id="item-alarm"
                           disable-on-readonly="true"
                           oncommand="updateReminder()">
                   <menupopup id="item-alarm-menupopup">
--- a/calendar/base/themes/common/dialogs/calendar-event-dialog.css
+++ b/calendar/base/themes/common/dialogs/calendar-event-dialog.css
@@ -37,32 +37,42 @@ dialog[systemcolors] {
 /*--------------------------------------------------------------------
  *   Event dialog toolbar buttons
  *-------------------------------------------------------------------*/
 
 #button-save {
     list-style-image: url(chrome://calendar-common/skin/icons/save.svg);
 }
 
+#button-save[mode="send"] {
+    list-style-image: url("chrome://messenger/skin/icons/send.svg");
+}
+
+#saveandcloseButton,
 #button-saveandclose {
     list-style-image: url(chrome://calendar-common/skin/icons/save-close.svg);
 }
 
+#button-saveandclose[mode="send"] {
+    list-style-image: url("chrome://messenger/skin/icons/send.svg");
+}
+
 #button-attendees {
     list-style-image: url(chrome://calendar-common/skin/icons/address.svg);
 }
 
 #button-privacy {
     list-style-image: url(chrome://calendar-common/skin/icons/security.svg);
 }
 
 #button-url {
     list-style-image: url(chrome://calendar-common/skin/icons/attach.svg);
 }
 
+#deleteButton,
 #button-delete.cal-event-toolbarbutton {
     /* !important to override the SM #button-delete states */
     list-style-image: url(chrome://calendar-common/skin/icons/delete.svg) !important;
     -moz-image-region: auto !important;
 }
 
 #button-priority {
     list-style-image: url(chrome://calendar-common/skin/icons/priority.svg);
@@ -75,16 +85,28 @@ dialog[systemcolors] {
 #button-freebusy {
     list-style-image: url(chrome://calendar-common/skin/icons/freebusy.svg);
 }
 
 #button-timezones {
     list-style-image: url(chrome://calendar-common/skin/icons/timezones.svg);
 }
 
+#acceptButton {
+    list-style-image: url(chrome://calendar-common/skin/icons/complete.svg);
+}
+
+#tentativeButton {
+    list-style-image: url(chrome://calendar-common/skin/icons/tentative.svg);
+}
+
+#declineButton {
+    list-style-image: url(chrome://calendar-common/skin/icons/decline.svg);
+}
+
 /*--------------------------------------------------------------------
  *   Event dialog counter box section
  *-------------------------------------------------------------------*/
 
 #counter-proposal-box {
     background-color: rgb(186, 238, 255);
     border-bottom: 1px solid #444444;
 }
@@ -572,16 +594,23 @@ freebusy-day > box {
 .checkbox-no-label > .checkbox-label-box {
     display: none;
 }
 
 /*--------------------------------------------------------------------
  *   Event summary dialog
  *-------------------------------------------------------------------*/
 
+#summary-toolbox {
+    margin-top: -8px;
+    margin-inline-start: -8px;
+    margin-inline-end: -10px;
+    margin-bottom: 10px;
+}
+
 #calendar-summary-dialog,
 #calendar-event-summary-dialog,
 #calendar-task-summary-dialog {
     min-width: 35em;
 }
 
 #calendar-summary-dialog #item-attachment-cell,
 #calendar-event-summary-dialog #item-attachment-cell,
--- a/calendar/lightning/content/lightning-item-iframe.js
+++ b/calendar/lightning/content/lightning-item-iframe.js
@@ -244,16 +244,19 @@ function receiveMessage(aEvent) {
                 command: "replyToClosingWindowWithTabs",
                 response: response
             });
             break;
         }
         case "attachFileByAccountKey":
             attachFileByAccountKey(aEvent.data.accountKey);
             break;
+        case "triggerUpdateSaveControls":
+            updateParentSaveControls();
+            break;
     }
 }
 
 /**
  * Sets up the event dialog from the window arguments, also setting up all
  * dialog controls from the window's item.
  */
 function onLoad() {
@@ -824,16 +827,17 @@ function loadDialog(aItem) {
 
 /**
  * Enables/disables undiscloseCheckbox on (un)checking notifyCheckbox
  */
 function changeUndiscloseCheckboxStatus() {
     let notifyCheckbox = document.getElementById("notify-attendees-checkbox");
     let undiscloseCheckbox = document.getElementById("undisclose-attendees-checkbox");
     undiscloseCheckbox.disabled = (!notifyCheckbox.checked);
+    updateParentSaveControls();
 }
 
 /**
  * Loads the item's categories into the category panel
  *
  * @param aItem     The item to load into the category panel
  */
 function loadCategories(aItem) {
@@ -3004,17 +3008,21 @@ function onCommandSave(aIsClosing) {
             }
             // this triggers the update of the imipbar in case this is a rescheduling case
             if (window.counterProposal && window.counterProposal.onReschedule) {
                 window.counterProposal.onReschedule();
             }
         },
         onGetResult: function() {}
     };
-    window.onAcceptCallback(item, calendar, originalItem, listener);
+    let resp = document.getElementById("notify-attendees-checkbox").checked
+             ? Components.interfaces.calIItipItem.AUTO
+             : Components.interfaces.calIItipItem.NONE;
+    let extResponse = { autoResponse: resp };
+    window.onAcceptCallback(item, calendar, originalItem, listener, extResponse);
 }
 
 /**
  * This function is called when the user chooses to delete an Item
  * from the Event/Task dialog
  *
  */
 function onCommandDeleteItem() {
@@ -3552,16 +3560,35 @@ function updateAttendees() {
         } else {
             setBooleanAttribute("item-organizer-row", "collapsed", true);
         }
         setupAttendees();
     } else {
         attendeeTab.setAttribute("collapsed", "true");
         attendeePanel.setAttribute("collapsed", "true");
     }
+    updateParentSaveControls();
+}
+
+/**
+ * Update the save controls in parent context depending on the whether attendees
+ * exist for this event and notifying is enabled
+ */
+function updateParentSaveControls() {
+    let mode = cal.item.isEvent(window.calendarItem) &&
+               window.organizer &&
+               window.organizer.id &&
+               window.attendees &&
+               window.attendees.length > 0 &&
+               document.getElementById("notify-attendees-checkbox").checked;
+
+    sendMessage({
+        command: "updateSaveControls",
+        argument: { sendNotSave: mode }
+    });
 }
 
 /**
  * This function updates dialog controls related to recurrence, in this case the
  * text describing the recurrence rule.
  */
 function updateRepeatDetails() {
     // Don't try to show the details text for
--- a/calendar/lightning/content/lightning-item-panel.js
+++ b/calendar/lightning/content/lightning-item-panel.js
@@ -142,19 +142,24 @@ function receiveMessage(aEvent) {
         case "removeDisableAndCollapseOnReadonly":
             removeDisableAndCollapseOnReadonly();
             break;
         case "setElementAttribute": {
             let arg = aEvent.data.argument;
             setElementValue(arg.id, arg.value, arg.attribute);
             break;
         }
-        case "loadCloudProviders":
+        case "loadCloudProviders": {
             loadCloudProviders(aEvent.data.items);
             break;
+        }
+        case "updateSaveControls": {
+            updateSaveControls(aEvent.data.argument.sendNotSave);
+            break;
+        }
     }
 }
 
 window.addEventListener("message", receiveMessage);
 
 /**
  * Send an asynchronous message to an iframe.  Additional properties of
  * aMessage are generally arguments that will be passed to the function
@@ -1094,8 +1099,87 @@ function loadCloudProviders(aItemObjects
  * Send a message to attach a file using a given cloud provider,
  * to be identified by the cloud provider's accountKey.
  *
  * @param {string} aAccountKey  The accountKey for a cloud provider
  */
 function attachFileByAccountKey(aAccountKey) {
     sendMessage({ command: "attachFileByAccountKey", accountKey: aAccountKey });
 }
+
+/**
+ * Updates the save controls depending on whether the event has attendees
+ * @param {boolean} aSendNotSave
+ */
+function updateSaveControls(aSendNotSave) {
+    if (window.calItemSaveControls &&
+        window.calItemSaveControls.state == aSendNotSave) {
+        return;
+    }
+
+    let saveBtn = document.getElementById("button-save");
+    let saveandcloseBtn = document.getElementById("button-saveandclose");
+    let saveMenu = document.getElementById("item-save-menuitem") ||
+                   document.getElementById("ltnSave");
+    let saveandcloseMenu = document.getElementById("item-saveandclose-menuitem") ||
+                           document.getElementById("ltnSaveAndClose");
+
+    // we store the initial label and tooltip values to be able to reset later
+    if (!window.calItemSaveControls) {
+        window.calItemSaveControls = {
+            state: false,
+            saveMenu: { label: saveMenu.label },
+            saveandcloseMenu: { label: saveandcloseMenu.label },
+            saveBtn: null,
+            saveandcloseBtn: null
+        };
+        // we need to check for each button whether it exists since toolbarbuttons
+        // can be removed by customizing
+        if (saveBtn) {
+            window.window.calItemSaveControls.saveBtn = {
+                label: saveBtn.label,
+                tooltiptext: saveBtn.tooltip
+            };
+        }
+        if (saveandcloseBtn) {
+            window.window.calItemSaveControls.saveandcloseBtn = {
+                label: saveandcloseBtn.label,
+                tooltiptext: saveandcloseBtn.tooltip
+            };
+        }
+    }
+
+    // we update labels and tooltips but leave accesskeys as they are
+    window.calItemSaveControls.state = aSendNotSave;
+    if (aSendNotSave) {
+        if (saveBtn) {
+            saveBtn.label = cal.calGetString("calendar-event-dialog",
+                                             "saveandsendButtonLabel");
+            saveBtn.tooltiptext = cal.calGetString("calendar-event-dialog",
+                                                   "saveandsendButtonTooltip");
+            saveBtn.setAttribute("mode", "send");
+        }
+        if (saveandcloseBtn) {
+            saveandcloseBtn.label = cal.calGetString("calendar-event-dialog",
+                                                     "sendandcloseButtonLabel");
+            saveandcloseBtn.tooltiptext = cal.calGetString("calendar-event-dialog",
+                                                           "sendandcloseButtonTooltip");
+            saveandcloseBtn.setAttribute("mode", "send");
+        }
+        saveMenu.label = cal.calGetString("calendar-event-dialog",
+                                          "saveandsendMenuLabel");
+        saveandcloseMenu.label = cal.calGetString("calendar-event-dialog",
+                                                  "sendandcloseMenuLabel");
+    } else {
+        if (saveBtn) {
+            saveBtn.label = window.calItemSaveControls.saveBtn.label;
+            saveBtn.tooltiptext = window.calItemSaveControls.saveBtn.tooltip;
+            saveBtn.removeAttribute("mode");
+        }
+        if (saveandcloseBtn) {
+            saveandcloseBtn.label = window.calItemSaveControls.saveandcloseBtn.label;
+            saveandcloseBtn.tooltiptext = window.calItemSaveControls.saveandcloseBtn.tooltip;
+            saveandcloseBtn.removeAttribute("mode");
+        }
+        saveMenu.label = window.calItemSaveControls.saveMenu.label;
+        saveandcloseMenu.label = window.calItemSaveControls.saveandcloseMenu.label;
+    }
+}
--- a/calendar/lightning/content/messenger-overlay-sidebar.js
+++ b/calendar/lightning/content/messenger-overlay-sidebar.js
@@ -31,16 +31,29 @@ var calendarTabMonitor = {
         // type definitions. To make sure the commands are correctly disabled,
         // we want to update calendar/task commands when switching away from
         // those tabs.
         if (aOldTab.mode.name == "calendar" ||
             aOldTab.mode.name == "task") {
             calendarController.updateCommands();
             calendarController2.updateCommands();
         }
+        // we reset the save menu controls when moving away (includes closing)
+        // from an event or task editor tab
+        if ((aNewTab.mode.name == "calendarEvent" ||
+             aNewTab.mode.name == "calendarTask")) {
+            sendMessage({ command: "triggerUpdateSaveControls" });
+        } else if (window.calItemSaveControls) {
+            // we need to reset the labels of the menu controls for saving if we
+            // are not switching to an item tab and displayed an item tab before
+            let saveMenu = document.getElementById("ltnSave");
+            let saveandcloseMenu = document.getElementById("ltnSaveAndClose");
+            saveMenu.label = window.calItemSaveControls.saveMenu.label;
+            saveandcloseMenu.label = window.calItemSaveControls.saveandcloseMenu.label;
+        }
     }
 };
 
 var calendarTabType = {
     name: "calendar",
     panelId: "calendarTabPanel",
     modes: {
         calendar: {
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.dtd
@@ -393,16 +393,17 @@
 
 <!-- Timezone dialog -->
 <!ENTITY timezone.title.label            "Please Specify the Timezone">
 <!ENTITY event.timezone.custom.label     "More Timezones…">
 
 <!-- Read-Only dialog -->
 <!ENTITY read.only.general.label         "General">
 <!ENTITY read.only.title.label           "Title:">
+<!ENTITY read.only.calendar.label        "Calendar:">
 <!ENTITY read.only.event.start.label     "Start Date:">
 <!ENTITY read.only.task.start.label      "Start Date:">
 <!ENTITY read.only.event.end.label       "End Date:">
 <!ENTITY read.only.task.due.label        "Due Date:">
 <!ENTITY read.only.repeat.label          "Repeat:">
 <!ENTITY read.only.location.label        "Location:">
 <!ENTITY read.only.category.label        "Category:">
 <!ENTITY read.only.organizer.label       "Organizer:">
@@ -411,8 +412,22 @@
 <!ENTITY read.only.decline.label         "I will not attend">
 <!ENTITY read.only.tentative.label       "I might attend">
 <!ENTITY read.only.needs.action.label    "I will confirm later">
 <!ENTITY read.only.reminder.label        "Reminder:">
 <!ENTITY read.only.attachments.label     "Attachments:">
 <!ENTITY read.only.attendees.label       "Attendees">
 <!ENTITY read.only.description.label     "Description">
 <!ENTITY read.only.link.label            "Related Link">
+
+<!-- Summary dialog -->
+<!ENTITY summary.dialog.saveclose.label         "Save and Close">
+<!ENTITY summary.dialog.saveclose.tooltiptext   "Save changes and close the window without changing the participation status and sending a response">
+<!ENTITY summary.dialog.accept.label            "Accept">
+<!ENTITY summary.dialog.accept.tooltiptext      "Accept the invitation">
+<!ENTITY summary.dialog.tentative.label         "Tentative">
+<!ENTITY summary.dialog.tentative.tooltiptext   "Accept the invitation tentatively">
+<!ENTITY summary.dialog.decline.label           "Decline">
+<!ENTITY summary.dialog.decline.tooltiptext     "Decline the invitation">
+<!ENTITY summary.dialog.dontsend.label          "Do not send a response">
+<!ENTITY summary.dialog.dontsend.tooltiptext    "Change your participation status without sending a reply to the organizer and close the window">
+<!ENTITY summary.dialog.send.label              "Send a response now">
+<!ENTITY summary.dialog.send.tooltiptext        "Send out a response to the organizer  and close the window">
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
@@ -442,8 +442,88 @@ counterSummaryTentative=%1$S has accepte
 
 # LOCALIZATION NOTE (counterOnPreviousVersionNotification) - this is only visible when opening the
 # dialog from the email summary view after receiving a counter message
 counterOnPreviousVersionNotification=This is a counter proposal for a previous version of this event.
 
 # LOCALIZATION NOTE (counterOnCounterDisallowedNotification) - this is only visible when opening the
 # dialog from the email summary view after receiving a counter message
 counterOnCounterDisallowedNotification=You disallowed countering when sending out the invitation.
+
+# LOCALIZATION NOTE (eventAccepted) - this will be displayed as notification
+# in the summary dialog if the user has accepted the event invitation
+eventAccepted=You have accepted this invitation
+
+# LOCALIZATION NOTE (eventTentative) - this will be displayed as notification
+# in the summary dialog if the user has accepted the event invitation tentatively
+eventTentative=You have accepted this invitation tentatively
+
+# LOCALIZATION NOTE (eventDeclined) - this will be displayed as notification
+# in the summary dialog if the user has declined the event invitation
+eventDeclined=You have declined this invitation
+
+# LOCALIZATION NOTE (eventDelegated) - this will be displayed as notification
+# in the summary dialog if the user has delegated his/her participation to one
+# or more other participants (without attending / working on it his/herself)
+eventDelegated=You have delegated this invitation
+
+# LOCALIZATION NOTE (eventNeedsAction) - this will be displayed as notification
+# in the summary dialog if the user hasn't yet responded to an invitation
+eventNeedsAction=You haven't yet responded to this invitation
+
+# LOCALIZATION NOTE (taskAccepted) - this will be displayed as notification
+# in the summary dialog if the user has accepted the assigned task
+taskAccepted=You have accepted to work on this task
+
+# LOCALIZATION NOTE (taskTentative) - this will be displayed as notification
+# in the summary dialog if the user has accepted tentatively the assigned task
+taskTentative=You have tentatively accepted to work on this task
+
+# LOCALIZATION NOTE (taskDeclined) - this will be displayed as notification
+# in the summary dialog if the user has declined the assigned task
+taskDeclined=You have declined to work on this task
+
+# LOCALIZATION NOTE (taskDelegated) - this will be displayed as notification
+# in the summary dialog if the user has delegated his/her assignement to one or
+# more others (without attending / working on it his/herself)
+taskDelegated=You have delegated the work on this task
+
+# LOCALIZATION NOTE (taskNeedsAction) - this will be displayed as notification
+# in the summary dialog if the user hasn't yet responded to the task assignment
+taskNeedsAction=You haven't yet responded to this task assignment
+
+# LOCALIZATION NOTE (taskInProgress) - this will be displayed as notification
+# in the summary dialog if the user is working on an assigned task
+taskInProgress=You have started to work on this assigned task
+
+# LOCALIZATION NOTE (taskCompleted) - this will be displayed as notification
+# in the summary dialog if the user has completed the work on this assigned task
+taskCompleted=You have completed your work on this assigned task
+
+# LOCALIZATION NOTE (sendandcloseButtonLabel) - this is a runtime replacement for
+# event.toolbar.saveandclose.label in the event dialog/tab toolbar if attendees
+# will be notified on saving & closing
+sendandcloseButtonLabel=Send And Close
+
+# LOCALIZATION NOTE (sendandcloseButtonTooltip) - this is a runtime replacement for
+# event.toolbar.saveandclose.tooltip in the event dialog/tab toolbar if attendees
+# will be notified on saving & closing
+sendandcloseButtonTooltip=Notify attendees and close
+
+# LOCALIZATION NOTE (saveandsendButtonLabel) - this is a runtime replacement for
+# event.toolbar.save.label2 in the event dialog/tab toolbar if attendees
+# will be notified on saving
+saveandsendButtonLabel=Save And Send
+
+# LOCALIZATION NOTE (saveandsendButtonTooltip) - this is a runtime replacement
+# for event.toolbar.save.tooltip2 in the event dialog/tab toolbar if attendees
+# will be notified on saving
+saveandsendButtonTooltip=Save and notify attendees
+
+# LOCALIZATION NOTE (saveandsendMenuLabel) - this is a runtime replacement for
+# event.menu.item.save.label in the event dialog/tab toolbar if attendees
+# will be notified on saving
+saveandsendMenuLabel=Save and Send
+
+# LOCALIZATION NOTE (sendandcloseMenuLabel) - this is a runtime replacement for
+# event.menu.item.saveandclose.label in the event dialog/tab toolbar if attendees
+# will be notified on saving
+sendandcloseMenuLabel=Send and Close