Bug 669262 - Lightning cannot do semi-monthly entries. r=mmecca
authorDecathlon <bv1578@gmail.com>
Mon, 18 Feb 2013 18:25:17 +0100
changeset 14917 ceeb14e607503c7dce79011c57f9ad8f10170412
parent 14916 478a56a58bfffd319fe6202afd9acdd78eb04e8b
child 14918 574493001aad52824aa5d8f8e42987e10481282c
push id867
push userbugzilla@standard8.plus.com
push dateMon, 01 Apr 2013 20:44:27 +0000
treeherdercomm-beta@797726b8d244 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmmecca
bugs669262
Bug 669262 - Lightning cannot do semi-monthly entries. r=mmecca
calendar/base/content/calendar-daypicker.xml
calendar/base/jar.mn
calendar/base/modules/calRecurrenceUtils.jsm
calendar/base/themes/common/calendar-daypicker.css
calendar/base/themes/common/images/daypicker-background.png
calendar/base/themes/gnomestripe/calendar-daypicker.css
calendar/base/themes/pinstripe/calendar-daypicker.css
calendar/base/themes/winstripe/calendar-daypicker.css
calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
--- a/calendar/base/content/calendar-daypicker.xml
+++ b/calendar/base/content/calendar-daypicker.xml
@@ -14,25 +14,22 @@
   -->
 
   <binding id="daypicker" display="xul:button"
            extends="chrome://global/content/bindings/button.xml#button-base">
     <resources>
       <stylesheet src="chrome://calendar/skin/calendar-daypicker.css"/>
     </resources>
     <content>
