<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/.
-->

<!-- Note that this file depends on helper functions in calUtils.js-->

<!DOCTYPE bindings SYSTEM "chrome://global/locale/global.dtd" >

<bindings id="calendar-month-view-bindings"
  xmlns="http://www.mozilla.org/xbl"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="calendar-month-day-box-item" extends="chrome://calendar/content/calendar-view-core.xml#calendar-editable-item">
    <content mousethrough="never" tooltip="itemTooltip">
      <xul:vbox flex="1">
        <xul:hbox>
          <xul:box anonid="event-container"
                   class="calendar-color-box"
                   xbl:inherits="calendar-uri,calendar-id"
                   flex="1">
            <xul:box class="calendar-event-selection" orient="horizontal" flex="1">
              <xul:stack anonid="eventbox"
                         class="calendar-event-box-container"
                         xbl:inherits="readonly,flashing,alarm,allday,priority,progress,status,calendar,categories"
                         flex="1">
                <xul:hbox class="calendar-event-details">
                  <xul:vbox pack="center">
                    <xul:image anonid="item-icon"
                               class="calendar-item-image"
                               xbl:inherits="progress,allday,itemType"/>
                  </xul:vbox>
                  <xul:label anonid="item-label"
                             class="calendar-month-day-box-item-label"
                             xbl:inherits="context"/>
                  <xul:vbox align="left"
                            flex="1"
                            xbl:inherits="context">
                    <xul:label anonid="event-name"
                               crop="end"
                               flex="1"
                               style="margin: 0;"/>
                    <xul:textbox anonid="event-name-textbox"
                                 class="plain calendar-event-name-textbox"
                                 crop="end"
                                 hidden="true"
                                 wrap="true"/>
                    <xul:spacer flex="1"/>
                  </xul:vbox>
                  <xul:stack anonid="category-box-stack">
                    <xul:calendar-category-box anonid="category-box" xbl:inherits="categories" pack="end"/>
                    <xul:hbox align="center">
                      <xul:hbox anonid="alarm-icons-box"
                                class="alarm-icons-box"
                                pack="end"
                                align="top"
                                xbl:inherits="flashing"/>
                      <xul:image anonid="item-classification-box"
                                 class="item-classification-box"
                                 pack="end"/>
                    </xul:hbox>
                  </xul:stack>
                </xul:hbox>
              </xul:stack>
            </xul:box>
          </xul:box>
        </xul:hbox>
      </xul:vbox>
    </content>
    <implementation>
      <property name="occurrence">
        <getter><![CDATA[
            return this.mOccurrence;
        ]]></getter>
        <setter><![CDATA[
          ASSERT(!this.mOccurrence, "Code changes needed to set the occurrence twice", true);
          this.mOccurrence = val;
          if (cal.isEvent(val)) {
            if (!val.startDate.isDate) {
              var label = document.getAnonymousElementByAttribute(this,"anonid","item-label");
              var df = Components.classes["@mozilla.org/calendar/datetime-formatter;1"].
                       getService(Components.interfaces.calIDateTimeFormatter);
              var timezone = this.calendarView ? this.calendarView.mTimezone :
                             calendarDefaultTimezone();
              var parentDate = ensureDateTime(this.parentBox.date);
              var startTime = val.startDate.getInTimezone(timezone);
              var endTime = val.endDate.getInTimezone(timezone);
              var nextDay = parentDate.clone();
              nextDay.day++;
              var comp = endTime.compare(nextDay);
              if(startTime.compare( parentDate) == -1 ) {
                if (comp == 1)  {
                  label.value = "↔";
                } else if (comp == 0) {
                  label.value = "↤";
                } else {
                  label.value = "⇥ " + df.formatTime(endTime);
                }
              } else {
                if (comp == 1) {
                  label.value = "⇤ " + df.formatTime(startTime);
                } else {
                  label.value = df.formatTime(startTime);
                }
              }
              label.setAttribute("time", "true");
            }
          }

          this.setEditableLabel();
          this.setCSSClasses();
          return val;
        ]]></setter>
      </property>
    </implementation>
  </binding>

  <binding id="calendar-month-day-box" extends="chrome://calendar/content/widgets/calendar-widgets.xml#dragndropContainer">
    <content orient="vertical">
      <xul:label anonid="day-label"
                 crop="end"
                 mousethrough="always"
                 class="calendar-month-day-box-date-label"
                 xbl:inherits="relation,selected,value"/>
      <xul:vbox anonid="day-items" class="calendar-month-day-box-items-box" flex="1">
        <children/>
      </xul:vbox>
    </content>

    <implementation>
      <field name="mDate">null</field>
      <field name="mItemHash">{}</field>
      <field name="mShowMonthLabel">false</field>

      <property name="date"
                onget="return this.mDate"
                onset="this.setDate(val); return val;"/>

      <property name="selected">
        <getter><![CDATA[
          var sel = this.getAttribute("selected");
          if (sel && sel == "true") {
            return true;
          }

          return false;
        ]]></getter>
        <setter><![CDATA[
          if (val)
            this.setAttribute("selected", "true");
          else
            this.removeAttribute("selected");
          return val;
        ]]></setter>
      </property>

      <property name="dayitems">
        <getter>return document.getAnonymousElementByAttribute(this, "anonid", "day-items");</getter>
      </property>

      <property name="showMonthLabel">
        <getter><![CDATA[
          return this.mShowMonthLabel;
        ]]></getter>
        <setter><![CDATA[
          if (this.mShowMonthLabel == val) {
            return val;
          }
          this.mShowMonthLabel = val;

          if (!this.mDate) {
            return val;
          }
          if (val) {
            let monthName = calGetString("dateFormat", "month." + (this.mDate.month + 1) + ".Mmm");
            this.setAttribute("value", this.mDate.day + " " + monthName);
          } else {
            this.setAttribute("value", this.mDate.day);
          }
          return val;
        ]]></setter>
      </property>

      <method name="setDate">
        <parameter name="aDate"/>
        <body><![CDATA[
          if (!aDate) {
            throw Components.results.NS_ERROR_NULL_POINTER;
          }

          // Remove all the old events
          this.mItemHash = {};
          removeChildren(this);

          if (this.mDate && this.mDate.compare(aDate) == 0) {
            return;
          }

          this.mDate = aDate;

          // Set up DOM attributes for custom CSS coloring.
          let weekTitle = cal.getWeekInfoService().getWeekTitle(aDate);
          this.setAttribute("year", aDate.year);
          this.setAttribute("month", aDate.month + 1);
          this.setAttribute("week", weekTitle);
          this.setAttribute("day", aDate.day);

          if (this.mShowMonthLabel) {
             let monthName = calGetString("dateFormat", "month." + (aDate.month+1) + ".Mmm");
             this.setAttribute("value", aDate.day + " " + monthName);
          } else {
             this.setAttribute("value", aDate.day);
          }
        ]]></body>
      </method>

      <method name="addItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          if (aItem.hashId in this.mItemHash) {
            this.deleteItem(aItem);
          }

          function dayboxItemComparator(a, b) {
            let aIsEvent = cal.isEvent(a);
            let aIsTodo = cal.isToDo(a);

            let bIsEvent = cal.isEvent(b);
            let bIsTodo = cal.isToDo(b);

            // sort todos before events
            if (aIsTodo && bIsEvent) return -1;
            if (aIsEvent && bIsTodo) return 1;

            if (aIsEvent && bIsEvent) {
              // sort all day events before events with a duration
              if (a.startDate.isDate && !b.startDate.isDate) return -1;
              if (!a.startDate.isDate && b.startDate.isDate) return 1;

              let cmp = a.startDate.compare(b.startDate);
              if (cmp != 0)
                return cmp;

              cmp = a.endDate.compare(b.endDate);
              if (cmp != 0)
                return cmp;

              cmp = (a.title > b.title) - (a.title < b.title);
              return cmp;
            }

            return 0;
          }

          let box = createXULElement("calendar-month-day-box-item");
          let context = this.getAttribute("item-context") ||
                        this.getAttribute("context");
          box.setAttribute("context", context);
          box.setAttribute("calendar-uri", aItem.calendar.uri.spec);
          box.setAttribute("calendar-id", aItem.calendar.id);

          cal.binaryInsertNode(this, box, aItem, dayboxItemComparator, false);

          box.calendarView = this.calendarView;
          box.item = aItem;
          box.parentBox = this;
          box.occurrence = aItem;

          this.mItemHash[aItem.hashId] = box;
          return box;
        ]]></body>
      </method>

      <method name="selectItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          if (aItem.hashId in this.mItemHash) {
            this.mItemHash[aItem.hashId].selected = true;
          }
        ]]></body>
      </method>

      <method name="unselectItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          if (aItem.hashId in this.mItemHash) {
            this.mItemHash[aItem.hashId].selected = false;
          }
        ]]></body>
      </method>

      <method name="deleteItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          if (aItem.hashId in this.mItemHash) {
            let node =  this.mItemHash[aItem.hashId];
            node.parentNode.removeChild(node);
            delete this.mItemHash[aItem.hashId];
          }
        ]]></body>
      </method>

      <method name="onDropItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          return cal.moveItem(aItem, this.mDate);
        ]]></body>
      </method>

    </implementation>

    <handlers>
      <handler event="mousedown"><![CDATA[
        event.stopPropagation();
        if (this.mDate)
          this.calendarView.selectedDay = this.mDate;
      ]]></handler>
      <handler event="dblclick"><![CDATA[
        event.stopPropagation();
        this.calendarView.controller.createNewEvent();
      ]]></handler>
      <handler event="click" button="0"><![CDATA[
        if (!(event.ctrlKey || event.metaKey)) {
          this.calendarView.setSelectedItems(0, []);
        }
      ]]></handler>
      <handler event="wheel"><![CDATA[
        if (getParentNodeOrThisByAttribute(event.originalTarget, "anonid", "day-label") == null) {
            if (this.dayitems.scrollHeight > this.dayitems.clientHeight) {
                event.stopPropagation();
            }
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="calendar-month-base-view" extends="chrome://calendar/content/calendar-base-view.xml#calendar-base-view">
    <content style="overflow: auto;" flex="1" xbl:inherits="context,item-context">
      <xul:vbox anonid="mainbox" flex="1">
        <xul:hbox class="labeldaybox-container"
                  anonid="labeldaybox"
                  equalsize="always"/>

        <xul:grid anonid="monthgrid" flex="1">
          <xul:columns anonid="monthgridcolumns" equalsize="always">
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
            <xul:column flex="1" class="calendar-month-view-grid-column"/>
          </xul:columns>

          <xul:rows anonid="monthgridrows" equalsize="always">
            <xul:row flex="1" class="calendar-month-view-grid-row">
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
            </xul:row>
            <xul:row flex="1" class="calendar-month-view-grid-row">
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
            </xul:row>
            <xul:row flex="1" class="calendar-month-view-grid-row">
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
            </xul:row>
            <xul:row flex="1" class="calendar-month-view-grid-row">
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
            </xul:row>
            <xul:row flex="1" class="calendar-month-view-grid-row">
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
            </xul:row>
            <xul:row flex="1" class="calendar-month-view-grid-row">
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
              <xul:calendar-month-day-box/>
            </xul:row>
          </xul:rows>
        </xul:grid>
      </xul:vbox>
    </content>

    <implementation implements="calICalendarView">

      <!-- constructor -->
      <constructor><![CDATA[
          // Set the preference for the default start of the week
          this.weekStartOffset = getPrefSafe("calendar.week.start", 0);

          for (var i = 0; i < 7; i++) {
              let hdr = createXULElement("calendar-day-label");
              this.labeldaybox.appendChild(hdr);
              hdr.weekDay = (i + this.mWeekStartOffset) % 7;
              hdr.shortWeekNames = false;
          }
      ]]></constructor>

      <!-- fields -->

      <field name="mDateBoxes">null</field>
      <field name="mSelectedDayBox">null</field>

      <field name="mShowDaysOutsideMonth">true</field>
      <field name="mShowFullMonth">true</field>

      <field name="mClickedTime">null</field>

      <!-- other methods -->
      <method name="setAttribute">
        <parameter name="aAttr"/>
        <parameter name="aVal"/>
        <body><![CDATA[
          var needsrelayout = false;
          if (aAttr == "context" || aAttr == "item-context")
              needsrelayout = true;

          var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);

          if (needsrelayout)
              this.relayout();

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

      <!-- calICalendarView -->

      <property name="supportsDisjointDates" readonly="true"
                onget="return false;"/>
      <property name="hasDisjointDates" readonly="true"
                onget="return false;"/>

      <property name="startDate" readonly="true"
                onget="return this.mStartDate"/>

      <property name="endDate" readonly="true"
                onget="return this.mEndDate"/>

      <property name="showFullMonth">
        <getter><![CDATA[
          return this.mShowFullMonth;
        ]]></getter>
        <setter><![CDATA[
          this.mShowFullMonth = val;
          return val;
        ]]></setter>
      </property>

      <!-- this property may be overridden by the
          descendent classes if neeeded  -->
      <property name="weeksInView">
        <getter><![CDATA[
            return 0;
        ]]></getter>
        <setter><![CDATA[
            return val;
        ]]></setter>
      </property>

      <method name="handlePreference">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aPreference"/>
        <body><![CDATA[
           aSubject.QueryInterface(Components.interfaces.nsIPrefBranch);

           switch (aPreference) {
               case "calendar.previousweeks.inview":
                   this.updateDaysOffPrefs();
                   this.refreshView();
                   break;

               case "calendar.week.start":
                   this.weekStartOffset = aSubject.getIntPref(aPreference);
                   // Refresh the view so the settings take effect
                   this.refreshView();
                   break;

               case "calendar.weeks.inview":
                   this.weeksInView = aSubject.getIntPref(aPreference);
                   break;

               case "calendar.previousweeks.inview":
                   this.refreshView();
                   break;

               default:
                   this.handleCommonPreference(aSubject, aTopic, aPreference);
                   break;
           }
           return;
        ]]></body>
      </method>

      <method name="getSelectedItems">
        <parameter name="aCount"/>
        <body><![CDATA[
          aCount.value = this.mSelectedItems.length;
          return this.mSelectedItems;
        ]]></body>
      </method>

      <method name="setSelectedItems">
        <parameter name="aCount"/>
        <parameter name="aItems"/>
        <parameter name="aSuppressEvent"/>
        <body><![CDATA[
          if (this.mSelectedItems.length) {
              for each (var item in this.mSelectedItems) {
                  let oldboxes = this.findDayBoxesForItem(item);
                  for each (let oldbox in oldboxes) {
                      oldbox.unselectItem(item);
                  }
              }
          }

          this.mSelectedItems = aItems || [];

          if (this.mSelectedItems.length) {
              for each (var item in this.mSelectedItems) {
                  let newboxes = this.findDayBoxesForItem(item);
                  for each (let newbox in newboxes) {
                      newbox.selectItem(item);
                  }
              }
          }

          if (!aSuppressEvent) {
              this.fireEvent("itemselect", this.mSelectedItems);
          }
        ]]></body>
      </method>

      <method name="centerSelectedItems">
        <body>
        </body>
      </method>

      <property name="selectedDay">
        <getter><![CDATA[
          if (this.mSelectedDayBox)
            return this.mSelectedDayBox.date.clone();

          return null;
        ]]></getter>
        <setter><![CDATA[
          if (this.mSelectedDayBox)
            this.mSelectedDayBox.selected = false;

          var realVal = val;
          if (!realVal.isDate) {
            realVal = val.clone();
            realVal.isDate = true;
          }
          let box = this.findDayBoxForDate(realVal);
          if (box) {
            box.selected = true;
            this.mSelectedDayBox = box;
          }
          this.fireEvent("dayselect", realVal);
          return val;
        ]]></setter>
      </property>

      <property name="selectedDateTime">
        <getter><![CDATA[
            return getDefaultStartDate(this.selectedDay);
        ]]></getter>
        <setter><![CDATA[
            this.mClickedTime = val;
        ]]></setter>
      </property>

      <method name="showDate">
        <parameter name="aDate"/>
        <body><![CDATA[
          this.setDateRange(aDate.startOfMonth, aDate.endOfMonth);
          this.selectedDay = aDate;
        ]]></body>
      </method>

      <method name="onResize">
      <parameter name="aBinding"/>
        <body><![CDATA[
          aBinding.adjustWeekdayLength();
          // Delete the timer for the time indicator in day/week view.
          timeIndicator.cancel();
        ]]></body>
      </method>

      <method name="setDateRange">
        <parameter name="aStartDate"/>
        <parameter name="aEndDate"/>
        <body><![CDATA[
          this.rangeStartDate = aStartDate;
          this.rangeEndDate = aEndDate;
          this.mStartDate = getWeekInfoService().getStartOfWeek(aStartDate.getInTimezone(this.mTimezone));
          this.mEndDate = getWeekInfoService().getEndOfWeek(aEndDate.getInTimezone(this.mTimezone));
          this.refresh();

          // Update the navigation bar.
          cal.navigationBar.setDateRange(aStartDate, aEndDate);
        ]]></body>
      </method>

      <method name="setDateList">
        <parameter name="aCount"/>
        <parameter name="aDates"/>
        <body><![CDATA[
           throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        ]]></body>
      </method>

      <method name="getDateList">
        <parameter name="aCount"/>
        <body><![CDATA[
          if (!this.mStartDate || !this.mEndDate) {
            aCount.value = 0;
            return [];
          }

          let results = [];
          let curDate = this.mStartDate.clone();
          curDate.isDate = true;

          while (curDate.compare(this.mEndDate) <= 0) {
            results.push(curDate.clone());
            curDate.day += 1;
          }
          aCount.value = results.length;
          return results;
        ]]></body>
      </method>

      <!-- public properties and methods -->

      <!-- whether to show days outside of the current month -->
      <property name="showDaysOutsideMonth">
        <getter><![CDATA[
          return this.mShowDaysOutsideMonth;
        ]]></getter>
        <setter><![CDATA[
          if (this.mShowDaysOutsideMonth != val) {
            this.mShowDaysOutsideMonth = val;
            this.refresh();
          }
          return val;
        ]]></setter>
      </property>

      <!-- private properties and methods -->

      <property name="monthgrid" readonly="true"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'monthgrid');"/>

      <property name="monthgridrows" readonly="true"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'monthgridrows');"/>

      <method name="relayout">
        <body><![CDATA[
          // Adjust headers based on the starting day of the week, if necessary
          if (this.labeldaybox.firstChild.weekDay != this.weekStartOffset) {
            for (let i = 0; i < this.labeldaybox.childNodes.length; i++) {
              this.labeldaybox.childNodes[i].weekDay = (i + this.weekStartOffset) % 7;
            }
          }

          if (this.mSelectedItems.length) {
            this.mSelectedItems = [];
          }

          if (!this.mStartDate || !this.mEndDate) {
            throw Components.results.NS_ERROR_FAILURE;
          }

          // Days that are not in the main month on display are displayed with
          // a gray background.  Unless the month actually starts on a Sunday,
          // this means that mStartDate.month is 1 month less than the main month
          var mainMonth = this.mStartDate.month;
          if (this.mStartDate.day != 1) {
            mainMonth++;
            mainMonth = mainMonth % 12;
          }

          var dateBoxes = [];
          var today = this.today();

          // This gets set to true, telling us to collapse the rest of the rows
          var finished = false;
          var dateList = this.getDateList({})

          var rows = this.monthgridrows.childNodes;

          // Iterate through each monthgridrow and set up the day-boxes that 
          // are its child nodes.  Remember, childNodes is not a normal array,
          // so don't use the in operator if you don't want extra properties 
          // coming out.
          for (var i = 0; i < rows.length; i++) {
            var row = rows[i];
            // If we've already assigned all of the day-boxes that we need, just 
            // collapse the rest of the rows, otherwise expand them if needed.
            if (finished) {
              row.setAttribute("collapsed", true);
              continue;
            } else {
              row.removeAttribute("collapsed");
            }
            for (var j = 0; j < row.childNodes.length; j++) {
              var daybox = row.childNodes[j];
              var date = dateList[dateBoxes.length];

              // Remove the attribute "relation" for all the column headers.
              // Consider only the first row index otherwise it will be
              // removed again afterwards the correct setting.
              if (i == 0) {
                  this.labeldaybox.childNodes[j].removeAttribute("relation");
              }

              daybox.setAttribute("context", this.getAttribute("context"));
              daybox.setAttribute("item-context", this.getAttribute("item-context") || this.getAttribute("context"));

              // Set the box-class depending on if this box displays a day in
              // the month being currently shown or not.
              var boxClass;
              if (this.showFullMonth) {
                  boxClass = "calendar-month-day-box-" + 
                             (mainMonth == date.month ? "current-month" : "other-month");
              } else {
                  boxClass = "calendar-month-day-box-current-month";
              }
              function matchesDayOff(dayOffNum) { return dayOffNum == date.weekday; }
              if (this.mDaysOffArray.some(matchesDayOff)) {
                boxClass = "calendar-month-day-box-day-off " + boxClass;
              }

              // Set up date relations
              switch (date.compare(today)) {
                  case -1:
                      daybox.setAttribute("relation", "past");
                      break;
                  case 0:
                      daybox.setAttribute("relation", "today");
                      this.labeldaybox.childNodes[j].setAttribute("relation", "today");
                      break;
                  case 1:
                      daybox.setAttribute("relation", "future");
                      break;
              }

              daybox.setAttribute("class", boxClass);

              daybox.setDate(date);
              if (date.day == 1 || date.day == date.endOfMonth.day) {
                daybox.showMonthLabel = true;
              } else {
                daybox.showMonthLabel = false;
              }
              daybox.calendarView = this;
              daybox.date = date;
              dateBoxes.push(daybox);

              // If we've now assigned all of our dates, set this to true so we
              // know we can just collapse the rest of the rows.
              if (dateBoxes.length == dateList.length) {
                finished = true;
              }
            }
          }

          // If we're not showing a full month, then add a few extra labels to
          // help the user orient themselves in the view.
          if (!this.mShowFullMonth) {
            dateBoxes[0].showMonthLabel = true;
            dateBoxes[dateBoxes.length - 1].showMonthLabel = true;
          }

          // Store these, so that we can access them later
          this.mDateBoxes = dateBoxes;
          this.hideDaysOff();

          this.adjustWeekdayLength();
        ]]></body>
      </method>

      <method name="hideDaysOff">
        <body><![CDATA[
          var columns = document.getAnonymousElementByAttribute(this, "anonid", "monthgridcolumns").childNodes;
          let headerkids = document.getAnonymousElementByAttribute(this, "anonid", "labeldaybox").childNodes;
          for (var i in columns) {
            var dayForColumn = (Number(i) + this.mWeekStartOffset) % 7;
            var dayOff = (this.mDaysOffArray.indexOf(dayForColumn) != -1);
            columns[i].collapsed = dayOff && !this.mDisplayDaysOff;
            headerkids[i].collapsed = dayOff && !this.mDisplayDaysOff;
          }
        ]]></body>
      </method>

      <method name="findDayBoxForDate">
        <parameter name="aDate"/>
        <body><![CDATA[
          for each (let box in this.mDateBoxes) {
            if (box.mDate.compare(aDate) == 0)
              return box;
          }
          return null;
        ]]></body>
      </method>

      <method name="findDayBoxesForItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          var targetDate = null;
          var finishDate = null;
          var boxes = new Array();

          // All our boxes are in default tz, so we need these times in them too.
          if (isEvent(aItem)) {
            targetDate = aItem.startDate.getInTimezone(this.mTimezone);
            finishDate = aItem.endDate.getInTimezone(this.mTimezone);
          } else if (isToDo(aItem)) {
            if (aItem.entryDate) {
              targetDate = aItem.entryDate.getInTimezone(this.mTimezone);
              if (aItem.dueDate) {
                finishDate = aItem.dueDate.getInTimezone(this.mTimezone);
              }
            }
          }

          if (!targetDate)
            return boxes;

          if (!finishDate) {
            let maybeBox = this.findDayBoxForDate(targetDate);
            if (maybeBox) {
              boxes.push(maybeBox);
            }
            return boxes;
          }

          if (!targetDate.isDate) {
            // Reset the time to 00:00, so that we really get all the boxes
            targetDate.hour = 0;
            targetDate.minute = 0;
            targetDate.second = 0;
          }

          if (targetDate.compare(finishDate) == 0) {
              // Zero length events are silly, but we have to handle them
              let box = this.findDayBoxForDate(targetDate);
              if (box) {
                  boxes.push(box);
              }
          }

          while (targetDate.compare(finishDate) == -1) {
            let box = this.findDayBoxForDate(targetDate);

            // This might not exist, if the event spans the view start or end
            if (box) {
                boxes.push(box);
            }
            targetDate.day += 1;
          }

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

      <method name="doAddItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          let boxes = this.findDayBoxesForItem(aItem);

          if (!boxes.length)
            return;

          for each (let box in boxes) {
            box.addItem(aItem);
          }
        ]]></body>
      </method>

      <method name="doDeleteItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          let boxes = this.findDayBoxesForItem(aItem);

          if (!boxes.length)
            return;

          function isNotItem(a) {
              return (a.hashId != aItem.hashId);
          }
          var oldLength = this.mSelectedItems.length;
          this.mSelectedItems = this.mSelectedItems.filter(isNotItem);

          for each (let box in boxes) {
            box.deleteItem(aItem);
          }

          // If a deleted event was selected, we need to announce that the
          // selection changed.
          if (oldLength != this.mSelectedItems.length) {
              this.fireEvent("itemselect", this.mSelectedItems);
          }
        ]]></body>
      </method>

      <method name="deleteItemsFromCalendar">
        <parameter name="aCalendar"/>
        <body><![CDATA[
          for each (let box in this.mDateBoxes) {
            for each (let node in box.mItemHash) {
              let item = node.item;
              if (item.calendar.id == aCalendar.id) {
                box.deleteItem(item);
              }
            }
          }
        ]]></body>
      </method>

      <method name="flashAlarm">
        <parameter name="aAlarmItem"/>
        <parameter name="aStop"/>
        <body><![CDATA[
          var showIndicator = getPrefSafe("calendar.alarms.indicator.show", true);
          var totaltime = getPrefSafe("calendar.alarms.indicator.totaltime", 3600);

          if (!aStop && (!showIndicator || totaltime < 1)) {
            // No need to animate if the indicator should not be shown.
            return;
          }

          // Make sure the flashing attribute is set or reset on all visible
          // boxes.
          let boxes = this.findDayBoxesForItem(aAlarmItem);
          for each (var box in boxes) {
            for each (var itemData in box.mItemHash) {
              if (itemData.item.hasSameIds(aAlarmItem)) {
                if (aStop) {
                  itemData.removeAttribute("flashing");
                } else {
                  itemData.setAttribute("flashing", "true");
                }
              }
            }
          }

          if (!aStop) {
            // Set up a timer to stop the flashing after the total time.
            var this_ = this;
            this.mFlashingEvents[aAlarmItem.hashId] = aAlarmItem;
            setTimeout(function() { this_.flashAlarm(aAlarmItem, true) }, totaltime);
          } else {
            // We are done flashing, prevent newly created event boxes from flashing.
            delete this.mFlashingEvents[aAlarmItem.hashId];
          }
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="wheel"><![CDATA[
        const pixelThreshold = 150;
        let scrollEnabled = getPrefSafe('calendar.view.mousescroll', true);
        if (!event.ctrlKey && !event.shiftKey &&
            !event.altKey && !event.metaKey && scrollEnabled) {
            // In the month view, the only thing that can be scrolled
            // is the month the user is in. calendar-base-view takes care
            // of the shift key, so only move the view when no modifier
            // is pressed.
            let deltaView = 0;
            if (event.deltaMode == event.DOM_DELTA_LINE) {
                if (event.deltaY != 0) {
                    deltaView = event.deltaY < 0 ? -1 : 1;
                }
            } else if (event.deltaMode == event.DOM_DELTA_PIXEL) {
                this.mPixelScrollDelta += event.deltaY;
                if (this.mPixelScrollDelta > pixelThreshold){
                    deltaView = 1;
                    this.mPixelScrollDelta = 0;
                } else if (this.mPixelScrollDelta < -pixelThreshold) {
                    deltaView = -1;
                    this.mPixelScrollDelta = 0;
                }
            }

            if (deltaView != 0) {
                this.moveView(deltaView);
            }
        }
      ]]></handler>
    </handlers>

  </binding>
</bindings>
