Bug 1579030 - Change how horizontal scrolling works in attendees dialog. r+a=pmorris
authorGeoff Lankow <geoff@darktrojan.net>
Wed, 11 Sep 2019 17:07:18 +1200
changeset 79088 b01e94099186d0733b63128950811c00ad3fdfcc
parent 79087 ed03e668db0f427b311066b418d52269b76ce41a
child 79089 3a71c5c5ccc1f7f188d5fe4de94900d516769aaa
push id9345
push userkaie@kuix.de
push dateFri, 20 Sep 2019 05:14:40 +0000
treeherdertry-comm-central@b9101c33b5e5 [default view] [failures only]
bugs1579030
Bug 1579030 - Change how horizontal scrolling works in attendees dialog. r+a=pmorris
calendar/base/content/dialogs/calendar-event-dialog-attendees-custom-elements.js
calendar/base/content/dialogs/calendar-event-dialog-attendees.js
calendar/base/themes/common/dialogs/calendar-event-dialog.css
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees-custom-elements.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees-custom-elements.js
@@ -193,58 +193,16 @@
 
       // What we want to know is the scale of the total shift needed to step one block further.
       // Since the content is divided into 'numHours' equal parts, we can simply state:
       let numHours = this.mEndHour - this.mStartHour;
       return this.contentWidth / (numHours * shift);
     }
 
     /**
-     * Scrolls to a new date.
-     *
-     * @param {Number} val       New scroll offset
-     * @returns {Number}         New scroll offset
-     */
-    set scroll(val) {
-      this.mScrollOffset = val;
-
-      // How much pixels spans a single day
-      let oneday = this.contentWidth;
-
-      // The difference in pixels between the content and the container.
-      let shift = oneday * this.mRange - this.containerWidth;
-
-      // Now calculate the (positive) offset in pixels which the content needs to be shifted.
-      // This is a simple scaling in one dimension.
-      let offset = Math.floor(val * shift);
-
-      // Now find out how much days this offset effectively skips. This is a simple division which
-      // always yields a positive integer value.
-      this.dayOffset = (offset - (offset % oneday)) / oneday;
-
-      // Set the pixel offset for the content which will always need to be in the range
-      // [0 <= offset <= oneday].
-      offset %= oneday;
-
-      // Set the offset at the content node.
-      let container = this.getElementsByTagName("calendar-event-scroll-container")[0];
-      container.x = offset;
-      return val;
-    }
-
-    /**
-     * Gets scroll offset of freebusy-timebar.
-     *
-     * @returns {Number}       Scroll offset
-     */
-    get scroll() {
-      return this.mScrollOffset;
-    }
-
-    /**
      * Refreshes calendar-event-scroll-container's children. calendar-event-scroll-container
      * contains date and time labels with regular interval gap.
      */
     refresh() {
       let date = this.mStartDate.clone();
       let template = this.getElementsByTagName("calendar-event-freebusy-day")[0];
       let parent = template.parentNode;
       for (let child of parent.childNodes) {
@@ -1600,30 +1558,16 @@
      *
      * @returns {Number}        Document size
      */
     get documentSize() {
       return this.contentWidth * this.range;
     }
 
     /**
-     * Scrolls the element to a particular x value.
-     *
-     * @param {Number} val      New x value
-     * @returns {Number}        New x value
-     */
-    set scroll(val) {
-      let timebarContainer = document.querySelector("#timebar calendar-event-scroll-container");
-      let offset = timebarContainer.x;
-      // Set the offset at the content node.
-      this.containerNode.x = offset;
-      return val;
-    }
-
-    /**
      * Setup some properties of the element.
      */
     onLoad() {
       let numHours = this.endHour - this.startHour;
       this.state = new Array(this.range * numHours);
       for (let i = 0; i < this.state.length; i++) {
         this.state[i] = Ci.calIFreeBusyInterval.UNKNOWN;
       }
@@ -2295,36 +2239,42 @@
       this.addEventListener("mouseup", event => {
         this.mDragState = 0;
         window.setCursor("auto");
       });
     }
 
     connectedCallback() {
       if (!this.hasChildNodes() || !this.delayConnectedCallback()) {
+        // The width of the second inner box is set dynamically to ensure that
+        // the outer scrollbox is at least as wide as the free/busy grid.
+        // Otherwise it won't scroll properly.
         this.appendChild(
           MozXULElement.parseXULToFragment(`
                 <scrollbox width="0" orient="horizontal" flex="1">
                     <box class="selection-bar">
                         <box class="selection-bar-left"></box>
                         <spacer class="selection-bar-spacer" flex="1"></spacer>
                         <box class="selection-bar-right"></box>
                     </box>
+                    <box class="selection-padding"/>
                 </scrollbox>
             `)
         );
       }
 
       this.initTimeRange();
 
       // The basedate is the date/time from which the display of the timebar starts. The range is
       // the number of days we should be able to show. the start- and enddate is the time the
       // event is scheduled for.
       this.mRange = Number(this.getAttribute("range"));
+      this.mScrollBox = this.getElementsByTagName("scrollbox")[0];
       this.mSelectionbar = this.querySelector(".selection-bar");
+      this.mPaddingBox = this.querySelector(".selection-padding");
     }
 
     /**
      * Gets the zoom factor for the selection bar.
      *
      * @returns {Number}        Zoom factor
      */
     get zoomFactor() {
@@ -2480,16 +2430,35 @@
     init(width, height) {
       this.mContentWidth = width;
       this.mHeaderHeight = height + 2;
       this.mMargin = 0;
       this.update();
     }
 
     /**
+     * Scrolls horizontally to align with the free/busy grid.
+     *
+     * @param {Number} x        X position to scroll to
+     * @param {Number} y        Ignored
+     */
+    scrollTo(x, y) {
+      return this.mScrollBox.scrollTo(x, 0);
+    }
+
+    /**
+     * Ensures the scrollWidth of this element is at least the specified width.
+     *
+     * @param {Number} width    Width in pixels
+     */
+    padTo(width) {
+      return this.mPaddingBox.setAttribute("width", width);
+    }
+
+    /**
      * Given some specific date this method calculates the corrposonding offset in fractional hours.
      *
      * @param {calIDateTime} date       Date object
      */
     date2offset(date) {
       let num_hours = this.mEndHour - this.mStartHour;
       let diff = date.subtractDate(this.mBaseDate);
       let offset = diff.days * num_hours;
@@ -2818,31 +2787,16 @@
      *
      * @returns {Number}       Document size
      */
     get documentSize() {
       return this.getFreeBusyElement(1).documentSize;
     }
 
     /**
-     * Sets scroll offset value of freebusy grid element and and scroll property of all
-     * freebusy-row elements.
-     *
-     * @param {Number} val       Scroll offset value
-     * @return {Number}          Scroll offset value
-     */
-    set scroll(val) {
-      this.mScrollOffset = val;
-      for (let i = 1; i <= this.mMaxFreeBusy; i++) {
-        this.getFreeBusyElement(i).scroll = val;
-      }
-      return val;
-    }
-
-    /**
      * Cancel pending free/busy requests.
      */
     onUnload() {
       for (let request of this.mPendingRequests) {
         request.cancel(null);
       }
 
       this.mPendingRequests = [];
@@ -2937,19 +2891,16 @@
         let freebusy = this.getFreeBusyElement(i);
         freebusy.setAttribute("calid", list[i - 1].calid);
         freebusy.removeAttribute("dirty");
         if (list[i - 1].dirty) {
           freebusy.setAttribute("dirty", "true");
         }
       }
 
-      // Align all rows
-      this.scroll = this.mScrollOffset;
-
       this.updateFreeBusy();
     }
 
     /**
      * Updates the freebusy-grid element.
      */
     updateFreeBusy() {
       let fbService = cal.getFreeBusyService();
--- a/calendar/base/content/dialogs/calendar-event-dialog-attendees.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog-attendees.js
@@ -86,16 +86,17 @@ function onLoad() {
     zoom.setAttribute("disabled", "true");
     zoomOut.setAttribute("disabled", "true");
     zoomIn.setAttribute("disabled", "true");
     setZoomFactor(zoom.value);
   }
 
   loadDateTime(startTime, endTime);
   propagateDateTime();
+  onResize();
   // Set the scroll bar at where the event is
   scrollToCurrentTime();
   updateButtons();
 
   // attach an observer to get notified of changes
   // that are relevant to this dialog.
   let prefObserver = {
     observe: function(aSubject, aTopic, aPrefName) {
@@ -557,22 +558,24 @@ function changeAllDay() {
  */
 function onResize() {
   // Don't do anything if we haven't been initialized.
   if (!gStartDate || !gEndDate) {
     return;
   }
 
   let grid = document.getElementById("freebusy-grid");
+  grid.fitDummyRows();
+
   let gridScrollbar = document.getElementById("horizontal-scrollbar");
-  grid.fitDummyRows();
-  let gridRatio = grid.getBoundingClientRect().width / grid.documentSize;
-  let gridMaxpos = gridScrollbar.getAttribute("maxpos");
-  let gridInc = (gridMaxpos * gridRatio) / (1 - gridRatio);
-  gridScrollbar.setAttribute("pageincrement", gridInc);
+  gridScrollbar.setAttribute("maxpos", grid.scrollWidth - grid.clientWidth);
+  gridScrollbar.setAttribute("pageincrement", grid.clientWidth);
+
+  let selectionbar = document.getElementById("selection-bar");
+  selectionbar.padTo(grid.scrollWidth);
 
   let attendees = document.getElementById("attendees-list");
   let attendeesScrollbar = document.getElementById("vertical-scrollbar");
   let box = document.getElementById("vertical-scrollbar-box");
   attendees.fitDummyRows();
   let attRatio = attendees.getBoundingClientRect().height / attendees.documentSize;
   let attMaxpos = attendeesScrollbar.getAttribute("maxpos");
   if (attRatio < 1) {
@@ -747,26 +750,16 @@ function applyCurrentZoomFactor() {
   selectionbar.zoomFactor = gZoomFactor;
   let grid = document.getElementById("freebusy-grid");
   grid.zoomFactor = gZoomFactor;
 
   // Calling onResize() will update the scrollbars and everything else
   // that needs to adopt the previously made changes. We need to call
   // this after the changes have actually been made...
   onResize();
-
-  let scrollbar = document.getElementById("horizontal-scrollbar");
-  if (scrollbar.hasAttribute("maxpos")) {
-    let curpos = scrollbar.getAttribute("curpos");
-    let maxpos = scrollbar.getAttribute("maxpos");
-    let ratio = curpos / maxpos;
-    timebar.scroll = ratio;
-    grid.scroll = ratio;
-    selectionbar.ratio = ratio;
-  }
 }
 
 /**
  * Force the time grid to show 24 hours.
  *
  * @param aValue        If true, the view will be forced to 24 hours.
  * @return              aValue (for chaining)
  */
@@ -788,27 +781,16 @@ function setForce24Hours(aValue) {
   let grid = document.getElementById("freebusy-grid");
   grid.force24Hours = gForce24Hours;
 
   // Calling onResize() will update the scrollbars and everything else
   // that needs to adopt the previously made changes. We need to call
   // this after the changes have actually been made...
   onResize();
 
-  let scrollbar = document.getElementById("horizontal-scrollbar");
-  if (!scrollbar.hasAttribute("maxpos")) {
-    return aValue;
-  }
-  let curpos = scrollbar.getAttribute("curpos");
-  let maxpos = scrollbar.getAttribute("maxpos");
-  let ratio = curpos / maxpos;
-  timebar.scroll = ratio;
-  grid.scroll = ratio;
-  selectionbar.ratio = ratio;
-
   return aValue;
 }
 
 /**
  * Initialize the time range, setting the start and end hours from the prefs, or
  * to 24 hrs if gForce24Hours is set.
  */
 function initTimeRange() {
@@ -893,24 +875,22 @@ function onAttrModified(event) {
         let grid = document.getElementById("freebusy-grid");
         if (event.attrName == "curpos") {
           let maxpos = scrollbar.getAttribute("maxpos");
           attendees.ratio = event.newValue / maxpos;
         }
         grid.firstVisibleRow = attendees.firstVisibleRow;
       } else if (scrollbar.getAttribute("id") == "horizontal-scrollbar") {
         if (event.attrName == "curpos") {
-          let maxpos = scrollbar.getAttribute("maxpos");
-          let ratio = event.newValue / maxpos;
           let timebar = document.getElementById("timebar");
           let grid = document.getElementById("freebusy-grid");
           let selectionbar = document.getElementById("selection-bar");
-          timebar.scroll = ratio;
-          grid.scroll = ratio;
-          selectionbar.ratio = ratio;
+          timebar.scrollTo(event.newValue, 0);
+          grid.scrollTo(event.newValue, grid.scrollTop);
+          selectionbar.scrollTo(event.newValue, 0);
         }
       }
     }
   }
 }
 
 /**
  * Handler function for initializing the selection bar, event usually emitted
--- a/calendar/base/themes/common/dialogs/calendar-event-dialog.css
+++ b/calendar/base/themes/common/dialogs/calendar-event-dialog.css
@@ -522,17 +522,16 @@ calendar-event-freebusy-day > box {
     color: inherit !important;
 
     /* we set the minimal height to the height of the
      largest icon [the usertype-icon in this case] to
      ensure that the rows of the freebusy-grid and
      the attendee-list always have the same height,
      regardless of the font size. */
     height: 17px;
-    overflow: hidden;
 }
 
 .addressingWidgetCell {
     border-bottom: 1px solid var(--eventWidgetBorderColor);
     padding: 0px;
 }
 
 .addressingWidgetCell:first-child {