-      <xul:stack anonid="stackid" class="daystack" xbl:inherits="bottom,right">
-        <xul:image anonid="imageid" xbl:inherits="mode,height,width"/>
-        <xul:hbox align="center">
-          <xul:label anonid="daytext" 
-                     class="toolbarbutton-text" 
-                     flex="1"
-                     xbl:inherits="value=label"/>
-        </xul:hbox>
-      </xul:stack>
+      <xul:hbox anonid="daypickerId" class="daypickerclass" align="center" flex="1">
+        <xul:label anonid="daytext"
+                   class="toolbarbutton-text"
+                   flex="1"
+                   xbl:inherits="value=label"/>
+      </xul:hbox>
     </content>
     <implementation>
       <method name="onmodified">
         <parameter name="aEvent"/>
         <body>
           <![CDATA[
             if (aEvent.attrName =="checked") {
                 var event = document.createEvent('Events');
@@ -199,20 +196,17 @@
               <daypicker label="26" xbl:inherits="disabled, mode=id"/>
               <daypicker label="27" xbl:inherits="disabled, mode=id"/>
               <daypicker label="28" right="true" xbl:inherits="disabled, mode=id"/>
             </xul:hbox>
             <xul:hbox class="daypicker-row" flex="1">
               <daypicker bottom="true" label="29" xbl:inherits="disabled, mode=id"/>
               <daypicker bottom="true" label="30" xbl:inherits="disabled, mode=id"/>
               <daypicker bottom="true" label="31" xbl:inherits="disabled, mode=id"/>
-              <daypicker disabled="true" bottom="true" xbl:inherits="mode=id"/>
-              <daypicker disabled="true" bottom="true" xbl:inherits="mode=id"/>
-              <daypicker disabled="true" bottom="true" xbl:inherits="mode=id"/>
-              <daypicker disabled="true" bottom="true" right="true" xbl:inherits="mode=id"/>
+              <daypicker bottom="true" right="true" label="" xbl:inherits="disabled, mode=id"/>
             </xul:hbox>
           </xul:vbox>
         </content>
     <implementation>
       <property name="days">
         <setter><![CDATA[
           var mainbox =
               document.getAnonymousElementByAttribute(
@@ -224,50 +218,55 @@
               var numChilds = row.childNodes.length;
               for (var j = 0; j < numChilds; j++) {
                   var child = row.childNodes[j];
                   child.removeAttribute("checked");
                   days.push(child);
               }
           }
           for (i = 0; i < val.length; i++) {
-              let index = (val[i] < 0 ? val[i] + days.length : val[i] - 1);
+              let lastDayOffset = val[i] == -1 ? 0 : -1;
+              let index = (val[i] < 0 ? val[i] + days.length + lastDayOffset
+                                      : val[i] - 1);
               days[index].setAttribute("checked", "true");
           }
           return val;
         ]]></setter>
         <getter><![CDATA[
           var mainbox =
               document.getAnonymousElementByAttribute(
                   this, "anonid", "mainbox");
           var numRows = mainbox.childNodes.length;
           var days = [];
           for (var i = 0; i < numRows; i++) {
               var row = mainbox.childNodes[i];
               var numChilds = row.childNodes.length;
               for (var j = 0; j < numChilds; j++) {
                   var child = row.childNodes[j];
                   if (child.getAttribute("checked") == "true") {
-                      days.push(Number(child.label));
+                      days.push(Number(child.label) ? Number(child.label) : -1);
                   }
               }
           }
           return days;
         ]]></getter>
       </property>
 
       <constructor><![CDATA[
-        var mainbox =
+        let mainbox =
             document.getAnonymousElementByAttribute(
                 this, "anonid", "mainbox");
-        var numRows = mainbox.childNodes.length;
-        for (var i = 0; i < numRows; i++) {
-            var row = mainbox.childNodes[i];
-            var numChilds = row.childNodes.length;
-            for (var j = 0; j < numChilds; j++) {
-                var child = row.childNodes[j];
+        let numRows = mainbox.childNodes.length;
+        let child = null;
+        for (let i = 0; i < numRows; i++) {
+            let row = mainbox.childNodes[i];
+            let numChilds = row.childNodes.length;
+            for (let j = 0; j < numChilds; j++) {
+                child = row.childNodes[j];
                 child.calendar = this;
             }
         }
+        let labelLastDay = calGetString("calendar-event-dialog", "eventRecurrenceMonthlyLastDayLabel");
+        child.setAttribute("label", labelLastDay);
       ]]></constructor>
     </implementation>
   </binding>
 </bindings>
--- a/calendar/base/jar.mn
+++ b/calendar/base/jar.mn
@@ -123,17 +123,16 @@ calendar.jar:
     skin/calendar/calendar-properties-dialog.css           (themes/common/dialogs/calendar-properties-dialog.css)
     skin/calendar/calendar-subscriptions-dialog.css        (themes/common/dialogs/calendar-subscriptions-dialog.css)
     skin/calendar/calendar-providerUninstall-dialog.css    (themes/common/calendar-providerUninstall-dialog.css)
     skin/calendar/calendar-overlay.png                     (themes/common/images/calendar-overlay.png)
     skin/calendar/calendar-printing.css                    (themes/common/calendar-printing.css)
     skin/calendar/calendar-status.png                      (themes/common/images/calendar-status.png)
     skin/calendar/classification.png                       (themes/common/images/classification.png)
     skin/calendar/common/calendar-task-tree.css            (themes/common/calendar-task-tree.css)
-    skin/calendar/daypicker-background.png                 (themes/common/images/daypicker-background.png)
     skin/calendar/day-box-item-image.png                   (themes/common/images/day-box-item-image.png)
     skin/calendar/event-grippy-bottom.png                  (themes/common/images/event-grippy-bottom.png)
     skin/calendar/event-grippy-left.png                    (themes/common/images/event-grippy-left.png)
     skin/calendar/event-grippy-right.png                   (themes/common/images/event-grippy-right.png)
     skin/calendar/event-grippy-top.png                     (themes/common/images/event-grippy-top.png)
     skin/calendar/task-images.png                          (themes/common/images/task-images.png)
     skin/calendar/timezone_map.png                         (themes/common/images/timezone_map.png)
     skin/calendar/timezones.png                            (themes/common/images/timezones.png)
--- a/calendar/base/modules/calRecurrenceUtils.jsm
+++ b/calendar/base/modules/calRecurrenceUtils.jsm
@@ -144,67 +144,69 @@ function recurrenceRule2String(recurrenc
                             ordinalString = getRString(ordinalString);
                             dayString = getRString(dayString);
                             let stringOrdinalWeekday = getRString("ordinalWeekdayOrder",
                                                                   [ordinalString, dayString]);
                             weekdaysString_position += stringOrdinalWeekday + ", ";
                         }
                     }
                     let weekdaysString = weekdaysString_every + weekdaysString_position;
-                    weekdaysString = weekdaysString.slice(0,-2).
-                                     replace(/,(?= [^,]*$)/, ' ' + getRString("repeatDetailsAnd"));
+                    weekdaysString = weekdaysString.slice(0,-2)
+                                     .replace(/,(?= [^,]*$)/, ' ' + getRString("repeatDetailsAnd"));
 
                     let monthlyString = weekdaysString_every ? "monthlyEveryOfEvery" : "monthlyRuleNthOfEvery";
                     monthlyString = nounClass("repeatDetailsDay" + day_of_week(firstDay), monthlyString);
                     monthlyString = getRString(monthlyString, [weekdaysString]);
                     ruleString = PluralForm.get(rule.interval, monthlyString).
                                             replace("#2", rule.interval);
                 } else if (checkRecurrenceRule(rule, ['BYMONTHDAY'])) {
                     let component = rule.getComponent("BYMONTHDAY", {});
 
                     // First, find out if the 'BYMONTHDAY' component contains
-                    // any elements with a negative value. If so we currently
-                    // don't support anything but the 'last day of the month' rule.
+                    // any elements with a negative value lesser than -1 ("the
+                    // last day"). If so we currently don't support any rule
                     if (component.some(function(element, index, array) {
-                                           return element < 0;
+                                           return element < -1;
                                        })) {
+                        // we don't support any other combination for now...
+                        return getRString("ruleTooComplex");
+                    } else {
                         if (component.length == 1 && component[0] == -1) {
+                            // i.e. one day, the last day of the month
                             let monthlyString = getRString("monthlyLastDayOfNth");
                             ruleString = PluralForm.get(rule.interval, monthlyString)
                                                    .replace("#1", rule.interval);
-                        } else {
-                            // we don't support any other combination for now...
-                            return null;
-                        }
-                    } else {
-                        if (component.length == 31 &&
-                            component.every(function (element, index, array) {
-                                                for (let i = 0; i < array.length; i++) {
-                                                    if ((index + 1) == array[i]) {
-                                                        return true;
-                                                    }
-                                                }
-                                                return false;
-                                            })) {
+                        } else if (component.length == 31 &&
+                                    component.every(function (element, index, array) {
+                                                        for (let i = 0; i < array.length; i++) {
+                                                            if ((index + 1) == array[i])
+                                                                return true;
+                                                        }
+                                                        return false;
+                                                    })) {
                             // i.e. every day every N months
                             ruleString = getRString("monthlyEveryDayOfNth");
                             ruleString = PluralForm.get(rule.interval, ruleString)
                                                    .replace("#2", rule.interval);
                         } else {
                             // i.e. one or more monthdays every N months
                             let day_string = "";
+                            let lastDay = false;
                             for (let i = 0; i < component.length; i++) {
-                                day_string += component[i];
-                                if (component.length > 1 &&
-                                    i == (component.length - 2)) {
-                                    day_string += ' ' + getRString("repeatDetailsAnd") + ' ';
-                                } else if (i < component.length-1) {
-                                    day_string += ', ';
+                                if (component[i] == -1) {
+                                    lastDay = true;
+                                    continue;
                                 }
+                                day_string += component[i] + ", ";
                             }
+                            if (lastDay) {
+                                day_string += getRString("monthlyLastDay") + ", ";
+                            }
+                            day_string = day_string.slice(0,-2)
+                                         .replace(/,(?= [^,]*$)/, ' ' + getRString("repeatDetailsAnd"));
                             let monthlyString = getRString("monthlyDayOfNth", [day_string]);
                             ruleString = PluralForm.get(rule.interval, monthlyString)
                                                    .replace("#2", rule.interval);
                         }
                     }
                 } else {
                     let monthlyString = getRString("monthlyDayOfNth", [startDate.day]);
                     ruleString = PluralForm.get(rule.interval, monthlyString)
--- a/calendar/base/themes/common/calendar-daypicker.css
+++ b/calendar/base/themes/common/calendar-daypicker.css
@@ -1,44 +1,53 @@
 /* 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/. */
 
 daypicker {
   -moz-binding: url(chrome://calendar/content/calendar-daypicker.xml#daypicker);
-  list-style-image: url("chrome://calendar/skin/daypicker-background.png");
-  -moz-image-region: rect(0px 31px 30px 0px);
+  background-image: linear-gradient(rgba(0, 0, 0, .0) 5%, rgba(0, 0, 0, .20));
   background-color: -moz-Field;
   text-align: center;
 }
 
 daypicker[mode="monthly-days"] {
-  width: 21px;
+  width: 32px;
+  height: 15px;
+}
+
+daypicker[mode="daypicker-weekday"] {
+  min-width: 36px;
+  height: 32px;
+}
+
+daypicker[mode="monthly-days"][bottom="true"][right="true"] {
+  width: 128px;
   height: 15px;
 }
 
 daypicker:hover {
-  -moz-image-region: rect(0px 62px 30px 31px);
+  background-image: linear-gradient(rgba(255, 255, 255, .0), rgba(0, 0, 0, .10) 90%);
   cursor: pointer;
 }
 
 daypicker:hover:active,
 daypicker[open="true"] {
-  -moz-image-region: rect(0px 93px 30px 62px);
+  background-image: linear-gradient(rgba(0, 0, 0, .15), rgba(0, 0, 0, .01) 15%);
   cursor: pointer;
 }
 
 daypicker[disabled="true"],
 daypicker[disabled="true"][checked="true"],
 daypicker[disabled="true"]:hover,
 daypicker[disabled="true"]:hover:active,
 daypicker[disabled="true"][open="true"] {
-  -moz-image-region: rect(0px 31px 30px 0px);
+  background-image: linear-gradient(rgba(0, 0, 0, .0) 5%, rgba(0, 0, 0, .20));
   color: GrayText;
   cursor: default;
   background-color: -moz-Dialog;
 }
 
 daypicker[checked="true"] {
-  -moz-image-region: rect(0px 124px 30px 93px);
+  background-image: linear-gradient(rgba(0, 0, 0, .30), rgba(255, 255, 255, .0) 35%);
   background-color: Highlight;
   color: HighlightText;
 }
deleted file mode 100644
index 6d584dbd50505ec20dfcdb095c2dd534ac34f45a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/calendar/base/themes/gnomestripe/calendar-daypicker.css
+++ b/calendar/base/themes/gnomestripe/calendar-daypicker.css
@@ -4,15 +4,15 @@
 
 @import url(chrome://calendar/skin/common/calendar-daypicker.css);
 
 daypicker {
   border-top: 1px solid ThreeDShadow;
   border-left: 1px solid ThreeDShadow;
 }
 
-daypicker [bottom="true"] {
+daypicker[bottom="true"] {
   border-bottom: 1px solid ThreeDShadow;
 }
 
 daypicker[right="true"] {
   border-right: 1px solid ThreeDShadow;
 }
--- a/calendar/base/themes/pinstripe/calendar-daypicker.css
+++ b/calendar/base/themes/pinstripe/calendar-daypicker.css
@@ -4,15 +4,15 @@
 
 @import url(chrome://calendar/skin/common/calendar-daypicker.css);
 
 daypicker {
   border-top: 1px solid #808080;
   border-left: 1px solid #808080;
 }
 
-daypicker [bottom="true"] {
+daypicker[bottom="true"] {
   border-bottom: 1px solid #808080;
 }
 
 daypicker[right="true"] {
   border-right: 1px solid #808080;
 }
\ No newline at end of file
--- a/calendar/base/themes/winstripe/calendar-daypicker.css
+++ b/calendar/base/themes/winstripe/calendar-daypicker.css
@@ -4,15 +4,15 @@
 
 @import url(chrome://calendar/skin/common/calendar-daypicker.css);
 
 daypicker {
   border-top: 1px solid ThreeDShadow;
   border-left: 1px solid ThreeDShadow;
 }
 
-daypicker [bottom="true"] {
+daypicker[bottom="true"] {
   border-bottom: 1px solid ThreeDShadow;
 }
 
 daypicker[right="true"] {
   border-right: 1px solid ThreeDShadow;
 }
--- a/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
+++ b/calendar/locales/en-US/chrome/calendar/calendar-event-dialog.properties
@@ -280,16 +280,23 @@ repeatDetailsInfinite=Occurs %1$S\neffec
 # Edit recurrence window -> Recurrence details link on Event/Task dialog window
 # %1%$ - A rule string (see above). This is the first line of the link
 # %2%$ - event start date (e.g. mm/gg/yyyy)
 # e.g. with monthlyDayOfNth and all day event:
 # "Occurs day 3 of every 5 month
 #  effective 1/1/2009"
 repeatDetailsInfiniteAllDay=Occurs %1$S\neffective %2$S.
 
+# LOCALIZATION NOTE (monthlyLastDay):
+# Edit recurrence window -> Recurrence details link on Event/Task dialog window
+# A monthly rule with one or more days of the month (monthlyDayOfNth) and the
+# string "the last day" of the month.
+# e.g.: "Occurs day 15, 20, 25 and the last day of every 3 months"
+monthlyLastDay=the last day
+
 # LOCALIZATION NOTE (ruleTooComplex):
 # This string is shown in the repeat details area if our code can't handle the
 # complexity of the recurrence rule yet.
 ruleTooComplex=Click here for details
 
 # LOCALIZATION NOTE (ruleTooComplexSummary):
 # This string is shown in the event summary dialog if our code can't handle the
 # complexity of the recurrence rule yet.
@@ -373,8 +380,14 @@ repeatDetailsDay5Plural=Thursday
 repeatDetailsDay6Plural=Friday
 repeatDetailsDay7Plural=Saturday
 
 # LOCALIZATION NOTE (eventRecurrenceForeverLabel):
 # Edit/New Event dialog -> datepicker that sets the until date.
 # For recurring rules that repeat forever, this labels appears in the
 # datepicker, below the minimonth, as an option for the until date.
 eventRecurrenceForeverLabel=Forever
+
+# LOCALIZATION NOTE (eventRecurrenceMonthlyLastDayLabel):
+# Edit dialog recurrence -> Monthly Recurrence pattern -> Monthly daypicker
+# The label on the monthly daypicker's last button that allows to select
+# the last day of the month inside a BYMONTHDAY rule.
+eventRecurrenceMonthlyLastDayLabel=Last day