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

<bindings id="calendar-multiday-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-base-view">
    <resources>
      <stylesheet src="chrome://calendar/skin/calendar-alarms.css"/>
    </resources>
    <implementation>
      <field name="mWeekStartOffset">0</field>
      <field name="mRangeStartDate">null</field>;
      <field name="mRangeEndDate">null</field>;
      <field name="mWorkdaysOnly">false</field>
      <field name="mRefreshQueue">[]</field>
      <field name="mRefreshPending">null</field>
      <field name="mCalendar">null</field>
      <field name="mController">null</field>
      <field name="mStartDate">null</field>
      <field name="mEndDate">null</field>
      <field name="mTasksInView">false</field>
      <field name="mShowCompleted">false</field>
      <field name="mDisplayDaysOff">true</field>
      <field name="mDaysOffArray">[0,6]</field>
      <field name="mTimezone">null</field>
      <field name="mFlashingEvents">new Object()</field>
      <field name="mSelectedItems">[]</field>
      <field name="mLongWeekdayTotalPixels">-1</field>
      <field name="mResizeHandler">null</field>
      <field name="mDropShadowsLength">null</field>
      <field name="mShadowOffset">null</field>
      <field name="mDropShadows">null</field>
      <field name="mMagnifyAmount">0</field>
      <field name="mPixelScrollDelta">0</field>
      <field name="mPrefObserver"><![CDATA[
      ({ calView: this,
         observe: function calViewPrefChange(subj, topic, pref) {
             this.calView.handlePreference(subj, topic, pref);
             return;
         }
      })
      ]]></field>

      <field name="mOperationListener"><![CDATA[
      ({
          calView: this,

          onOperationComplete:
          function onOperationComplete(aCalendar, aStatus, aOperationType,
                                       aId, aDetail) {
              // Fire event
              this.calView.fireEvent('viewloaded', aOperationType);

              // signal that the current operation finished.
              this.calView.mRefreshPending = null;

              // immediately start the next job on the queue.
              this.calView.popRefreshQueue();
          },

          onGetResult:
          function onGetResult(aCalendar, aStatus, aItemType, aDetail,
                               aCount, aItems) {
              if (!Components.isSuccessCode(aStatus)) {
                  return;
              }
              let items = aItems.filter(function(x) !isToDo(x) ||
                                (x.entryDate && x.dueDate));

              for each (let item in items) {
                  this.calView.doAddItem(item);
              }
          }
      })
      ]]></field>

      <field name="mObserver"><![CDATA[
        // the calIObserver, calICompositeObserver, and calIAlarmServiceObserver
        ({
            QueryInterface: function QueryInterface(aIID) {
                return cal.doQueryInterface(this, null, aIID,
                                    [Components.interfaces.calIObserver,
                                     Components.interfaces.calIAlarmServiceObserver,
                                     Components.interfaces.calICompositeObserver,
                                     Components.interfaces.nsISupports]);
            },

            calView: this,

            onStartBatch: function onStartBatch() {
            },
            onEndBatch: function onEndBatch() {
            },

            onLoad: function onLoad() {
                this.calView.refresh();
            },

            onAddItem: function onAddItem(aItem) {

                if (isToDo(aItem)) {
                    if (!aItem.entryDate || !aItem.dueDate) {
                        return;
                    }
                    if(!this.calView.mTasksInView){
                        return;
                    }
                    if (aItem.isCompleted && !this.calView.mShowCompleted) {
                        return;
                    }
                }

                let occs = aItem.getOccurrencesBetween(this.calView.startDate,
                                                       this.calView.queryEndDate,
                                                       {});
                for each (let occ in occs) {
                    this.calView.doAddItem(occ);
                }
                return;
            },

            onModifyItem: function onModifyItem(aNewItem, aOldItem) {

                if (isToDo(aNewItem) && isToDo(aOldItem) &&
                    !this.calView.mTasksInView) {
                    return;
                }
                let occs;

                if (!isToDo(aOldItem) ||
                    (aOldItem.entryDate && aOldItem.dueDate)) {
                    occs = aOldItem.getOccurrencesBetween(this.calView.startDate,
                                                          this.calView.queryEndDate,
                                                          {});
                    for each (let occ in occs) {
                        this.calView.doDeleteItem(occ);
                    }
                }
                if (isToDo(aNewItem)) {
                    if (!aNewItem.entryDate || !aNewItem.dueDate || !this.calView.mTasksInView) {
                        return;
                    }
                    if (aNewItem.isCompleted && !this.calView.mShowCompleted) {
                        return;
                    }
                }

                occs = aNewItem.getOccurrencesBetween(this.calView.startDate,
                                                      this.calView.queryEndDate,
                                                      {});
                for each (let occ in occs) {
                    this.calView.doAddItem(occ);
                }
            },

            onDeleteItem: function onDeleteItem(aItem) {

                if (isToDo(aItem)) {
                    if (!this.calView.mTasksInView) {
                        return;
                    }
                    if (!aItem.entryDate || !aItem.dueDate) {
                        return;
                    }
                    if (aItem.isCompleted && !this.calView.mShowCompleted) {
                        return;
                    }
                }

                let occs = aItem.getOccurrencesBetween(this.calView.startDate,
                                                       this.calView.queryEndDate,
                                                       {});
                for each (let occ in occs) {
                    this.calView.doDeleteItem(occ);
                }
            },

            onError: function onError(aCalendar, aErrNo, aMessage) { },

            onPropertyChanged: function(aCalendar, aName, aValue, aOldValue) {
                switch (aName) {
                    case "suppressAlarms":
                        if (!getPrefSafe("calendar.alarms.indicator.show", true) ||
                            aCalendar.getProperty("capabilities.alarms.popup.supported") === false ) {
                            break;
                        }
                        // else fall through
                    case "readOnly":
                    case "disabled":
                        // XXXvv we can be smarter about how we handle this stuff
                        this.calView.refresh();
                        break;
                }
            },

            onPropertyDeleting: function(aCalendar, aName) {
                // Values are not important here yet.
                this.onPropertyChanged(aCalendar, aName, null, null);
            },

            //
            // calIAlarmServiceObserver stuff
            //
            onAlarm: function onAlarm(aAlarmItem) {
                this.calView.flashAlarm(aAlarmItem, false);
            },

            onRemoveAlarmsByItem: function onRemoveAlarmsByItem(aItem) {
                // Stop the flashing for the item.
                this.calView.flashAlarm(aItem, true);
            },

            onRemoveAlarmsByCalendar: function onRemoveAlarmsByCalendar(aCalendar) {
                // Stop the flashing for all items of this calendar
                for each (let item in this.calView.mFlashingEvents) {
                    if (item.calendar.id == aCalendar.id) {
                        this.calView.flashAlarm(item, true);
                    }
                }
            },

            onAlarmsLoaded: function _onAlarmsLoaded(aCalendar) {},

            //
            // calICompositeObserver stuff
            // XXXvv we can be smarter about how we handle this stuff
            //
            onCalendarAdded: function onCalendarAdded(aCalendar) {
                if (!aCalendar.getProperty("disabled")) {
                    this.calView.addItemsFromCalendar(aCalendar);
                }
            },

            onCalendarRemoved: function onCalendarRemoved(aCalendar) {
                if (!aCalendar.getProperty("disabled")) {
                    this.calView.deleteItemsFromCalendar(aCalendar);
                }
            },

            onDefaultCalendarChanged:
            function onDefaultCalendarChanged(aNewDefaultCalendar) {
                // don't care, for now
            }
        })
      ]]></field>

      <constructor><![CDATA[
        Components.utils.import("resource://gre/modules/Services.jsm");

        const kWorkdaysCommand = "calendar_toggle_workdays_only_command";
        const kTasksInViewCommand = "calendar_toggle_tasks_in_view_command";
        const kShowCompleted = "calendar_toggle_show_completed_in_view_command";
        const kOrientation = "calendar_toggle_orientation_command";

        this.workdaysOnly = (document.getElementById(kWorkdaysCommand)
                                .getAttribute("checked") == "true");
        this.tasksInView = (document.getElementById(kTasksInViewCommand)
                                .getAttribute("checked") == "true");
        this.rotated = (document.getElementById(kOrientation)
                                .getAttribute("checked") == "true");
        this.showCompleted = (document.getElementById(kShowCompleted)
                                .getAttribute("checked") == "true");

        this.mTimezone = calendarDefaultTimezone();
        let alarmService = Components.classes['@mozilla.org/calendar/alarm-service;1']
                           .getService(Components.interfaces.calIAlarmService);
        alarmService.addObserver(this.mObserver);
        let self = this;
        this.setAttribute("type", this.type);
        this.mResizeHandler = function resizeHandler() {
            self.onResize(self); };
        this.viewBroadcaster.addEventListener(this.getAttribute("type") + "viewresized", this.mResizeHandler, true);
        // add a preference observer to monitor changes
        Services.prefs.addObserver("calendar.", this.mPrefObserver, false);
        this.weekStartOffset = getPrefSafe("calendar.week.start", 0);
        this.updateDaysOffPrefs();
      ]]></constructor>

      <destructor><![CDATA[
        Components.utils.import("resource://gre/modules/Services.jsm");

        if (this.mCalendar) {
            this.mCalendar.removeObserver(this.mObserver);
        }
        let alarmService = Components.classes['@mozilla.org/calendar/alarm-service;1']
                           .getService(Components.interfaces.calIAlarmService);
        alarmService.removeObserver(this.mObserver);
        this.viewBroadcaster.removeEventListener(this.getAttribute("type") + "viewresized", this.mResizeHandler, true);
        Services.prefs.removeObserver("calendar.", this.mPrefObserver);
      ]]></destructor>

      <property name="type" readonly="true">
        <getter><![CDATA[
            let typelist = this.id.split("-");
            return typelist[0];
        ]]></getter>
      </property>

      <property name="viewBroadcaster" readonly="true"
                onget="return document.getElementById('calendarviewBroadcaster')"/>

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

      <property name="rotated"
                onget="return (this.orient == 'horizontal')"
                onset="return (this.orient = (val ? 'horizontal' : 'vertical'))"/>
      <property name="supportsRotation" readonly="true"
                onget="return false"/>
      <property name="displayDaysOff"
                onget="return this.mDisplayDaysOff;"
                onset="return (this.mDisplayDaysOff = val);"/>
      <property name="controller"
                onget="return this.mController;"
                onset="return (this.mController = val);"/>
      <property name="daysOffArray"
                onget="return this.mDaysOffArray;"
                onset="return (this.mDaysOffArray = val);"/>
      <property name="tasksInView"
                onget="return this.mTasksInView;"
                onset="return (this.mTasksInView = val);"/>
      <property name="showCompleted"
                onget="return this.mShowCompleted;"
                onset="return (this.mShowCompleted = val);"/>
      <property name="timezone"
                onget="return this.mTimezone;"
                onset="return (this.mTimezone = val);"/>
      <property name="workdaysOnly"
                onget="return this.mWorkdaysOnly;"
                onset="return (this.mWorkdaysOnly = val);"/>
      <property name="supportsWorkdaysOnly" readonly="true"
                onget="return true;"/>
      <property name="supportsZoom" readonly="true"
                onget="return false;"/>
      <property name="selectionObserver" readonly="true"
                onget="return this.mSelectionObserver;"/>
      <property name="startDay" readonly="true"
                onget="return this.startDate;"/>
      <property name="endDay" readonly="true"
                onget="return this.endDate;"/>
      <property name="supportDisjointDates" readonly="true"
                onget="return false;"/>
      <property name="hasDisjointDates" readonly="true"
                onget="return false;"/>
      <property name="rangeStartDate"
                onget="return this.mRangeStartDate;"
                onset="return (this.mRangeStartDate = val);"/>
      <property name="rangeEndDate"
                onget="return this.mRangeEndDate;"
                onset="return (this.mRangeEndDate = val);"/>
      <property name="observerID" readonly="true"
                onget="return 'base-view-observer';"/>

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

      <property name="displayCalendar">
        <getter><![CDATA[
          return this.mCalendar;
        ]]></getter>
        <setter><![CDATA[
          if (this.mCalendar)
              this.mCalendar.removeObserver(this.mObserver);
          this.mCalendar = val;
          this.mCalendar.addObserver(this.mObserver);
          this.refresh();
          return val;
        ]]></setter>
      </property>

      <property name="initialized">
        <getter><![CDATA[
            let retval;

            // Some views throw when accessing an uninitialized startDay
            try {
                retval = this.displayCalendar && this.startDay
                         && this.endDay;
            } catch(ex) {
                return false;
            }
            return retval;
        ]]></getter>
      </property>

      <method name="goToDay">
        <parameter name="aDate"/>
        <body><![CDATA[
            this.showDate(aDate);
        ]]></body>
      </method>

      <method name="getRangeDescription">
        <body><![CDATA[
            return getDateFormatter().formatInterval(this.rangeStartDate, this.rangeEndDate);
        ]]></body>
      </method>

      <method name="popRefreshQueue">
        <body><![CDATA[
          let pendingRefresh = this.mRefreshPending;
          if (pendingRefresh) {
              if (calInstanceOf(pendingRefresh, Components.interfaces.calIOperation)) {
                  this.mRefreshPending = null;
                  pendingRefresh.cancel(null);
              } else {
                  if(this.mRefreshQueue.length > 0) {
                      this.relayout();
                  }
                  return;
              }
          }

          let refreshJob = this.mRefreshQueue.pop();
          if (!refreshJob) {
            return;
          }

          if (!this.startDate || !this.endDate) {
            return;
          }

          if (!this.mCalendar) {
              return;
          }

          // start our items query; for a disjoint date range
          // we get all the items, and just filter out the ones we don't
          // care about in addItem

          let filter = this.mCalendar.ITEM_FILTER_CLASS_OCCURRENCES;
          if (this.mShowCompleted) {
              filter |= this.mCalendar.ITEM_FILTER_COMPLETED_ALL;
          } else {
              filter |= this.mCalendar.ITEM_FILTER_COMPLETED_NO;
          }

          if(this.mTasksInView) {
              filter |= this.mCalendar.ITEM_FILTER_TYPE_ALL;
          } else {
              filter |= this.mCalendar.ITEM_FILTER_TYPE_EVENT;
          }

          this.mRefreshPending = true;
          let refreshCalendar = refreshJob.calendar;
          if (!refreshCalendar) {
              refreshCalendar = this.mCalendar;
              this.relayout();
          }
          pendingRefresh = refreshCalendar.getItems(filter,
                                                    0,
                                                    this.startDate,
                                                    this.queryEndDate,
                                                    this.mOperationListener);
          if (pendingRefresh && pendingRefresh.isPending) { // support for calIOperation
              this.mRefreshPending = pendingRefresh;
          }
        ]]></body>
      </method>

      <!-- This function guarantees that the
       labels are clipped in the instance that the overflow occurrs,
       avoiding horizontal scrollbars from showing up for a short period. -->
      <method name="adjustWeekdayLength">
        <parameter name="forceShortName"/>
        <body><![CDATA[
          let useShortNames = false;
          let labeldayboxkids = this.labeldaybox.childNodes;
          if (!labeldayboxkids || !labeldayboxkids[0]) {
              useShortNames = true;
          } else if (forceShortName && forceShortName === true) {
              useShortNames = true;
          } else {
              let clientWidth = document.getAnonymousElementByAttribute(this, "anonid", "mainbox").clientWidth;
              let timespacer = document.getAnonymousElementByAttribute(this, "anonid", "headertimespacer");
              if (timespacer) {
                  clientWidth -= timespacer.clientWidth;
              }
              if (this.getLongWeekdayTotalPixels() > 0.95 * clientWidth) {
                  useShortNames = true;
              }
          }
          for (let i = 0; i < labeldayboxkids.length; i++) {
              labeldayboxkids[i].shortWeekNames = useShortNames;
          }
        ]]></body>
      </method>

      <method name="today">
        <body><![CDATA[
          let date = createDateTime();
          date.jsDate = new Date();
          date = date.getInTimezone(this.mTimezone);
          date.isDate = true;
          return date;
        ]]></body>
      </method>

      <method name="isVisible">
        <body><![CDATA[
            return (this.nodeName == currentView().nodeName);
        ]]></body>
      </method>

      <method name="refresh">
        <body><![CDATA[
          if (this.isVisible()) {
              var refreshJob = {};
              this.mRefreshQueue.push(refreshJob);
              this.popRefreshQueue();
          }
        ]]></body>
      </method>

      <method name="addItemsFromCalendar">
        <parameter name="aCalendar"/>
        <body><![CDATA[
            let refreshJob = { calendar: aCalendar };
            this.mRefreshQueue.push(refreshJob);
            this.popRefreshQueue();
        ]]></body>
      </method>

      <method name="deleteItemsFromCalendar">
        <parameter name="aCalendar"/>
        <body><![CDATA[
            /* This method must be implemented in subclasses. */
            throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
        ]]></body>
      </method>

      <!-- the end date that should be used for getItems and similar queries -->
      <property name="queryEndDate" readonly="true">
        <getter><![CDATA[
          let end = this.endDate;
          if (!end) {
              return null;
          }
          end = end.clone();
          end.day += 1;
          end.isDate = true;
          return end;
        ]]></getter>
      </property>

      <method name="fireEvent">
        <parameter name="aEventName"/>
        <parameter name="aEventDetail"/>
        <body><![CDATA[
          let event = document.createEvent('Events');
          event.initEvent(aEventName, true, false);
          event.detail = aEventDetail;
          this.dispatchEvent(event);
        ]]></body>
      </method>

      <method name="removeDropShadows">
        <body><![CDATA[
            let dropbox = document.getAnonymousElementByAttribute(this, "dropbox", "true");
            if (dropbox && dropbox !== undefined) {
                dropbox.setAttribute("dropbox", "false");
            }
        ]]></body>
      </method>

      <method name="getLongWeekdayTotalPixels">
        <body><![CDATA[
          if (this.mLongWeekdayTotalPixels <= 0) {
              let maxDayWidth = 0;
              for each (let label in this.labeldaybox.childNodes) {
                  if (label.localName && label.localName == "calendar-day-label") {
                      label.shortWeekNames = false;
                      let curPixelLength = label.getLongWeekdayPixels();
                      maxDayWidth = Math.max(maxDayWidth, curPixelLength);
                  }
              }
              this.mLongWeekdayTotalPixels = (maxDayWidth * this.labeldaybox.childNodes.length) + 10;
          }
          return this.mLongWeekdayTotalPixels;
        ]]></body>
      </method>

      <!-- A preference handler which is called by the preference observer.
           Can be overwritten in derived bindings. -->
      <method name="handleCommonPreference">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aPreference"/>
        <body><![CDATA[
           // refresh view if categories seem to have changed
           if (aPreference.indexOf("calendar.category.color") == 0) {
               this.refreshView();
               return;
           }
           switch (aPreference) {
               case "calendar.week.d0sundaysoff":
               case "calendar.week.d1mondaysoff":
               case "calendar.week.d2tuesdaysoff":
               case "calendar.week.d3wednesdaysoff":
               case "calendar.week.d4thursdaysoff":
               case "calendar.week.d5fridaysoff":
               case "calendar.week.d6saturdaysoff":
                   this.updateDaysOffPrefs();
                   break;
               case "calendar.timezone.local":
                   this.timezone = calendarDefaultTimezone();
                   this.refreshView();
                   break;
               case "calendar.alarms.indicator.show":
                   //Break here to ensure the view is refreshed
                   break;
               case "calendar.week.start":
                   this.weekStartOffset = aSubject.getIntPref(aPreference);
                   break;
               case "calendar.date.format":
                   this.refreshView();
                   break;
               default:
                   return;
            }
            this.refreshView();
        ]]></body>
      </method>

      <method name="updateDaysOffPrefs">
        <body><![CDATA[
            const weekPrefix = "calendar.week.";
            const prefNames = ["d0sundaysoff", "d1mondaysoff", "d2tuesdaysoff",
                               "d3wednesdaysoff", "d4thursdaysoff",
                               "d5fridaysoff", "d6saturdaysoff"];
            const defaults = ["true", "false", "false", "false",
                              "false", "false", "true"];
            let daysOff = new Array();
            for (let i in prefNames) {
                if (getPrefSafe(weekPrefix+prefNames[i])) {
                    daysOff.push(Number(i));
                }
            }
            this.daysOffArray = daysOff;
        ]]></body>
      </method>

      <method name="refreshView">
        <body><![CDATA[
          if (!this.startDay || !this.endDay) {
             // don't refresh if we're not initialized
             return;
          }
          // Just refresh, the goToDay function will notice
          this.goToDay(this.selectedDay);
        ]]></body>
      </method>

      <!-- Default implementations follow, these make things easier for
            extensions that don't need certain features. -->
      <method name="handlePreference">
        <parameter name="aSubject"/>
        <parameter name="aTopic"/>
        <parameter name="aPref"/>
        <body><![CDATA[
          // Do nothing by default
        ]]></body>
      </method>
      <method name="setDateRange">
        <parameter name="aStartDate"/>
        <parameter name="aEndDate"/>
        <body><![CDATA[
          cal.navigationBar.setDateRange(aStartDate, aEndDate);
        ]]></body>
      </method>

      <property name="selectedDay"
                onget="return this.startDate"
                onset="return this.startDate"/>
       
      <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"/>
        <body><![CDATA[
          this.mSelectedItems = aItems.concat([]);
          return this.mSelectedItems;
        ]]></body>
      </method>

      <method name="setDateList">
        <parameter name="aCount"/>
        <parameter name="aDates"/>
        <body><![CDATA[
          throw Components.results.NS_ERROR_FAILURE;
        ]]></body>
      </method>
      <method name="getDateList">
        <parameter name="aCount"/>
        <parameter name="aDates"/>
        <body><![CDATA[
          let start = this.startDate.clone();
          while (start.compare(this.endDate) <= 0) {
              dates.push(start);
              start.day++;
          }
          aCount.value = dates.length;
          return dates;
        ]]></body>
      </method>

      <method name="flashAlarm">
        <parameter name="aAlarmItem"/>
        <parameter name="aStop"/>
        <body><![CDATA[
          // Do nothing by default
        ]]></body>
      </method>

      <method name="zoomIn">
        <parameter name="aLevel"/>
        <body><![CDATA[
        ]]></body>
      </method>
      <method name="zoomOut">
        <parameter name="aLevel"/>
        <body><![CDATA[
        ]]></body>
      </method>
      <method name="zoomReset">
        <body><![CDATA[
        ]]></body>
      </method>
    </implementation>

    <handlers>
      <handler event="move">
        <![CDATA[
          this.moveView(event.detail);
        ]]>
      </handler>
      <handler event="keypress"><![CDATA[
        const kKE = Components.interfaces.nsIDOMKeyEvent;
        switch (event.keyCode) {
            case kKE.DOM_VK_PAGE_UP:
                this.moveView(-1);
                break;
            case kKE.DOM_VK_PAGE_DOWN:
                this.moveView(1);
                break;
        }
      ]]></handler>
      <handler event="wheel"><![CDATA[
        const pixelThreshold = 150;
        if (event.shiftKey && cal.getPrefSafe('calendar.view.mousescroll', true)){
            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);
            }
        }

        // Prevent default scroll handling
        event.preventDefault();
      ]]></handler>
      <handler event="MozRotateGesture"><![CDATA[
        // Threshold for the minimum and maximum angle we should accept
        // rotation for. 90 degrees minimum is most logical, but 45 degrees
        // allows you to rotate with one hand.
        const MIN_ROTATE_ANGLE = 45;
        const MAX_ROTATE_ANGLE = 180;

        let absval = Math.abs(event.delta);
        if (this.supportsRotation &&
            absval >= MIN_ROTATE_ANGLE &&
            absval < MAX_ROTATE_ANGLE) {
            toggleOrientation();
            event.preventDefault();
        }
      ]]></handler>
      <handler event="MozMagnifyGestureStart"><![CDATA[
        this.mMagnifyAmount = 0;
      ]]></handler>
      <handler event="MozMagnifyGestureUpdate"><![CDATA[
        // Threshold as to how much magnification causes the zoom to happen
        const THRESHOLD = 30;

        if (this.supportsZoom) {
            this.mMagnifyAmount += event.delta;

            if (this.mMagnifyAmount > THRESHOLD) {
                this.zoomOut();
                this.mMagnifyAmount = 0;
            } else if (this.mMagnifyAmount < -THRESHOLD) {
                this.zoomIn();
                this.mMagnifyAmount = 0;
            }
            event.preventDefault();
        }
      ]]></handler>
      <handler event="MozSwipeGesture"><![CDATA[
        if ((event.direction == SimpleGestureEvent.DIRECTION_UP && !this.rotated) ||
            (event.direction == SimpleGestureEvent.DIRECTION_LEFT && this.rotated)) {
            this.moveView(-1);
        } else if ((event.direction == SimpleGestureEvent.DIRECTION_DOWN && !this.rotated) ||
                   (event.direction == SimpleGestureEvent.DIRECTION_RIGHT && this.rotated)) {
            this.moveView(1);
        }
      ]]></handler>
    </handlers>
  </binding>

  <binding id="calendar-day-label" extends="xul:box">
    <content flex="1" pack="center">
      <xul:label anonid="longWeekdayName" class="calendar-day-label-name" xbl:inherits="selected,relation"/>
      <xul:label anonid="shortWeekdayName" class="calendar-day-label-name" hidden="true" xbl:inherits="selected,relation"/>
    </content>
    <implementation>
      <field name="mWeekday">-1</field>
      <field name="longWeekdayPixels">0</field>
      <field name="mDate">null</field>
      <property name="longName" readonly="true"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'longWeekdayName');"/>
      <property name="shortName" readonly="true"
                onget="return document.getAnonymousElementByAttribute(this, 'anonid', 'shortWeekdayName');"/>
      <property name="weekDay">
        <getter>return this.mWeekday;</getter>
        <setter><![CDATA[
          this.mWeekday = val % 7;
          this.longName.value = getDateFormatter().dayName(val);
          this.shortName.value = getDateFormatter().shortDayName(val);
          return this.mWeekday;
        ]]></setter>
      </property>

      <property name="date">
        <getter><![CDATA[
          return this.mDate;
        ]]></getter>
        <setter><![CDATA[
          this.mDate = val;
          this.shortName.setAttribute("value", getDateFormatter().shortDayName(val.weekday) + " " +
                                               getDateFormatter().formatDateWithoutYear(val));
          this.longName.setAttribute("value", getDateFormatter().dayName(val.weekday) + " " +
                                              getDateFormatter().formatDateWithoutYear(val));
          return val;
        ]]></setter>
      </property>

      <property name="shortWeekNames">
        <getter><![CDATA[
        ]]></getter>
        <setter><![CDATA[
          // cache before change, in case we are switching to short
          this.getLongWeekdayPixels();
          setBooleanAttribute(this.longName, "hidden", val);
          setBooleanAttribute(this.shortName, "hidden", !val);
          return val;
        ]]></setter>
      </property>

      <method name="getLongWeekdayPixels">
        <body><![CDATA[
          // Only do this if the long weekdays are visible and we haven't already cached.
          let longNameWidth = this.longName.boxObject.width;
          if (longNameWidth > 0) {
              this.longWeekdayPixels = longNameWidth +
                                       getSummarizedStyleValues(this.longName, ["margin-left", "margin-right"]);
              this.longWeekdayPixels += getSummarizedStyleValues(this, ["border-left-width",
                                                                        "padding-left", "padding-right"]);
              return this.longWeekdayPixels;
          } else {
              // in this case the weekdaypixels have not yet been layouted;
              // by definition 0 is returned
              return 0;
          }
        ]]></body>
      </method>
    </implementation>
  </binding>
</bindings>
