<?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/. -->

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

<bindings id="calendar-widgets"
          xmlns="http://www.mozilla.org/xbl"
          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          xmlns:xbl="http://www.mozilla.org/xbl">
  <binding id="doubleimage-toolbarbutton" extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/calendar-widgets.css"/>
    </resources>
    <content>
      <children includes="observes|template|menupopup|tooltip"/>
      <xul:image class="toolbarbutton-icon-begin" xbl:inherits="validate,src-begin=image,toolbarmode,buttonstyle"/>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,toolbarmode,buttonstyle"/>
      <xul:image class="toolbarbutton-icon-end" xbl:inherits="validate,src-end=image,toolbarmode,buttonstyle"/>
    </content>
  </binding>

  <binding id="todaypane-toolbarbutton" extends="chrome://calendar/content/widgets/calendar-widgets.xml#doubleimage-toolbarbutton">
    <content>
      <children includes="observes|template|menupopup|tooltip"/>
      <xul:stack pack="center" align="end">
        <xul:image class="toolbarbutton-icon-begin" xbl:inherits="validate,src-begin=image,toolbarmode,buttonstyle"/>
        <xul:label anonid="day-label" class="toolbarbutton-day-text"/>
      </xul:stack>
      <xul:label class="toolbarbutton-text" crop="right" flex="1"
                 xbl:inherits="value=label,accesskey,crop,toolbarmode,buttonstyle"/>
      <xul:image class="toolbarbutton-icon-end" xbl:inherits="validate,src-end=image,toolbarmode,buttonstyle"/>
    </content>
    <implementation>
      <constructor><![CDATA[
          this.setUpTodayDate();
      ]]></constructor>
      <method name="setUpTodayDate">
        <body><![CDATA[
            let dayNumber = calGetString("dateFormat", "day." + cal.now().day + ".number");
            document.getAnonymousElementByAttribute(this, "anonid", "day-label").value = dayNumber;
        ]]></body>
      </method>
    </implementation>
   </binding>

  <!-- this binding directly extends to a checkbox but is visualized as
       a treenode in a treecontrol-->
  <binding id="treenode-checkbox" extends="chrome://global/content/bindings/checkbox.xml#checkbox-baseline">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/calendar-widgets.css"/>
    </resources>
  </binding>

  <!-- this binding directly extends to a xul:box element and automatically
       sets the "orient" attribute to "vertical" thus behaving like a vbox-->
  <binding id="modevbox" extends="chrome://calendar/content/widgets/calendar-widgets.xml#modebox">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/calendar-widgets.css"/>
    </resources>
    <implementation>
      <constructor><![CDATA[
            this.setAttribute("orient", "vertical");
      ]]></constructor>
      </implementation>
  </binding>

  <!-- this binding directly extends to a xul:box element and automatically
       sets the "orient" attribute to "horizontal" thus behaving like a vbox-->
  <binding id="modehbox" extends="chrome://calendar/content/widgets/calendar-widgets.xml#modebox">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/calendar-widgets.css"/>
    </resources>
    <implementation>
      <constructor><![CDATA[
         this.setAttribute("orient", "horizontal");
      ]]></constructor>
    </implementation>
  </binding>

  <!-- this binding directly extends to a xul:box element and enriches this with some functionality: It is designed
      to be displayed only 1) in given application modes (e.g "task" mode, "calendar" mode) and 2) only in relation
      to the "checked" attribute of command or a checkbox control.
    - The attribute "mode" denotes a coma-separated list of all modes that the binding should not be collapsed in,
      e.g. mode="calendar,task"
    - The attribute "broadcaster" points to the id of a broadcaster that is supposed to be notified (by the application)
      as soon as the mode changes. When this happens the modebox" will be notified and will check if it should
      collapse itself or not.
    - The attribute "refcontrol" points to a control either a "command", "checkbox" or a "treenode-checkbox" or other
      elements that support a "checked" attribute that is often used to denote whether a modebox is supposed to be
      displayed or not. If "refcontrol" is set to the id of a command you can there set the oncommend attribute like:
      "oncommand='document.getElementById('my-mode-pane').togglePane(event)'. In case it is a checkbox element or derived
      checkbox element this is done automatically by listening to the event "CheckboxChange";
      So if the current application mode is one of the modes listed in the "mode" attribute it is
      additionally verified if the xul-element denoted by "refcontrol" is checked or not. During runtime an attribute named
      "collapsedinmodes" with the collpsed modes comma-separated e.g. "mail,calendar,task. This attribute is also made
      persistent-->
  <binding id="modebox" extends="xul:box">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/calendar-widgets.css"/>
    </resources>
    <implementation>
      <field name="mBroadcaster">null</field>;
      <field name="mModHandler">null</field>;
      <field name="mRefControl">null</field>;
      <field name="mControlHandler">null</field>;
      <constructor><![CDATA[
        if (this.hasAttribute("broadcaster")) {
            this.setAttribute("broadcaster", this.getAttribute("broadcaster"));
        }
        if (this.hasAttribute("refcontrol")) {
            this.mRefControl = document.getElementById(this.getAttribute("refcontrol"));
            if (this.mRefControl && ((this.mRefControl.localName == "treenode-checkbox") ||
                                  (this.mRefControl.localName == "checkbox"))){
                this.mControlHandler = {
                    binding: this,
                    handleEvent : function(aEvent, aHandled) {
                        return this.binding.onCheckboxStateChange(aEvent, this.binding);
                    }
                };
                this.mRefControl.addEventListener("CheckboxStateChange", this.mControlHandler, true);
            }
        }
      ]]>
      </constructor>
      <destructor><![CDATA[
          if (this.mBroadcaster) {
              this.mBroadcaster.removeEventListener("DOMAttrModified", this.mModHandler, true);
          }
          if (this.mRefControl) {
              this.mRefControl.removeEventListener("CheckboxStateChange", this.mControlHandler, true);
          }
      ]]></destructor>

      <property name="currentMode">
        <getter><![CDATA[
            if (this.mBroadcaster && this.mBroadcaster.hasAttribute("mode")) {
                return this.mBroadcaster.getAttribute("mode");
            } else {
                return "";
            }
        ]]></getter>
      </property>

      <method name="isVisible">
        <parameter name="aMode"/>
        <body><![CDATA[
            var lMode = aMode || this.currentMode;
            if (!this.isVisibleInMode(lMode)) {
                return false;
            }
            var collapsedModes = this.getAttribute("collapsedinmodes").split(",");
            var modeIndex = collapsedModes.indexOf(lMode);
            return (modeIndex == -1);
        ]]></body>
      </method>

      <method name="setModeAttribute"> 
        <parameter name="aModeAttribute"/>
        <parameter name="aModeValue"/>
        <parameter name="amode"/>
        <body><![CDATA[
            if (this.hasAttribute(aModeAttribute)) {
                let lMode = amode || this.currentMode;
                let modeAttributeValues = this.getAttribute(aModeAttribute).split(",");
                let modes = this.getAttribute("mode").split(",");
                modeAttributeValues[modes.indexOf(lMode)] = aModeValue;
                this.setAttribute(aModeAttribute, modeAttributeValues.join(","));
            }
        ]]></body>
      </method>

      <method name="getModeAttribute">
        <parameter name="aModeAttribute"/>
        <parameter name="aAttribute"/>
        <parameter name="amode"/>
        <body><![CDATA[
            if (this.hasAttribute(aModeAttribute)) {
                let lMode = amode || this.currentMode;
                let modeAttributeValues = this.getAttribute(aModeAttribute).split(",");
                let modes = this.getAttribute("mode").split(",");
                return modeAttributeValues[modes.indexOf(lMode)];
            } else {
                return "";
            }
        ]]></body>
      </method>

      <method name="setVisible">
        <parameter name="aVisible"/>
        <parameter name="aPushModeCollapsedAttribute"/>
        <parameter name="aNotifyRefControl"/>
        <body><![CDATA[
            var notifyRefControl = ((aNotifyRefControl == null) || (aNotifyRefControl === true))
            var pushModeCollapsedAttribute = ((aPushModeCollapsedAttribute == null)
                                                 || (aPushModeCollapsedAttribute === true));
            var collapsedModes = [];
            var modeIndex = -1;
            var display = aVisible;
            var collapsedInMode = false;
            if (this.hasAttribute("collapsedinmodes")) {
                var collapsedModes = this.getAttribute("collapsedinmodes").split(",");
                var modeIndex = collapsedModes.indexOf(this.currentMode);
                collapsedInMode = (modeIndex > -1);
            }
            if ((aVisible === true) && (pushModeCollapsedAttribute == false)){
                display = (aVisible === true) && (!collapsedInMode);
            }

            setBooleanAttribute(this, "collapsed", (!display || !this.isVisibleInMode()));
            if (pushModeCollapsedAttribute) {
                if (!display) {
                    if (modeIndex == -1) {
                        collapsedModes.push(this.currentMode);
                        if (this.getAttribute("collapsedinmodes") == ",") {
                            collapsedModes.splice(0,2);
                        }
                    }
                } else {
                    if (modeIndex > -1) {
                        collapsedModes.splice(modeIndex, 1);
                        if (collapsedModes.join(",")  == "") {
                            collapsedModes[0]=",";
                        }
                    }
                }
                this.setAttribute("collapsedinmodes", collapsedModes.join(","));
                var id = this.getAttribute("id");
                if (id) {
                    document.persist(id, "collapsedinmodes");
                }
            }
            if (notifyRefControl === true) {
                if (this.hasAttribute("refcontrol")) {
                    var command =  document.getElementById(this.getAttribute("refcontrol"))
                    if (command) {
                        command.setAttribute("checked", display);
                        setBooleanAttribute(command, "disabled", !this.isVisibleInMode());
                    }
                }
            }
        ]]></body>
      </method>

      <method name="isVisibleInMode">
        <parameter name="aMode"/>
        <body><![CDATA[
            var lMode = aMode || this.currentMode;
            var display = true;
            var lModes = new Array();
            if (this.hasAttribute("mode")) {
                var modeString = this.getAttribute("mode");
                lModes = modeString.split(",");
            }
            if (lModes && lModes.length > 0) {
                display = lModes.indexOf(lMode) > -1;
            }
            return display;
        ]]></body>
      </method>

      <method name="onModeModified">
        <parameter name="aEvent"/>
        <parameter name="aBinding"/>
        <body><![CDATA[
            if (aEvent.attrName == "mode") {
                var display = aBinding.isVisibleInMode(aEvent.newValue);
                aBinding.setVisible(display, false, true);
            }
        ]]></body>
      </method>

      <method name="togglePane">
        <parameter name="aEvent"/>
        <body><![CDATA[
            var command = aEvent.target;
            var newValue = (command.getAttribute("checked") == "true" ? "false" : "true");
            command.setAttribute("checked", newValue);
            this.setVisible(newValue == "true", true, true);
        ]]></body>
      </method>

      <method name="onCheckboxStateChange">
        <parameter name="aEvent"/>
        <parameter name="aBinding"/>
        <body><![CDATA[
            var newValue = aEvent.target.checked;
            this.setVisible(newValue, true, true);
        ]]></body>
      </method>
      <method name="setAttribute">
        <parameter name="aAttr"/>
        <parameter name="aVal"/>
        <body><![CDATA[
          if (aAttr == "broadcaster") {
              this.mBroadcaster = document.getElementById(aVal);
              if (this.mBroadcaster) {
                  this.mModHandler = {
                  binding: this,
                  handleEvent : function(aEvent, aHandled) {
                      return this.binding.onModeModified(aEvent, this.binding);
                  }
              };
              this.mBroadcaster.addEventListener("DOMAttrModified", this.mModHandler, true);
            }
          }
          var ret = XULElement.prototype.setAttribute.call(this, aAttr, aVal);
        ]]></body>
      </method>
    </implementation>
  </binding>

