calendar/base/content/calendar-month-view.xml
author Jim <squibblyflabbetydoo@gmail.com>
Fri, 17 Dec 2010 10:17:38 +0000
changeset 6852 2df02366747d0dcbaa8672a011482349fff81d51
parent 6479 bc64f8b8655b3c8c656d7a29e2cf6dc35f5087f5
child 7137 c4e01e8c0649059ee104459fb44f1c688542b08e
permissions -rw-r--r--
Bug 304835 - Attachments aren't draggable from compose windows; r=bienvenu

<?xml version="1.0" encoding="UTF-8"?>
<!--
   - ***** 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 calendar views.
   -
   - The Initial Developer of the Original Code is
   -   Oracle Corporation
   - Portions created by the Initial Developer are Copyright (C) 2005
   - the Initial Developer. All Rights Reserved.
   -
   - Contributor(s):
   -   Vladimir Vukicevic <vladimir@pobox.com>
   -   Stefan Sitter <ssitter@googlemail.com>
   -   Clint Talbert <cmtalbert@myfastmail.com>
   -   Michael Buettner <michael.buettner@sun.com>
   -   Philipp Kewisch <mozilla@kewis.ch>
   -   Markus Adrario <MarkusAdrario@web.de>
   -   Berend Cornelius <berend.cornelius@sun.com>
   -   Martin Schroeder <mschroeder@mozilla.x-home.org>
   -   Gianfranco Balza <bv1578@gmail.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 GPL or the LGPL. 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 *****
-->

<!-- 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>
                    <xul:calendar-category-box anonid="category-box" xbl:inherits="categories" pack="end"/>
                    <xul:hbox anonid="alarm-icons-box"
                              class="alarm-icons-box"
                              align="center"
                              pack="end"
                              xbl:inherits="flashing"/>
                  </xul:stack>
                </xul:hbox>
                <xul:image anonid="gradient"
                           class="calendar-event-box-gradient"
                           height="1px"
                           mousethrough="always"/>
              </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>
      <!-- mItemData will always be kept sorted in display order -->
      <field name="mItemData">[]</field>
      <field name="mShowMonthLabel">false</field>

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

      <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.mItemData = new Array();
          while (this.hasChildNodes()) {
              this.removeChild(this.lastChild);
          }

          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[
          // XXX This scales badly for adding lots of items: O(N^2)
          for each (ed in this.mItemData) {
            if (aItem.hashId == ed.item.hashId)
            {
              this.deleteItem(aItem);
              break;
            }
          }

          function dayboxItemComparator(a, b) {
            var aIsEvent = isEvent(a.item);
            var aIsTodo = isToDo(a.item);

            var bIsEvent = isEvent(b.item);
            var bIsTodo = isToDo(b.item);

            if ((!aIsEvent && !aIsTodo) || (!bIsEvent && !bIsTodo)) {
              // XXX ????
              dump ("Don't know how to sort these two events: " + a.item + " " + b.item + "\n");
              return 0;
            }

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

            // XXX how do I sort todos?
            if (aIsTodo && bIsTodo) {
              return 0;
            }

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

              var cmp;

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

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

              if (a.item.title < b.item.title)
                return -1;
              if (a.item.title > b.item.title)
                return 1;
            }

            return 0;
          }

          // Insert the new item block (while keeping the array sorted),
          // and then relayout. Use a binary search insertation because the
          // array is already sorted. Array.Sort would use quicksort which is
          // not good for this case.
          binaryInsert(this.mItemData, { item: aItem }, dayboxItemComparator, false);
          this.relayout();
        ]]></body>
      </method>

      <method name="selectItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          for each (var itd in this.mItemData) {
              if (aItem && (itd.item.hashId == aItem.hashId)) {
                  itd.box.selected = true;
              }
          }
        ]]></body>
      </method>

      <method name="unselectItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          for each (var itd in this.mItemData) {
              if (aItem && (itd.item.hashId == aItem.hashId)) {
                  itd.box.selected = false;
              }
          }
        ]]></body>
      </method>

      <method name="deleteItem">
        <parameter name="aItem"/>
        <body><![CDATA[
          var deleted = [];

          var origLen = this.mItemData.length;
          this.mItemData = this.mItemData.filter(
            function(itd) {
              if (aItem.hashId == itd.item.hashId)
              {
                deleted.push(itd);
                return false;
              }
              return true;
            });

          if (deleted.length > 0) {
            for each (itd in deleted) {
              if (itd.box)
                this.removeChild(itd.box);
            }
            // no need to relayout; all we did was delete
            //this.relayout();
          }
        ]]></body>
      </method>

      <method name="relayout">
        <body><![CDATA[
          for (var i = 0; i < this.mItemData.length; i++) {
            var itd = this.mItemData[i];

            if (!itd.box) {
              // find what element to insert before
              var before = null;
              for (var j = i+1; !before && this.mItemData[j]; j++)
                before = this.mItemData[j].box;

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

              this.insertBefore(box, before);

              box.calendarView = this.calendarView;
              box.item = itd.item;
              box.parentBox = this;
              box.occurrence = itd.item;
              itd.box = box;
            }
          }
        ]]></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[
        this.calendarView.setSelectedItems(0, []);
      ]]></handler>
      <handler event="DOMMouseScroll"><![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"
                  chromedir="&locale.dir;"
                  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.nsIPrefBranch2);

           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 (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 (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();
        ]]></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 = [];
          }
	  
          // Clear out the old selection, since it won't be valid after relayout
          if (this.mSelectedDayBox) {
            this.mSelectedDayBox.selected = false;
          }

          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];

              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");
                      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();

          // Highlight box and column header corresponding to today if it's
          // in the range of the view.
          for (let i = 0; i < this.labeldaybox.childNodes.length; i++) {
              this.labeldaybox.childNodes[i].removeAttribute("relation");
          }
          if (today.compare(dateList[0]) != -1 &&
              today.compare(dateList[dateList.length-1]) != 1) {
              this.findDayBoxForDate(today).setAttribute("today", "true");
              this.labeldaybox.childNodes[(7 + today.weekday - this.weekStartOffset) % 7]
                              .setAttribute("relation", "today");
          }
          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 (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 (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 (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[
          let deleteHash = {};
          for each (let box in this.mDateBoxes) {
            let boxItemData = Array.slice(box.mItemData);
            for each (let itemData in boxItemData) {
              let item = itemData.item;
              if (!deleteHash[item.hashId] && item.calendar.id == aCalendar.id) {
                deleteHash[item.hashId] = true;
                this.doDeleteItem(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.mItemData) {
              if (itemData.item.hasSameIds(aAlarmItem)) {
                if (aStop) {
                  itemData.box.removeAttribute("flashing");
                } else {
                  itemData.box.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="DOMMouseScroll"><![CDATA[
        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.
            this.moveView(event.detail < 0 ? -1 : 1);
        }
      ]]></handler>
    </handlers>

  </binding>
</bindings>