calendar/base/content/widgets/calendar-widgets.xml
author Arshad Khan <arshdkhn1@gmail.com>
Mon, 20 Aug 2018 18:30:13 +0530
changeset 33137 2dc8e39bc37f5aebdd85a237f5d6287db6c606ab
parent 33126 b2f73a356bde87b73610025998cc86b3ec663633
child 33140 84f675096b814ddb550c4cee0f25336366c16c2b
permissions -rw-r--r--
Bug 1484680 - Remove treenode-checkbox binding. r=philipp

<?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 dialog [
  <!ENTITY % dtd1 SYSTEM "chrome://global/locale/global.dtd" > %dtd1;
  <!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar-event-dialog.dtd"> %dtd2;
]>

<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">
    <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 = cal.l10n.getDateFmtString(`day.${cal.dtz.now().day}.number`);
            document.getAnonymousElementByAttribute(this, "anonid", "day-label").value = dayNumber;
        ]]></body>
      </method>
    </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 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">
    <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 == "checkbox")) {
                  this.mControlHandler = {
                      binding: this,
                      handleEvent: function(aEvent, aHandled) {
                          return this.binding.onCheckboxStateChange(aEvent, this.binding);
                      }
                  };
                  this.mRefControl.addEventListener("CheckboxStateChange", this.mControlHandler, true);
              }
          }

          this.dispatchEvent(new CustomEvent("bindingattached", { bubbles: false }));
      ]]></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[
            let lMode = aMode || this.currentMode;
            if (!this.isVisibleInMode(lMode)) {
                return false;
            }
            let collapsedModes = this.getAttribute("collapsedinmodes").split(",");
            return !collapsedModes.includes(lMode);
        ]]></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[
            let notifyRefControl = aNotifyRefControl == null || aNotifyRefControl === true;
            let pushModeCollapsedAttribute = aPushModeCollapsedAttribute == null ||
                                             aPushModeCollapsedAttribute === true;
            let collapsedModes = [];
            let modeIndex = -1;
            let display = aVisible;
            let collapsedInMode = false;
            if (this.hasAttribute("collapsedinmodes")) {
                collapsedModes = this.getAttribute("collapsedinmodes").split(",");
                modeIndex = collapsedModes.indexOf(this.currentMode);
                collapsedInMode = modeIndex > -1;
            }
            if (aVisible === true && !pushModeCollapsedAttribute) {
                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(","));

                // This binding is used all over the place. We can't guarantee that Services is available.
                ChromeUtils.import("resource://gre/modules/Services.jsm");
                Services.xulStore.persist(this, "collapsedinmodes");
            }
            if (notifyRefControl === true) {
                if (this.hasAttribute("refcontrol")) {
                    let 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[
            let lMode = aMode || this.currentMode;
            let display = true;
            let lModes = [];
            if (this.hasAttribute("mode")) {
                let modeString = this.getAttribute("mode");
                lModes = modeString.split(",");
            }
            if (lModes && lModes.length > 0) {
                display = lModes.includes(lMode);
            }
            return display;
        ]]></body>
      </method>

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

      <method name="togglePane">
        <parameter name="aEvent"/>
        <body><![CDATA[
            let command = aEvent.target;
            let 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[
            let 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);
                }
            }
            return 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...
            if (this.calendarView.mDropShadows) {
                for (let shadow of this.calendarView.mDropShadows) {
                    cal.view.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" phase="capturing"><![CDATA[
          let draggedDOMNode = event.target;
          if (!draggedDOMNode || draggedDOMNode.parentNode != this) {
              return;
          }
          let item = draggedDOMNode.occurrence.clone();
          let beginMoveDate = draggedDOMNode.mParentBox.date;
          let itemStartDate = (item.startDate || item.entryDate || item.dueDate).getInTimezone(calendarView.mTimezone);
          let itemEndDate = (item.endDate || item.dueDate || item.entryDate).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="drop"><![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 (cal.item.isEvent(item)) {
              transfer.addDataFlavor("application/x-moz-cal-event");
          } else {
              transfer.addDataFlavor("application/x-moz-cal-task");
          }

          session.getData(transfer, 0);
          item = session.sourceNode.sourceObject;

          let newItem = this.onDropItem(item).clone();
          let newStart = newItem.startDate || newItem.entryDate || newItem.dueDate;
          let newEnd = newItem.endDate || newItem.dueDate || newItem.entryDate;
          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">
    <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>