<!-- This binding may server as a droptarget container for arbitrary items
     it contains methods to add DropShadows. This binding is meant to be used
     as a parent binding. The methods may be overwritten.
-->
<binding id="dragndropContainer">
    <implementation>
      <field name="mDropShadows">[]</field>
      <field name="mCalendarView">null</field>

      <!-- The ViewController that supports the interface 'calICalendarView'-->
      <property name="calendarView"
                onget="return this.mCalendarView;"
                onset="return (this.mCalendarView = val);"/>

       <!-- method to add individual code e.g to set up the new item during
       'ondrop' -->
      <method name="onDropItem">
        <parameter name="aItem"/>
        <body><![CDATA[
            // method that may be overridden by derived bindings...
        ]]></body>
      </method>

      <method name="getDropShadows">
        <body><![CDATA[
            return this.mDropShadows;
        ]]></body>
      </method>

      <!-- Adds the dropshadows to the children of the binding. The dropshadows
           are added at the first position of the children -->
      <method name="addDropShadows">
        <body><![CDATA[
          if (this.mDropShadows) {
              if (this.getElementsByAttribute("class", "dropshadow").length == 0) {
                  let offset = this.calendarView.mShadowOffset;
                  let shadowStartDate = this.date.clone();
                  shadowStartDate.addDuration(offset);
                  this.calendarView.mDropShadows = [];
                  for (let i = 0; i < this.calendarView.mDropShadowsLength; i++) {
                      let box = this.calendarView.findDayBoxForDate(shadowStartDate);
                      if (!box) {
                          // Dragging to the end or beginning of a view
                          shadowStartDate.day += 1;
                          continue;
                      }
                      let dropshadow = createXULElement("box");
                      dropshadow.setAttribute("class", "dropshadow");
                      if (box.hasChildNodes()) {
                          box.insertBefore(dropshadow, box.firstChild);
                      } else {
                          box.appendChild(dropshadow);
                      }
                      shadowStartDate.day += 1;
                      this.calendarView.mDropShadows.push(box);
                  }
              }
          }
        ]]></body>
      </method>

      <!-- removes all dropShadows from the binding. Dropshadows are recognized
           as such by carrying an attribute "dropshadow" -->
      <method name="removeDropShadows">
        <body><![CDATA[
            // method that may be overwritten by derived bindings...
            for each (let shadow in this.calendarView.mDropShadows) {
                cal.removeChildElementsByAttribute(shadow, "class", "dropshadow");
            }
            this.calendarView.mDropShadows = null;
        ]]></body>
      </method>

      <!-- By setting the attribute "dropbox" to "true" or "false" the
           dropshadows are added or removed -->
      <method name="setAttribute">
        <parameter name="aAttr"/>
        <parameter name="aVal"/>
        <body><![CDATA[
          if (aAttr == "dropbox") {
              let session = cal.getDragService().getCurrentSession();
              let startingDayBox = session.sourceNode.mParentBox;
              if (session) {
                  session.canDrop = true;
                  // no shadows when dragging in the initial position
                  if (aVal == "true" && this != startingDayBox) {
                      this.mDropShadows = [session.sourceNode.sourceObject];
                      this.addDropShadows();
                  } else {
                      this.removeDropShadows();
                  }
              }
          }
          return XULElement.prototype.setAttribute.call(this, aAttr, aVal);
        ]]></body>
      </method>

    </implementation>

    <handlers>
      <handler event="dragstart"><![CDATA[
         let draggedDOMNode = event.target;
         if (!draggedDOMNode || draggedDOMNode.parentNode != this) {
             return;
         }
         let item = draggedDOMNode.occurrence.clone();
         let beginMoveDate = draggedDOMNode.mParentBox.date;
         let itemStartDate = (item.entryDate || item.startDate).getInTimezone(calendarView.mTimezone);
         let itemEndDate = (item.dueDate || item.endDate).getInTimezone(calendarView.mTimezone);
         let oneMoreDay = (itemEndDate.hour > 0 || itemEndDate.minute > 0);
         itemStartDate.isDate = true;
         itemEndDate.isDate = true;
         let offsetDuration = itemStartDate.subtractDate(beginMoveDate);
         let lenDuration = itemEndDate.subtractDate(itemStartDate);
         let len = lenDuration.weeks * 7 + lenDuration.days;
         this.calendarView.mShadowOffset = offsetDuration;
         this.calendarView.mDropShadowsLength = oneMoreDay ? len + 1 : len;
      ]]></handler>

      <handler event="dragover"><![CDATA[
         let session = cal.getDragService().getCurrentSession();
         if (!session || !session.sourceNode || !session.sourceNode.sourceObject) {
            // No source item? Then this is not for us.
            return;
         }

         // We handled the event
         event.preventDefault();
      ]]></handler>

      <handler event="dragenter"><![CDATA[
       if (event.target.localName == this.localName) {
           let session = cal.getDragService().getCurrentSession();
           if (session) {
               if (!session.sourceNode || !session.sourceNode.sourceObject) {
                  // No source item? Then this is not for us.
                  return;
               }

               // We can drop now, tell the drag service.
               event.preventDefault();

               if (!this.hasAttribute("dropbox") || this.getAttribute("dropbox") == "false") {
                   // As it turned out it was not possible to remove the remaining dropshadows
                   // at the "dragleave" or "dragexit" event, majorly because it was not reliably
                   // fired. As the dragndropcontainer may be anonymous it is further on not
                   // possible to remove the dropshadows by something like
                   // "document.getElementsByAttribute('dropbox').removeDropShadows();";
                   // So we have to remove them at the currentView(). The restriction of course is
                   // that these containers so far may not be used for drag and drop from/to e.g.
                   // the today-pane.
                   currentView().removeDropShadows();
               }
               this.setAttribute("dropbox", "true");
           }
       }
      ]]></handler>

      <handler event="dragdrop"><![CDATA[
         let session = cal.getDragService().getCurrentSession();
         if (!session || !session.sourceNode || !session.sourceNode.sourceObject) {
            // No source node? Not our drag.
            return;
         }
         let item = session.sourceNode.sourceObject.clone();
         this.setAttribute("dropbox", "false");
         let transfer = Components.classes["@mozilla.org/widget/transferable;1"].
                        createInstance(Components.interfaces.nsITransferable);
         transfer.init(null);

         if (isEvent(item)) {
             transfer.addDataFlavor("application/x-moz-cal-event");
         } else {
             transfer.addDataFlavor("application/x-moz-cal-task");
         }

         session.getData(transfer, 0);
         let flavor = {};
         let data = {};
         // nsITransferable sucks when it comes to trying to add extra flavors.
         // This will throw NS_ERROR_FAILURE, so as a workaround, we use the
         // sourceNode property and get the event that way
         //transfer.getAnyTransferData(flavor, data, {});

         item = session.sourceNode.sourceObject;
         let newItem = this.onDropItem(item).clone();
         let newStart = newItem.startDate || newItem.entryDate;
         let newEnd = newItem.endDate || newItem.dueDate;
         let offset = this.calendarView.mShadowOffset;
         newStart.addDuration(offset);
         newEnd.addDuration(offset);
         this.calendarView.controller.modifyOccurrence(item, newStart, newEnd);

         // We handled the event
         event.stopPropagation();
      ]]></handler>

      <handler event="dragend"><![CDATA[
          currentView().removeDropShadows();
      ]]></handler>

    </handlers>
  </binding>

  <binding id="view-tab" extends="chrome://global/content/bindings/tabbox.xml#tab">
    <resources>
      <stylesheet src="chrome://calendar/skin/widgets/calendar-widgets.css"/>
    </resources>
    <content>
      <xul:hbox class="tab-middle box-inherit" xbl:inherits="align,dir,pack,orient,selected" flex="1">
        <xul:image class="tab-icon" xbl:inherits="validate,src=image"/>
        <xul:stack>
          <xul:label class="tab-text unselected-text"
                     xbl:inherits="value=label,accesskey,crop,disabled,selected"
                     flex="1"/>
          <xul:label class="tab-text selected-text"
                     xbl:inherits="value=label,accesskey,crop,disabled,selected"
                     flex="1"/>
        </xul:stack>
      </xul:hbox>
    </content>
  </binding>
</bindings>
