<?xml version="1.0"?>

<!-- ***** BEGIN LICENSE BLOCK *****
   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
   -
   - The contents of this file are subject to the Mozilla Public License Version
   - 1.1 (the "License"); you may not use this file except in compliance with
   - the License. You may obtain a copy of the License at
   - http://www.mozilla.org/MPL/
   -
   - Software distributed under the License is distributed on an "AS IS" basis,
   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   - for the specific language governing rights and limitations under the
   - License.
   -
   - The Original Code is OEone Calendar Code, released October 31st, 2001.
   -
   - The Initial Developer of the Original Code is
   - OEone Corporation.
   - Portions created by the Initial Developer are Copyright (C) 2001
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   - Martin Schroeder <mschroeder@mozilla.x-home.org>
   - Vladimir Vukicevic <vladimir@pobox.com>
   - Gekacheka <gekacheka@yahoo.com>
   - Philipp Kewisch <mozilla@kewis.ch>
   - Joey Minta <jminta@gmail.com>
   - Michael Buettner <Michael.Buettner@sun.com>
   - Simon Paquet <bugzilla@babylonsounds.com>
   - Michiel van Leeuwen <mvl@exedo.nl>
   - Lars Wohlfahrt <thetux.moz@googlemail.com>
   - Matt Willis <mattwillis@gmail.com>
   - Dan Mosedale <dmose@mozilla.org>
   - Olli Pettay <Olli.Pettay@helsinki.fi>
   - Thomas Benisch <thomas.benisch@sun.com>
   - Stefan Sitter <ssitter@gmail.com>
   - Christian Schmidt <bugzilla.mozilla.org-1@chsc.dk>
   - Berend Cornelius <Berend.Cornelius@sun.com>
   -
   - Alternatively, the contents of this file may be used under the terms of
   - either the GNU General Public License Version 2 or later (the "GPL"), or
   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
   - in which case the provisions of the GPL or the LGPL are applicable instead
   - of those above. If you wish to allow use of your version of this file only
   - under the terms of either the GPL or the LGPL, and not to allow others to
   - use your version of this file under the terms of the MPL, indicate your
   - decision by deleting the provisions above and replace them with the notice
   - and other provisions required by the LGPL or the GPL. If you do not delete
   - the provisions above, a recipient may use your version of this file under
   - the terms of any one of the MPL, the GPL or the LGPL.
   -
   - ***** END LICENSE BLOCK ***** -->

<!--
/* MiniMonth Calendar: day-of-month grid XBL component.
   Displays month name and year above grid of days of month by week rows.
   Arrows move forward or back a month.
   Selecting a month name from month menu moves to that month in same year.
   Selecting a year from year menu moves to same month in selected year.
   Clicking on a day cell calls onchange attribute.
   Changing month via arrows or menus calls onmonthchange attribute.

   At site, can provide id, and code to run when value changed by picker.
     <calendar id="my-date-picker" onchange="myDatePick( this );"/>

   May get/set value in javascript with
     document.getElementById("my-date-picker").value = new Date();

   Use attributes onpopuplisthidden and onmonthchange for working around
   bugs that occur when minimonth is displayed in a popup (as in datepicker):
     Currently (2005.3)
       whenever a child popup is hidden, the parent popup needs to be reshown.
         Use onpopuplisthidden to reshow parent popop (hidePopup, openPopup).
       When title month or year changes, parent popup may need to be reshown.
         Use onmonthchange to reshow parent popop (hidePopup, openPopup).
*/
-->
<!DOCTYPE bindings 
[
    <!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
    <!ENTITY % dtd2 SYSTEM "chrome://global/locale/global.dtd" > %dtd2;
]>

