calendar/import-export/CalMonthGridPrinter.jsm
author Mozilla Releng Treescript <release+treescript@mozilla.org>
Mon, 01 Jun 2020 21:06:52 +0000
changeset 38423 a72e4d7908e0d4da3cfb13e0db7eee2d478269d3
parent 37973 6c708ef50ab7e74e16d63e113e730939eb012d3d
child 39075 18f27bc33168c576676908b2a9cdb34707fb89e2
permissions -rw-r--r--
no bug - Bumping Thunderbird l10n changesets r=release a=l10n-bump CLOSED TREE af -> removed ar -> 13f4e5c63aed ast -> 06238bcd1d71 be -> 2a2c4bfa536c bg -> b066986baad7 br -> 77130fec77d1 ca -> 05263001b4fb cak -> 34cffcb6f7ca cs -> 7d6080744017 cy -> 74a71abffa27 da -> d79d59042826 de -> 23f2aa4ae3de dsb -> c7548879c855 el -> e7b6bf516f62 en-GB -> 64789d6d3264 es-AR -> 44ce7b8a21f6 es-ES -> e131e33378bd et -> af1beec7a2c5 eu -> dff4a7c14438 fa -> removed fi -> b04da68482de fr -> 7e2320d07b94 fy-NL -> f43fd875b36b ga-IE -> 56b7bb91b552 gd -> 94b469991a3b gl -> 228a11d8ba8c he -> ed93e84e5416 hr -> 037459e37cc3 hsb -> 192b76cd4451 hu -> 92669fa62f5a hy-AM -> 49a2e6124917 id -> 240f090ba92f is -> 7cda20fa902a it -> ec692ed62c10 ja -> 5eed784b3cd9 ja-JP-mac -> e37f1ed0ff6d ka -> 4ce3c0e3feb7 kab -> 6ea74b301cc8 kk -> 95ec87f8788e ko -> ed50767d5b05 lt -> d71a8aa38738 ms -> ac99de934f0c nb-NO -> 4c42b2eb457c nl -> f31b19039101 nn-NO -> e2d68d6fd0eb pa-IN -> removed pl -> d6453ed2bb87 pt-BR -> ad22cae34261 pt-PT -> 693f4dace00c rm -> 80d37050607e ro -> a5af9f2e8f1d ru -> 3a82831ae1d9 si -> 55e501a4e096 sk -> d1c8c57d517d sl -> ecae5ba0d9b7 sq -> b9ad05079398 sr -> f15c2fb5fd90 sv-SE -> 21c851897e57 th -> removed tr -> dc70d34d90d1 uk -> bbfae865d18b uz -> 3a9d7e55dbe9 vi -> 20973eb20233 zh-CN -> d9fa062cd366 zh-TW -> 62ce156b3e49

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

var EXPORTED_SYMBOLS = ["CalMonthPrinter"];

var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");

/**
 * Prints a rough month-grid of events/tasks
 */
function CalMonthPrinter() {
  this.wrappedJSObject = this;
}

