diff options
Diffstat (limited to '')
-rw-r--r-- | comm/calendar/base/src/CalAlarmMonitor.jsm | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/comm/calendar/base/src/CalAlarmMonitor.jsm b/comm/calendar/base/src/CalAlarmMonitor.jsm new file mode 100644 index 0000000000..0412e72850 --- /dev/null +++ b/comm/calendar/base/src/CalAlarmMonitor.jsm @@ -0,0 +1,233 @@ +/* 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/. */ + +var EXPORTED_SYMBOLS = ["CalAlarmMonitor"]; + +var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm"); + +const lazy = {}; +ChromeUtils.defineModuleGetter(lazy, "CalEvent", "resource:///modules/CalEvent.jsm"); + +function peekAlarmWindow() { + return Services.wm.getMostRecentWindow("Calendar:AlarmWindow"); +} + +/** + * The alarm monitor takes care of playing the alarm sound and opening one copy + * of the calendar-alarm-dialog. Both depend on their respective prefs to be + * set. This monitor is only used for DISPLAY type alarms. + */ +function CalAlarmMonitor() { + this.wrappedJSObject = this; + this.mAlarms = []; + // A map from itemId to item. + this._notifyingItems = new Map(); + + this.mSound = Cc["@mozilla.org/sound;1"].createInstance(Ci.nsISound); + + Services.obs.addObserver(this, "alarm-service-startup"); + Services.obs.addObserver(this, "alarm-service-shutdown"); +} + +var calAlarmMonitorClassID = Components.ID("{4b7ae030-ed79-11d9-8cd6-0800200c9a66}"); +var calAlarmMonitorInterfaces = [Ci.nsIObserver, Ci.calIAlarmServiceObserver]; +CalAlarmMonitor.prototype = { + mAlarms: null, + + // This is a work-around for the fact that there is a delay between when + // we call openWindow and when it appears via getMostRecentWindow. If an + // alarm is fired in that time-frame, it will actually end up in another window. + mWindowOpening: null, + + // nsISound instance used for playing all sounds + mSound: null, + + classID: calAlarmMonitorClassID, + QueryInterface: cal.generateQI(["nsIObserver", "calIAlarmServiceObserver"]), + classInfo: cal.generateCI({ + contractID: "@mozilla.org/calendar/alarm-monitor;1", + classDescription: "Calendar Alarm Monitor", + classID: calAlarmMonitorClassID, + interfaces: calAlarmMonitorInterfaces, + flags: Ci.nsIClassInfo.SINGLETON, + }), + + /** + * nsIObserver + */ + observe(aSubject, aTopic, aData) { + let alarmService = Cc["@mozilla.org/calendar/alarm-service;1"].getService(Ci.calIAlarmService); + switch (aTopic) { + case "alarm-service-startup": + alarmService.addObserver(this); + break; + case "alarm-service-shutdown": + alarmService.removeObserver(this); + break; + case "alertclickcallback": { + let item = this._notifyingItems.get(aData); + if (item) { + let calWindow = cal.window.getCalendarWindow(); + if (calWindow) { + calWindow.openEventDialogForViewing(item, true); + } + } + break; + } + case "alertfinished": + this._notifyingItems.delete(aData); + break; + } + }, + + /** + * calIAlarmServiceObserver + */ + onAlarm(aItem, aAlarm) { + if (aAlarm.action != "DISPLAY") { + // This monitor only looks for DISPLAY alarms. + return; + } + + this.mAlarms.push([aItem, aAlarm]); + + if (Services.prefs.getBoolPref("calendar.alarms.playsound", true)) { + // We want to make sure the user isn't flooded with alarms so we + // limit this using a preference. For example, if the user has 20 + // events that fire an alarm in the same minute, then the alarm + // sound will only play 5 times. All alarms will be shown in the + // dialog nevertheless. + let maxAlarmSoundCount = Services.prefs.getIntPref("calendar.alarms.maxsoundsperminute", 5); + let now = new Date(); + + if (!this.mLastAlarmSoundDate || now - this.mLastAlarmSoundDate >= 60000) { + // Last alarm was long enough ago, reset counters. Note + // subtracting JSDate results in microseconds. + this.mAlarmSoundCount = 0; + this.mLastAlarmSoundDate = now; + } else { + // Otherwise increase the counter + this.mAlarmSoundCount++; + } + + if (maxAlarmSoundCount > this.mAlarmSoundCount) { + // Only ring the alarm sound if we haven't hit the max count. + try { + let soundURL; + if (Services.prefs.getIntPref("calendar.alarms.soundType", 0) == 0) { + soundURL = "chrome://calendar/content/sound.wav"; + } else { + soundURL = Services.prefs.getStringPref("calendar.alarms.soundURL", null); + } + if (soundURL && soundURL.length > 0) { + soundURL = Services.io.newURI(soundURL); + this.mSound.play(soundURL); + } else { + this.mSound.beep(); + } + } catch (exc) { + cal.ERROR("Error playing alarm sound: " + exc); + } + } + } + + if (!Services.prefs.getBoolPref("calendar.alarms.show", true)) { + return; + } + + let calAlarmWindow = peekAlarmWindow(); + if (!calAlarmWindow && (!this.mWindowOpening || this.mWindowOpening.closed)) { + this.mWindowOpening = Services.ww.openWindow( + null, + "chrome://calendar/content/calendar-alarm-dialog.xhtml", + "_blank", + "chrome,dialog=yes,all,resizable", + this + ); + } + if (!this.mWindowOpening) { + calAlarmWindow.addWidgetFor(aItem, aAlarm); + } + }, + + /** + * @see {calIAlarmServiceObserver} + * @param {calIItemBase} item - The item to notify about. + */ + onNotification(item) { + // Don't notify about canceled events. + if (item.status == "CANCELLED") { + return; + } + // Don't notify if you declined this event invitation. + if ( + (item instanceof lazy.CalEvent || item instanceof Ci.calIEvent) && + item.calendar instanceof Ci.calISchedulingSupport && + item.calendar.isInvitation(item) && + item.calendar.getInvitedAttendee(item)?.participationStatus == "DECLINED" + ) { + return; + } + + let alert = Cc["@mozilla.org/alert-notification;1"].createInstance(Ci.nsIAlertNotification); + let alertsService = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); + alert.init( + item.id, // name + "chrome://messenger/skin/icons/new-mail-alert.png", + item.title, + item.getProperty("description"), + true, // clickable + item.id // cookie + ); + this._notifyingItems.set(item.id, item); + alertsService.showAlert(alert, this); + }, + + window_onLoad() { + let calAlarmWindow = this.mWindowOpening; + this.mWindowOpening = null; + if (this.mAlarms.length > 0) { + for (let [item, alarm] of this.mAlarms) { + calAlarmWindow.addWidgetFor(item, alarm); + } + } else { + // Uh oh, it seems the alarms were removed even before the window + // finished loading. Looks like we can close it again + calAlarmWindow.closeIfEmpty(); + } + }, + + onRemoveAlarmsByItem(aItem) { + let calAlarmWindow = peekAlarmWindow(); + this.mAlarms = this.mAlarms.filter(([thisItem, alarm]) => { + let ret = aItem.hashId != thisItem.hashId; + if (!ret && calAlarmWindow) { + // window is open + calAlarmWindow.removeWidgetFor(thisItem, alarm); + } + return ret; + }); + }, + + onRemoveAlarmsByCalendar(calendar) { + let calAlarmWindow = peekAlarmWindow(); + this.mAlarms = this.mAlarms.filter(([thisItem, alarm]) => { + let ret = calendar.id != thisItem.calendar.id; + + if (!ret && calAlarmWindow) { + // window is open + calAlarmWindow.removeWidgetFor(thisItem, alarm); + } + return ret; + }); + }, + + onAlarmsLoaded(aCalendar) { + // the alarm dialog won't close while alarms are loading, check again now + let calAlarmWindow = peekAlarmWindow(); + if (calAlarmWindow && this.mAlarms.length == 0) { + calAlarmWindow.closeIfEmpty(); + } + }, +}; |