<bindings id="xulMiniMonth"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xbl="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">

   <binding id="minimonth-header" extends="xul:box">
     <content class="minimonth-month-box" align="center">
         <xul:deck anonid="monthheader" xbl:inherits="selectedIndex=month" class="minimonth-month-name-readonly">
           <xul:text value="&month.1.name;"/>
           <xul:text value="&month.2.name;"/>
           <xul:text value="&month.3.name;"/>
           <xul:text value="&month.4.name;"/>
           <xul:text value="&month.5.name;"/>
           <xul:text value="&month.6.name;"/>
           <xul:text value="&month.7.name;"/>
           <xul:text value="&month.8.name;"/>
           <xul:text value="&month.9.name;"/>
           <xul:text value="&month.10.name;"/>
           <xul:text value="&month.11.name;"/>
           <xul:text value="&month.12.name;"/>
         </xul:deck>
         <xul:text anonid="yearcell" class="minimonth-year-name-readonly" xbl:inherits="value=year"/>
         <xul:spacer flex="1"/>
     </content>
   </binding>

   <binding id="active-minimonth-header" extends="chrome://calendar/content/widgets/minimonth.xml#minimonth-header">
       <content class="minimonth-month-box" align="center">
         <xul:deck anonid="monthheader" xbl:inherits="selectedIndex=month"  class="minimonth-month-name">
           <xul:toolbarbutton label="&month.1.name;" oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.2.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.3.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.4.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.5.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.6.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.7.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.8.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.9.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.10.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.11.name;"  oncommand="showPopupList(event, 'months-popup')"/>
           <xul:toolbarbutton label="&month.12.name;"  oncommand="showPopupList(event, 'months-popup')"/>
         </xul:deck>
         <xul:toolbarbutton anonid="yearcell"
                            class="minimonth-year-name"
                            oncommand="showPopupList(event, 'years-popup')"
                            xbl:inherits="label=year"/>
         <xul:spacer flex="1"/>
         <xul:toolbarbutton anonid="back-button" class="minimonth-nav-btns" dir="-1"
                            oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
                            chromedir="&locale.dir;"
                            tooltiptext="&onemonthbackward.tooltip;"/>
         <xul:toolbarbutton anonid="today-button" class="minimonth-nav-btns" dir="0"
                            oncommand="this.kMinimonth.value = new Date();"
                            tooltiptext="&showToday.tooltip;"/>
         <xul:toolbarbutton anonid="forward-button" class="minimonth-nav-btns" dir="1"
                            oncommand="this.kMinimonth.advanceMonth(parseInt(this.getAttribute('dir'), 10))"
                            chromedir="&locale.dir;"
                            tooltiptext="&onemonthforward.tooltip;"/>
       <xul:popupset anonid="minmonth-popupset">
         <xul:menupopup anonid="months-popup" position="after_start"
                        onpopupshowing="event.stopPropagation();"
                        onpopuphidden="firePopupListHidden();">
           <xul:vbox>
             <xul:text class="minimonth-list" value="&month.1.name;" index="0"/>
             <xul:text class="minimonth-list" value="&month.2.name;" index="1"/>
             <xul:text class="minimonth-list" value="&month.3.name;" index="2"/>
             <xul:text class="minimonth-list" value="&month.4.name;" index="3"/>
             <xul:text class="minimonth-list" value="&month.5.name;" index="4"/>
             <xul:text class="minimonth-list" value="&month.6.name;" index="5"/>
             <xul:text class="minimonth-list" value="&month.7.name;" index="6"/>
             <xul:text class="minimonth-list" value="&month.8.name;" index="7"/>
             <xul:text class="minimonth-list" value="&month.9.name;" index="8"/>
             <xul:text class="minimonth-list" value="&month.10.name;" index="9"/>
             <xul:text class="minimonth-list" value="&month.11.name;" index="10"/>
             <xul:text class="minimonth-list" value="&month.12.name;" index="11"/>
           </xul:vbox>
         </xul:menupopup>
         <xul:menupopup anonid="years-popup" position="after_start"
                        onpopupshowing="moveYears('reset', 0); event.stopPropagation();"
                        onpopuphidden="firePopupListHidden();">
           <xul:vbox>
             <xul:autorepeatbutton class="autorepeatbutton-up"
                                   orient="vertical"
                                   oncommand="moveYears('up', 1);"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:text class="minimonth-list"/>
             <xul:autorepeatbutton class="autorepeatbutton-down"
                                   orient="vertical"
                                   oncommand="moveYears('down', 1);"/>
           </xul:vbox>
         </xul:menupopup>
       </xul:popupset>
     </content>
     <implementation>
       <field name="kMinimonth">null</field>
       <field name="mPopup">null</field>
       <field name="mScrollYearsHandler">{}</field>
       <constructor><![CDATA[
         this.kMinimonth = getParentNodeOrThis(this, "minimonth");
         document.getAnonymousElementByAttribute(this, "anonid", "back-button").kMinimonth = this.kMinimonth;
         document.getAnonymousElementByAttribute(this, "anonid", "today-button").kMinimonth = this.kMinimonth;
         document.getAnonymousElementByAttribute(this, "anonid", "forward-button").kMinimonth = this.kMinimonth;
         this.mScrollYearsHandler =  {
             binding: this,
             handleEvent: function scrollYears_handleEvent(event) {
                 this.binding.scrollYears(event);
             }
         };
         document.getAnonymousElementByAttribute(this, "anonid", "years-popup")
                 .addEventListener("DOMMouseScroll", this.mScrollYearsHandler, true);
       ]]></constructor>
       <destructor><![CDATA[
         document.getAnonymousElementByAttribute(this, "anonid", "years-popup")
                 .removeEventListener("DOMMouseScroll", this.mScrollYearsHandler, true);
         this.mScrollYearsHandler.binding = null;
       ]]></destructor>
       <method name="showPopupList">
         <parameter name="aEvent"/>
         <parameter name="aPopupAnonId"/>
          <body><![CDATA[
           // Close open popups (if any), to prevent linux crashes
           if (this.mPopup)
               this.mPopup.hidePopup();
           this.mPopup = document.getAnonymousElementByAttribute(this, "anonid", aPopupAnonId);
           this.mPopup.openPopup(aEvent.target, "after_start");
         ]]></body>
       </method>

       <method name="hidePopupList">
         <body><![CDATA[
           // Close open popups (if any)
           let popup = this.mPopup;
           this.mPopup = null;
           if (popup)
               popup.hidePopup();
         ]]></body>
       </method>
       <method name="firePopupListHidden">
         <body><![CDATA[
           if (this.mPopup) {
               this.mPopup = null;
               this.kMinimonth.fireEvent("popuplisthidden");
           }
         ]]></body>
       </method>
       <method name="updateMonthPopup">
         <parameter name="aDate"/>
         <body><![CDATA[
           let months = document.getAnonymousElementByAttribute(this, "anonid", "months-popup").firstChild.childNodes;
           let month = aDate.getMonth();
           for (let i = 0; i < months.length; i++) {
               months[i].setAttribute("current", (i == month) ? "true" : "false");
           }
         ]]></body>
       </method>
       <method name="updateYearPopup">
         <parameter name="aDate"/>
         <body><![CDATA[
           let years = document.getAnonymousElementByAttribute(this, "anonid", "years-popup").firstChild.childNodes;
           let year = new Date(aDate);
           let compFullYear = aDate.getFullYear();
           year.setFullYear(Math.max(1, compFullYear - parseInt(years.length / 2, 10) + 1));
           for (let i = 1; i < years.length - 1; i++) {
               let curfullYear = year.getFullYear();
               years[i].setAttribute("value", curfullYear);
               years[i].setAttribute("current", (curfullYear == compFullYear) ? "true" : "false");
               year.setFullYear(curfullYear + 1);
           }
         ]]></body>
       </method>

       <method name="scrollYears">
         <parameter name="event"/>
         <body><![CDATA[
           let yearPopup = getParentNodeOrThis(event.target, "menupopup");
           if (yearPopup) {
               let monthList = yearPopup.getElementsByAttribute("class", "minimonth-list");
               if (monthList && monthList.length > 0) {
                   if (event.detail == NSUIEvent.SCROLL_PAGE_UP) {
                     this.moveYears('up', monthList.length);
                   } else if (event.detail == NSUIEvent.SCROLL_PAGE_DOWN) {
                     this.moveYears('down', monthList.length);
                   } else if (event.detail < 0) {
                     this.moveYears('up', 1);
                   } else if (event.detail > 0) {
                     this.moveYears('down', 1);
                   }
                   event.stopPropagation();
                   event.preventDefault();
               }
           }
         ]]></body>
       </method>

       <method name="moveYears">
         <parameter name="direction"/>
         <parameter name="scrollOffset"/>
         <body><![CDATA[
           // Update the year popup
           let years = document.getAnonymousElementByAttribute(this, "anonid", "years-popup").firstChild.childNodes;
           let current = this.getAttribute("year");
           let offset;
           switch (direction) {
               case "reset":
                   let middleyear = years[Math.floor(years.length / 2)].getAttribute("value");
                   if (current <= (years.length / 2)) {
                       offset = - years[1].getAttribute("value") + 1;
                   } else {
                       offset = current - middleyear;
                   }
                   break;
               case "up":
                   offset = -Math.abs(scrollOffset) || -1;
                   break;
               case "down":
                   offset = Math.abs(scrollOffset) || 1;
                   break;
           }

           // Disable the up arrow when we get to the year 1.
           years[0].disabled = (parseInt(years[1].getAttribute("value"), 10) + offset < 2);

           if (!offset) {
               // No need to loop through when the offset is zero.
               return;
           }

           // Go through all visible years and set the new value. Be sure to
           // skip the autorepeatbuttons.
           for (let i = 1; i < years.length - 1; i++) {
               let value = parseInt(years[i].getAttribute("value"), 10) + offset;
               years[i].setAttribute("value", value);
               years[i].setAttribute("current", value == current ? "true" : "false");
           }
         ]]> </body>
       </method>
    </implementation>
    <!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
    <handlers>
      <handler event="bindingattached" action="this.initialize();"/>
      <!-- handle click from nested months popup and years popup -->
      <handler event="click">
        <![CDATA[
          let element = event.originalTarget;
          let popup = getParentNodeOrThis(element, "menupopup");
          if (popup) {
              let anonid = popup.getAttribute("anonid")
              switch(anonid) {
                  case "months-popup":
                      this.hidePopupList();
                      this.kMinimonth.switchMonth(element.getAttribute("index"));
                      break;
                  case "years-popup":
                      this.hidePopupList();
                      let value = element.getAttribute("value");
                      if (value) {
                          this.kMinimonth.switchYear(value);
                      }
                      break;
              }
          }
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="minimonth" extends="xul:box">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/minimonth.css"/>
    </resources>
    <content orient="vertical" xbl:inherits="onchange,onmonthchange,onpopuplisthidden,readonly">
        <xul:minimonth-header anonid="minimonth-header" xbl:inherits="readonly,month,year"/>
        <xul:vbox anonid="minimonth-calendar" class="minimonth-cal-box">
          <xul:hbox class="minimonth-row-head" anonid="minimonth-row-header" equalsize="always">
            <xul:text class="minimonth-row-header" flex="1"/>
            <xul:text class="minimonth-row-header" flex="1"/>
            <xul:text class="minimonth-row-header" flex="1"/>
            <xul:text class="minimonth-row-header" flex="1"/>
            <xul:text class="minimonth-row-header" flex="1"/>
            <xul:text class="minimonth-row-header" flex="1"/>
            <xul:text class="minimonth-row-header" flex="1"/>
          </xul:hbox>
          <xul:hbox class="minimonth-row-body" equalsize="always" flex="1">
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
          </xul:hbox>
          <xul:hbox class="minimonth-row-body" equalsize="always" flex="1">
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
          </xul:hbox>
          <xul:hbox class="minimonth-row-body" equalsize="always" flex="1">
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
          </xul:hbox>
          <xul:hbox class="minimonth-row-body" equalsize="always" flex="1">
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
          </xul:hbox>
          <xul:hbox class="minimonth-row-body" equalsize="always" flex="1">
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
          </xul:hbox>
          <xul:hbox class="minimonth-row-body" equalsize="always" flex="1">
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
            <xul:text class="minimonth-day" flex="1"/>
          </xul:hbox>
        </xul:vbox>
    </content>

    <!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
    <implementation implements="calICompositeObserver calIOperationListener nsIObserver" >
      <property name="value"
                onget="return this.mValue"
                onset="this.update(val)"/>

       <!--returns the first (inclusive) date of the minimonth as a calIDateTime object-->
      <property name="firstDate" readonly="true">
        <getter><![CDATA[
            let calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
            let date = calbox.childNodes[1].firstChild.date;
            return jsDateToDateTime(date);
        ]]></getter>
      </property>

       <!--returns the last (exclusive) date of the minimonth as a calIDateTime object-->
      <property name="lastDate" readonly="true">
        <getter><![CDATA[
            var calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
            var date = calbox.lastChild.lastChild.date;
            var lastDateTime = jsDateToDateTime(date);
            lastDateTime.day = lastDateTime.day + 1;
            return lastDateTime;
        ]]></getter>
      </property>
      <field name="mDaymap">null</field>
      <field name="mValue">null</field>
      <field name="mEditorDate">null</field>
      <field name="mIsReadOnly">false</field>
      <field name="mObservesComposite">false</field>
      <constructor><![CDATA[
          this.mToday = false;
          this.mSelected = false;
          this.mValue = new Date() // Default to "today"
          // save references for convenience
          if (this.hasAttribute("readonly")) {
              this.mIsReadOnly = this.getAttribute("readonly") == "true";
          }
          this.refreshDisplay( );
          if (this.hasAttribute("freebusy")) {
              this._setFreeBusy(this.getAttribute("freebusy") == "true");
          }

          // Add pref observer for calendar.week.start
          let prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                      .getService(Components.interfaces.nsIPrefService);
          let branch = prefService.getBranch("")
                                  .QueryInterface(Components.interfaces.nsIPrefBranch2);
          branch.addObserver("calendar.week.start", this, false);
      ]]></constructor>
      <destructor><![CDATA[
          var composite = getCompositeCalendar();
          if (composite && this.mObservesComposite == true) {
              getCompositeCalendar().removeObserver(this);
          }

          // Remove pref observer for calendar.week.start
          let prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                      .getService(Components.interfaces.nsIPrefService);
          let branch = prefService.getBranch("")
                                  .QueryInterface(Components.interfaces.nsIPrefBranch2);
          branch.removeObserver("calendar.week.start", this, false);
      ]]></destructor>

     <!-- calIOperationListener methods -->
      <method name="onOperationComplete">
        <parameter name="aCalendar"/>
        <parameter name="aStatus"/>
        <parameter name="aOperationType"/>
        <parameter name="aId"/>
        <parameter name="aDetail"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onGetResult">
        <parameter name="aCalendar"/>
        <parameter name="aStatus"/>
        <parameter name="aItemType"/>
        <parameter name="aDetail"/>
        <parameter name="aCount"/>
        <parameter name="aItems"/>
        <body><![CDATA[
            if (!Components.isSuccessCode(aStatus)) {
                return;
            }
            for each (var item in aItems) {
                this.setBusyDaysForOccurrence(item, true);
            }
        ]]></body>
      </method>

      <method name="setBusyDaysForItem">
        <parameter name="aItem"/>
        <parameter name="aState"/>
        <body><![CDATA[
          var items = [aItem];
          if (aItem.recurrenceInfo) {
              var startDate = this.firstDate;
              var endDate = this.lastDate;
              items = aItem.getOccurrencesBetween(startDate, endDate, {});
          }
          for each (var item in items) {
              this.setBusyDaysForOccurrence(item, aState);
          }
        ]]></body>
      </method>

      <method name="parseBoxBusy">
        <parameter name="aBox"/>
        <body><![CDATA[
          let boxBusy = {};

          let busyStr = aBox.getAttribute("busy");
          if (busyStr && busyStr.length > 0) {
              let calChunks = busyStr.split("\u001A");
              for each (let chunk in calChunks) {
                  let expr = chunk.split("=");
                  boxBusy[expr[0]] = parseInt(expr[1]);
              }
          }

          return boxBusy;
        ]]></body>
      </method>

      <method name="updateBoxBusy">
        <parameter name="aBox"/>
        <parameter name="aBoxBusy"/>
        <body><![CDATA[
          let calChunks = [];

          for (let calId in aBoxBusy) {
              if (aBoxBusy[calId]) {
                  calChunks.push(calId + "=" + aBoxBusy[calId]);
              }
          }

          if (calChunks.length > 0) {
              let busyStr = calChunks.join("\u001A");
              aBox.setAttribute("busy", busyStr);
          } else {
              aBox.removeAttribute("busy");
          }
        ]]></body>
      </method>

      <method name="removeCalendarFromBoxBusy">
        <parameter name="aBox"/>
        <parameter name="aCalendar"/>
        <body><![CDATA[
          let boxBusy = this.parseBoxBusy(aBox);
          if (boxBusy[aCalendar.id]) {
              delete boxBusy[aCalendar.id];
          }
          this.updateBoxBusy(aBox, boxBusy);
        ]]></body>
      </method>

      <method name="setBusyDaysForOccurrence">
        <parameter name="aOccurrence"/>
        <parameter name="aState"/>
        <body><![CDATA[
        if (aOccurrence.getProperty("TRANSP") == "TRANSPARENT") {
          // Skip transparent events
          return;
        }
        let start = aOccurrence[calGetStartDateProp(aOccurrence)] || aOccurrence.dueDate;
        let end = aOccurrence[calGetEndDateProp(aOccurrence)] || start;
        if (!start) {
            return;
        }

        // We need to compare with midnight of the current day, so reset the
        // time here.
        let current = start.clone().getInTimezone(cal.calendarDefaultTimezone());
        current.hour = 0;
        current.minute = 0;
        current.second = 0;

        // Cache the result so the compare isn't called in each iteration.
        let compareResult = (start.compare(end) == 0 ? 1 : 0);

        // Setup the busy days.
        while (current.compare(end) < compareResult) {
            let box = this.getBoxForDate(current);
            if (box) {
                let busyCalendars = this.parseBoxBusy(box);
                if (!busyCalendars[aOccurrence.calendar.id])
                    busyCalendars[aOccurrence.calendar.id] = 0;
                busyCalendars[aOccurrence.calendar.id] += (aState ? 1 : -1);
                this.updateBoxBusy(box, busyCalendars);
            }
            current.day++;
        }
        ]]></body>
      </method>

       <!--calIObserver methods -->
      <method name="onStartBatch">
        <parameter name="aCalendar"/>
        <body><![CDATA[
            this.mBatchCount++;
        ]]></body>
      </method>

      <method name="onEndBatch">
        <parameter name="aCalendar"/>
          this.mBatchCount--;
          if (this.mBatchCount == 0) {
              this.resetAttributesForDate();
              this.getItems();
          }
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onLoad">
        <parameter name="aCalendar"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onAddItem">
        <parameter name="aItem"/>
        <body><![CDATA[
            this.setBusyDaysForItem(aItem, true);
        ]]></body>
      </method>

      <method name="onDeleteItem">
        <parameter name="aItem"/>
        <body><![CDATA[
            this.setBusyDaysForItem(aItem, false);
        ]]></body>
      </method>

      <method name="onModifyItem">
        <parameter name="aNewItem"/>
        <parameter name="aOldItem"/>
        <body><![CDATA[
            this.setBusyDaysForItem(aOldItem, false);
            this.setBusyDaysForItem(aNewItem, true);
        ]]></body>
      </method>

      <method name="onError">
        <parameter name="aCalendar"/>
        <parameter name="aErrNo"/>
        <parameter name="aMessage"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <method name="onPropertyChanged">
        <parameter name="aCalendar"/>
        <parameter name="aName"/>
        <parameter name="aValue"/>
        <parameter name="aOldValue"/>
        <body><![CDATA[
            switch (aName) {
                case "disabled":
                    this.resetAttributesForDate();
                    this.getItems();
                    break;
            }
        ]]></body>
      </method>

      <method name="onPropertyDeleting">
        <parameter name="aCalendar"/>
        <parameter name="aName"/>
        <body><![CDATA[
            this.onPropertyChanged(aCalendar, aName, null, null);
        ]]></body>
      </method>

       <!-- calICompositeObserver methods -->
      <method name="onCalendarAdded">
        <parameter name="aCalendar"/>
        <body><![CDATA[
            this.getItems(aCalendar);
        ]]></body>
      </method>
      <method name="onCalendarRemoved">
        <parameter name="aCalendar"/>
        <body><![CDATA[
            for (let day in this.mDayMap) {
                this.removeCalendarFromBoxBusy(this.mDayMap[day], aCalendar);
            }
        ]]></body>
      </method>

      <method name="onDefaultCalendarChanged">
        <parameter name="aCalendar"/>
        <body><![CDATA[
        ]]></body>
      </method>

      <!-- nsIObserver methods -->
      <method name="observe">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aData"/>
        <body><![CDATA[
          // WARNING: In the constructor, only calendar.week.start is observed.
          // If you decide to add further prefs to this observer, you'll need to
          // modify constructor and destructor.
          switch (aData) {
              case "calendar.week.start":
                  this.refreshDisplay(true);
                  break;
          }
        ]]></body>
      </method>

      <method name="refreshDisplay">
        <body><![CDATA[
          // Find out which should be the first day of the week
          this.weekStart = getPrefSafe("calendar.week.start", 0);
          if (!this.mValue) {
              this.mValue = new Date();
          }
          this.setHeader();
          this.showMonth(this.mValue);
        ]]></body>
      </method>
      <method name="setHeader">
        <body><![CDATA[
          // Reset the headers
          var header = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-row-header");
          var dayList = new Array(7);
          var tempDate = new Date();
          var i, j;
          var useOSFormat;
          tempDate.setDate(tempDate.getDate() - (tempDate.getDay() - this.weekStart));
          for (i = 0; i < header.childNodes.length; i++) {
              // if available, use UILocale days, else operating system format
              try {
                  dayList[i] = calGetString("dateFormat",
                               "day." + (tempDate.getDay() + 1) + ".short");
              } catch (e) {
                  dayList[i] = tempDate.toLocaleFormat("%a");
                  useOSFormat = true;
              }
              tempDate.setDate(tempDate.getDate() + 1);
          }

          if (useOSFormat) {
              // To keep datepicker popup compact, shrink localized weekday
              // abbreviations down to 1 or 2 chars so each column of week can
              // be as narrow as 2 digits.
              //
              // 1. Compute the minLength of the day name abbreviations.
              var minLength = dayList[0].length;
              for (i = 1; i < dayList.length; i++) {
                  minLength = Math.min(minLength, dayList[i].length);
              }
              // 2. If some day name abbrev. is longer than 2 chars (not Catalan),
              //    and ALL localized day names share same prefix (as in Chinese),
              //    then trim shared "day-" prefix.
              if (dayList.some(function(dayAbbr){ return dayAbbr.length > 2; })) {
                  for (var endPrefix = 0; endPrefix < minLength; endPrefix++) {
                      var c = dayList[0][endPrefix];
                      if (dayList.some(function(dayAbbr) {
                                             return dayAbbr[endPrefix] != c; })) {
                          if (endPrefix > 0) {
                              for (i = 0; i < dayList.length; i++) // trim prefix chars.
                                  dayList[i] = dayList[i].substring(endPrefix);
                          }
                          break;
                      }
                  }
              }
              // 3. trim each day abbreviation to 1 char if unique, else 2 chars.
              for (i = 0; i < dayList.length; i++) {
                  var foundMatch = 1;
                  for (j = 0; j < dayList.length; j++) {
                      if (i != j) {
                          if (dayList[i].substring(0,1) == dayList[j].substring(0,1)) {
                              foundMatch = 2;
                              break;
                          }
                      }
                  }
                  dayList[i] = dayList[i].substring(0,foundMatch)
              }
          }
          for (var column = 0; column < header.childNodes.length; column++) {
              header.childNodes[column].setAttribute( "value", dayList[column]);
          }
        ]]></body>
      </method>
      <method name="showMonth">
        <parameter name="aDate"/>
        <body><![CDATA[
            if (!aDate) {
              aDate = new Date();
            } else {
              aDate = new Date(aDate);
            }
            aDate.setDate(1);
            // We set the hour and minute to something highly unlikely to be the
            // exact change point of DST, so timezones like America/Sao Paulo
            // don't display some days twice.
            aDate.setHours(12);
            aDate.setMinutes(34);
            aDate.setSeconds(0);
            aDate.setMilliseconds(0);
            // Don't fire onmonthchange event upon initialization
            var monthChanged = this.mEditorDate && (this.mEditorDate != aDate);
            this.mEditorDate = aDate; // only place mEditorDate is set.

            if (this.mToday) {
              this.mToday.removeAttribute("today");
              this.mToday = null;
            }

            if (this.mSelected) {
              this.mSelected.removeAttribute("selected");
              this.mSelected = null;
            }
            // Update the month and year title
            this.setAttribute("month", aDate.getMonth());
            this.setAttribute("year", aDate.getFullYear());
            if (!this.mIsReadOnly) {
                // Update the month popup
                var header = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-header");
                header.updateYearPopup(aDate);
                header.updateMonthPopup(aDate);
            }
            // Update the calendar
            var calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
            var date = this._getStartDate(aDate);

            // get today's date
            var today = new Date();

            this.mDayMap = {};
            for (var k = 1; k < calbox.childNodes.length; k++) {
                var row = calbox.childNodes[k];
                for (var i = 0; i < 7; i++) {
                    var day = row.childNodes[i];
                    var ymd = date.getFullYear() + "-" +
                              date.getMonth() + "-" +
                              date.getDate();
                    this.mDayMap[ymd] = day;

                    if (!this.mIsReadOnly) {
                        day.setAttribute("interactive", "true");
                    }

                    if (aDate.getMonth() != date.getMonth()) {
                        day.setAttribute("othermonth", "true");
                    } else {
                        day.removeAttribute("othermonth");
                    }

                    // highlight today
                    if (this._sameDay(today, date)) {
                        this.mToday = day;
                        day.setAttribute("today", "true");
                    }

                    // highlight the current date
                    var val = this.value;
                    if (this._sameDay(val, date)) {
                        this.mSelected = day;
                        day.setAttribute("selected", "true");
                    }

                    day.date = new Date(date);
                    day.minimonthParent = this;
                    day.setAttribute("value", date.getDate());
                    date.setDate(date.getDate() + 1);

                    if (monthChanged) {
                        this.resetAttributesForDate(day.date);
                    }
                }
            }
            if (monthChanged) this.fireEvent('monthchange');
            if (this.getAttribute("freebusy") == "true") {
                this.getItems();
            }
        ]]></body>
      </method>
      <!--Attention - duplicate!!!!-->
      <method name="fireEvent">
        <parameter name="aEventName"/>
        <body><![CDATA[
            var event = document.createEvent('Events');
            event.initEvent(aEventName, true, true);
            this.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="getBoxForDate">
        <parameter name="aDate"/>
        <body><![CDATA[
            let ymd;
            if (aDate instanceof Date) {
                ymd = [aDate.getFullYear(),
                       aDate.getMonth(),
                       aDate.getDate()].join("-");
            } else {
                // Probably a calIDateTime
                ymd = [aDate.year, aDate.month, aDate.day].join("-");
            }

            return (ymd in this.mDayMap ? this.mDayMap[ymd] : null);
        ]]></body>
      </method>

      <method name="resetAttributesForDate">
        <parameter name="aDate"/>
        <body><![CDATA[
            function removeForBox(aBox) {
              var allowedAttributes = 0;
              while (aBox.attributes.length > allowedAttributes) {
                switch (aBox.attributes[allowedAttributes].nodeName) {
                  case "selected":
                  case "othermonth":
                  case "today":
                  case "interactive":
                  case "value":
                  case "class":
                  case "flex":
                    allowedAttributes++;
                    break;
                  default:
                    aBox.removeAttribute(aBox.attributes[allowedAttributes].nodeName);
                    break;
                }
              }
            }

            if (aDate) {
              var box = this.getBoxForDate(aDate);
              if (box) {
                  removeForBox(box);
              }
            } else {
              var calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
              for (var k = 1; k < calbox.childNodes.length; k++) {
                for (var i = 0; i < 7; i++) {
                    removeForBox(calbox.childNodes[k].childNodes[i]);
                }
              }
            }
          ]]></body>
      </method>
      <method name="_setFreeBusy">
        <parameter name="aFreeBusy"/>
        <body><![CDATA[
            if (aFreeBusy == true) {
                if (this.mObservesComposite == false) {
                    getCompositeCalendar().addObserver(this);
                    this.mObservesComposite = true;
                    this.getItems()
                 }
            } else {
                if (this.mObservesComposite == true) {
                    getCompositeCalendar().removeObserver(this);
                    this.mObservesComposite = false;
                 }
            }
        ]]></body>
      </method>
       <method name="removeAttribute">
        <parameter name="aAttr"/>
        <body><![CDATA[
          if (aAttr == "freebusy") {
              this._setFreeBusy(false);
          }
          // this should be done using lookupMethod(), see bug 286629
          var ret = XULElement.prototype.removeAttribute.call (this, aAttr);
          return ret;
        ]]></body>
      </method>
      <method name="setAttribute">
        <parameter name="aAttr"/>
        <parameter name="aVal"/>
        <body><![CDATA[
          if (aAttr == "freebusy") {
              this._setFreeBusy(aVal == "true");
          }
          // this should be done using lookupMethod(), see bug 286629
          var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
          return ret;
        ]]></body>
      </method>
      <method name="getItems">
        <parameter name="aCalendar"/>
        <body><![CDATA[
          // The minimonth automatically clears extra styles on a month change.
          // Therefore we only need to fill the minimonth with new info.

          let calendar = aCalendar || getCompositeCalendar();
          let filter = calendar.ITEM_FILTER_COMPLETED_ALL |
                       calendar.ITEM_FILTER_CLASS_OCCURRENCES |
                       calendar.ITEM_FILTER_ALL_ITEMS;

          // Get new info
          calendar.getItems(filter,
                            0,
                            this.firstDate,
                            this.lastDate,
                            this);
        ]]></body>
      </method>

      <method name="onSelectDay">
        <parameter name="aDayBox"/>
        <body><![CDATA[
            if (this.mIsReadOnly) {
               return;
            }
            if (this.mSelected) {
              this.mSelected.removeAttribute("selected");
            }
            this.mSelected = aDayBox;
            this.value = aDayBox.date;
            this.fireEvent('select');
          ]]></body>
      </method>

      <method name="update">
        <parameter name="aValue"/>
        <body><![CDATA[
          this.mValue = aValue;
          this.fireEvent('change');
          this.showMonth(aValue);
        ]]></body>
      </method>
      <method name="hidePopupList">
        <body><![CDATA[
          if (!this.mIsReadOnly) {
             var header = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-header");
             header.hidePopupList();
          }
        ]]></body>
      </method>
      <method name="switchMonth">
        <parameter name="aMonth"/>
        <body><![CDATA[
            var newMonth = new Date(this.mEditorDate);
            newMonth.setMonth(aMonth);
            this.showMonth(newMonth);
        ]]></body>
      </method>

      <method name="switchYear">
        <parameter name="aYear"/>
        <body><![CDATA[
            var newMonth = new Date(this.mEditorDate);
            newMonth.setFullYear(aYear);
            this.showMonth(newMonth);
        ]]></body>
      </method>

      <method name="selectDate">
        <parameter name="aDate"/>
        <parameter name="aMainDate"/>
        <body><![CDATA[
            if (!aMainDate || (aDate < this._getStartDate(aMainDate) || aDate > this._getEndDate(aMainDate))) {
                aMainDate = new Date(aDate);
                aMainDate.setDate(1);
            }
            // note, that aMainDate and this.mEditorDate refer to the first day
            // of the corresponding month
            var sameMonth = this._sameDay(aMainDate, this.mEditorDate);
            var sameDate = this._sameDay(aDate,this.mValue);
            if (!sameMonth && !sameDate) {
                // change month and select day
                this.mValue = aDate;
                this.showMonth(aMainDate);
            } else if (!sameMonth) {
                // change month only
                this.showMonth(aMainDate);
            } else if (!sameDate) {
                // select day only
                var day = this.getBoxForDate(aDate);
                if (this.mSelected) {
                    this.mSelected.removeAttribute("selected");
                }
                this.mSelected = day;
                day.setAttribute("selected", "true");
                this.mValue = aDate;
            }
         ]]></body>
      </method>

      <method name="_getStartDate">
        <parameter name="aMainDate"/>
        <body><![CDATA[
          var date = new Date(aMainDate);
          var firstWeekday = (7 + aMainDate.getDay() - this.weekStart) % 7;
          date.setDate(date.getDate()-firstWeekday);
          return date;
        ]]></body>
      </method>

      <method name="_getEndDate">
        <parameter name="aMainDate"/>
        <body><![CDATA[
          var date = this._getStartDate(aMainDate);
          var calbox = document.getAnonymousElementByAttribute(this, "anonid", "minimonth-calendar");
          var days = (calbox.childNodes.length - 1) * 7;
          date.setDate(date.getDate() + days - 1);
          return date;
        ]]></body>
      </method>

      <method name="_sameDay">
        <parameter name="aDate1"/>
        <parameter name="aDate2"/>
        <body><![CDATA[
          if (aDate1 && aDate2 &&
             (aDate1.getDate() == aDate2.getDate()) &&
             (aDate1.getMonth() == aDate2.getMonth()) &&
             (aDate1.getFullYear() == aDate2.getFullYear())) {
             return true;
          }
          return false;
        ]]></body>
      </method>
      <method name="advanceMonth">
        <parameter name="aDir"/>
        <body><![CDATA[
          var advEditorDate = new Date(this.mEditorDate); // at 1st of month
          var advMonth = this.mEditorDate.getMonth() + aDir;
          advEditorDate.setMonth(advMonth);
          this.showMonth(advEditorDate);
        ]]></body>
      </method>
    </implementation>
     <handlers>
      <handler event="DOMMouseScroll">
        <![CDATA[
          if (this.mIsReadOnly) {
            // No scrolling on readonly months
            return;
          }
          var rows = event.detail;
          if (rows == NSUIEvent.SCROLL_PAGE_UP) {
            rows = -1;
          } else if (rows == NSUIEvent.SCROLL_PAGE_DOWN) {
            rows = 1;
          } else {
            // In this case event.detail contains the default number of lines
            // to scroll.  We always want to only scroll 1 month though
            rows = (rows > 0) ? 1 : -1;
          }
          this.advanceMonth(rows);
        ]]>
      </handler>
    </handlers>
  </binding>

  <binding id="minimonth-day" extends="xul:text">
    <handlers>
      <handler event="click" button="0"><![CDATA[
        if (this.minimonthParent.getAttribute("readonly") != "true") {
            this.setAttribute("selected", "true");
            this.minimonthParent.onSelectDay(this)
        }
      ]]></handler>
    </handlers>
  </binding>
</bindings>