CalMonthPrinter.prototype = {
  QueryInterface: ChromeUtils.generateQI([Ci.calIPrintFormatter]),
  classID: Components.ID("{f42d5132-92c4-487b-b5c8-38bf292d74c1}"),

  get name() {
    return cal.l10n.getCalString("monthPrinterName");
  },

  formatToHtml(aStream, aStart, aEnd, aItems, aTitle) {
    let document = cal.xml.parseFile("chrome://calendar/content/printing/calMonthGridPrinter.html");
    let defaultTimezone = cal.dtz.defaultTimezone;

    // Set page title
    document.getElementById("title").textContent = aTitle;

    cal.view.colorTracker.addColorsToDocument(document);

    // Table that maps YYYY-MM-DD to the DOM node container where items are to be added
    let dayTable = {};

    // Make sure to create tables from start to end, if passed
    if (aStart && aEnd) {
      let startDate = this.normalizeStartDate(aStart);
      let endDate = this.normalizeEndDate(aEnd);
      let weekInfoService = cal.getWeekInfoService();

      // Now set up all the months we need to
      for (
        let current = startDate.clone();
        weekInfoService.getEndOfWeek(current.endOfMonth).compare(endDate) < 0;
        current.month += 1
      ) {
        this.setupMonth(document, current, dayTable);
      }
    }

    for (let item of aItems) {
      let itemStartDate = item[cal.dtz.startDateProp(item)] || item[cal.dtz.endDateProp(item)];
      let itemEndDate = item[cal.dtz.endDateProp(item)] || item[cal.dtz.startDateProp(item)];

      if (!itemStartDate && !itemEndDate) {
        cal.print.addItemToDayboxNodate(document, item);
        continue;
      }
      itemStartDate = itemStartDate.getInTimezone(defaultTimezone);
      itemEndDate = itemEndDate.getInTimezone(defaultTimezone);

      let boxDate = itemStartDate.clone();
      boxDate.isDate = true;
      for (boxDate; boxDate.compare(itemEndDate) < (itemEndDate.isDate ? 0 : 1); boxDate.day++) {
        // Ignore items outside of the range, i.e tasks without start date
        // where the end date is somewhere else.
        if (
          aStart &&
          aEnd &&
          boxDate &&
          (boxDate.compare(aStart) < 0 || boxDate.compare(aEnd) >= 0)
        ) {
          continue;
        }

        let boxDateKey = cal.print.getDateKey(boxDate);

        if (!(boxDateKey in dayTable)) {
          // Doesn't exist, we need to create a new table for it
          let startOfMonth = boxDate.startOfMonth;
          this.setupMonth(document, startOfMonth, dayTable);
        }

        let dayBoxes = dayTable[boxDateKey];
        let addSingleItem = cal.print.addItemToDaybox.bind(cal.print, document, item, boxDate);

        if (Array.isArray(dayBoxes)) {
          dayBoxes.forEach(addSingleItem);
        } else {
          addSingleItem(dayBoxes);
        }
      }
    }

    // Remove templates from HTML, no longer needed
    let templates = document.getElementById("templates");
    templates.remove();

    // Stream out the resulting HTML
    let html = cal.xml.serializeDOM(document);
    let convStream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(
      Ci.nsIConverterOutputStream
    );
    convStream.init(aStream, "UTF-8");
    convStream.writeString(html);
  },

  normalizeStartDate(aStart) {
    // Make sure the start date is really a date.
    let startDate = aStart.clone();
    startDate.isDate = true;

    // Find out if the start date is also shown in the first week of the
    // following month. This means we can spare a month printout.
    let firstDayOfNextMonth = startDate.clone();
    firstDayOfNextMonth.day = 1;
    firstDayOfNextMonth.month++;
    if (
      cal
        .getWeekInfoService()
        .getStartOfWeek(firstDayOfNextMonth)
        .compare(startDate) <= 0
    ) {
      startDate = firstDayOfNextMonth;
    } else {
      startDate = startDate.startOfMonth;
    }
    return startDate;
  },

  normalizeEndDate(aEnd) {
    // Copy end date, which is exclusive. For our calculations, we will
    // only be handling dates and the formatToHtml() code is much cleaner with
    // the range being inclusive.
    let endDate = aEnd.clone();
    endDate.isDate = true;

    // Find out if the end date is also shown in the last week of the
    // previous month. This also means we can spare a month printout.
    let lastDayOfPreviousMonth = endDate.clone();
    lastDayOfPreviousMonth.month--;
    lastDayOfPreviousMonth = lastDayOfPreviousMonth.endOfMonth;
    if (
      cal
        .getWeekInfoService()
        .getEndOfWeek(lastDayOfPreviousMonth)
        .compare(endDate) >= 0
    ) {
      endDate = lastDayOfPreviousMonth;
    }

    return endDate;
  },

  setupMonth(document, startOfMonth, dayTable) {
    let monthTemplate = document.getElementById("month-template");
    let monthContainer = document.getElementById("month-container");

    // Clone the template month and make sure it doesn't have an id
    let currentMonth = monthTemplate.cloneNode(true);
    currentMonth.removeAttribute("id");
    currentMonth.item = startOfMonth.clone();

    // Set up the month title
    let monthName = cal.l10n.formatMonth(startOfMonth.month + 1, "calendar", "monthInYear");
    let monthTitle = cal.l10n.getCalString("monthInYear", [monthName, startOfMonth.year]);
    currentMonth.querySelector(".month-name").textContent = monthTitle;

    // Set up the weekday titles
    let wkst = Services.prefs.getIntPref("calendar.week.start", 0);
    for (let i = 1; i <= 7; i++) {
      let dayNumber = ((i + wkst - 1) % 7) + 1;
      let dayTitle = currentMonth.querySelector(`.day${i}-title`);
      dayTitle.textContent = cal.l10n.getDateFmtString(`day.${dayNumber}.Mmm`);
    }

    // Set up each week
    let weekInfoService = cal.getWeekInfoService();
    let endOfMonthView = weekInfoService.getEndOfWeek(startOfMonth.endOfMonth);
    let startOfMonthView = weekInfoService.getStartOfWeek(startOfMonth);
    let mainMonth = startOfMonth.month;
    let weekContainer = currentMonth.querySelector(".week-container");

    for (
      let weekStart = startOfMonthView;
      weekStart.compare(endOfMonthView) < 0;
      weekStart.day += 7
    ) {
      this.setupWeek(document, weekContainer, weekStart, mainMonth, dayTable);
    }

    // Now insert the month into the page container, sorting by date (and therefore by month)
    function compareDates(a, b) {
      return !a || !b ? -1 : a.compare(b);
    }

    cal.data.binaryInsertNode(monthContainer, currentMonth, currentMonth.item, compareDates);
  },

  setupWeek(document, weekContainer, startOfWeek, mainMonth, dayTable) {
    const weekdayMap = [
      "sunday",
      "monday",
      "tuesday",
      "wednesday",
      "thursday",
      "friday",
      "saturday",
    ];
    let weekTemplate = document.getElementById("week-template");

    // Clone the template week and make sure it doesn't have an id
    let currentWeek = weekTemplate.cloneNode(true);
    currentWeek.removeAttribute("id");

    // Set up day numbers for all days in this week
    let currentDate = startOfWeek.clone();
    for (let i = 1; i <= 7; i++) {
      let dayNumber = currentWeek.querySelector(".day" + i + "-number");
      let dayContainer = currentWeek.querySelector(".day" + i + "-container");
      let dayBox = currentWeek.querySelector(".day" + i + "-box");
      let dateKey = cal.print.getDateKey(currentDate);
      dayNumber.textContent = currentDate.day;

      // We need to support adding multiple boxes, since the months have
      // overlapping days.
      if (dateKey in dayTable) {
        if (Array.isArray(dayTable[dateKey])) {
          dayTable[dateKey].push(dayContainer);
        } else {
          dayTable[dateKey] = [dayTable[dateKey], dayContainer];
        }
      } else {
        dayTable[dateKey] = dayContainer;
      }

      let weekDay = currentDate.weekday;
      let dayOffPrefName = "calendar.week.d" + weekDay + weekdayMap[weekDay] + "soff";
      if (Services.prefs.getBoolPref(dayOffPrefName, false)) {
        dayBox.className += " day-off";
      }

      if (currentDate.month != mainMonth) {
        dayBox.className += " out-of-month";
      }
      currentDate.day++;
    }

    // No need for sorting, setupWeek will be called in sequence
    weekContainer.appendChild(currentWeek);
  },
};