Bug 606343, part 1, add centering position types for panels, r=neil, a=blocking
authorNeil Deakin <neil@mozilla.com>
Sun, 05 Dec 2010 17:09:36 -0500
changeset 58647 d0b7c821c18ae9671a47992e0e40d7742545224b
parent 58646 2a6dd48ee44c31bae041fc891ef690d6245073d3
child 58648 5ce0b1299094b8b52e984edea1a0fe7497382664
push id17394
push userneil@mozilla.com
push dateSun, 05 Dec 2010 22:12:05 +0000
treeherdermozilla-central@44641ad32c29 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersneil, blocking
bugs606343
milestone2.0b8pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 606343, part 1, add centering position types for panels, r=neil, a=blocking
layout/xul/base/src/nsMenuPopupFrame.cpp
layout/xul/base/src/nsMenuPopupFrame.h
toolkit/content/tests/widgets/popup_shared.js
toolkit/content/tests/widgets/popup_trigger.js
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp
+++ b/layout/xul/base/src/nsMenuPopupFrame.cpp
@@ -536,16 +536,24 @@ nsMenuPopupFrame::InitPositionFromAnchor
   if (aAnchor.EqualsLiteral("topleft"))
     mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
   else if (aAnchor.EqualsLiteral("topright"))
     mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
   else if (aAnchor.EqualsLiteral("bottomleft"))
     mPopupAnchor = POPUPALIGNMENT_BOTTOMLEFT;
   else if (aAnchor.EqualsLiteral("bottomright"))
     mPopupAnchor = POPUPALIGNMENT_BOTTOMRIGHT;
+  else if (aAnchor.EqualsLiteral("leftcenter"))
+    mPopupAnchor = POPUPALIGNMENT_LEFTCENTER;
+  else if (aAnchor.EqualsLiteral("rightcenter"))
+    mPopupAnchor = POPUPALIGNMENT_RIGHTCENTER;
+  else if (aAnchor.EqualsLiteral("topcenter"))
+    mPopupAnchor = POPUPALIGNMENT_TOPCENTER;
+  else if (aAnchor.EqualsLiteral("bottomcenter"))
+    mPopupAnchor = POPUPALIGNMENT_BOTTOMCENTER;
   else
     mPopupAnchor = POPUPALIGNMENT_NONE;
 
   if (aAlign.EqualsLiteral("topleft"))
     mPopupAlignment = POPUPALIGNMENT_TOPLEFT;
   else if (aAlign.EqualsLiteral("topright"))
     mPopupAlignment = POPUPALIGNMENT_TOPRIGHT;
   else if (aAlign.EqualsLiteral("bottomleft"))
@@ -591,17 +599,24 @@ nsMenuPopupFrame::InitializePopup(nsICon
         mXPos = mYPos = 0;
     }
     else if (!aPosition.IsEmpty()) {
       position.Assign(aPosition);
     }
 
     mFlipBoth = flip.EqualsLiteral("both");
 
-    if (position.EqualsLiteral("before_start")) {
+    position.CompressWhitespace();
+    PRInt32 spaceIdx = position.FindChar(' ');
+    // if there is a space in the position, assume it is the anchor and
+    // alignment as two separate tokens.
+    if (spaceIdx >= 0) {
+      InitPositionFromAnchorAlign(Substring(position, 0, spaceIdx), Substring(position, spaceIdx + 1));
+    }
+    else if (position.EqualsLiteral("before_start")) {
       mPopupAnchor = POPUPALIGNMENT_TOPLEFT;
       mPopupAlignment = POPUPALIGNMENT_BOTTOMLEFT;
     }
     else if (position.EqualsLiteral("before_end")) {
       mPopupAnchor = POPUPALIGNMENT_TOPRIGHT;
       mPopupAlignment = POPUPALIGNMENT_BOTTOMRIGHT;
     }
     else if (position.EqualsLiteral("after_start")) {
@@ -874,30 +889,53 @@ nsMenuPopupFrame::GetRootViewForPopup(ns
     }
     view = temp;
   }
 
   return nsnull;
 }
 
 nsPoint
-nsMenuPopupFrame::AdjustPositionForAnchorAlign(const nsRect& anchorRect,
+nsMenuPopupFrame::AdjustPositionForAnchorAlign(nsRect& anchorRect,
                                                FlipStyle& aHFlip, FlipStyle& aVFlip)
 {
   // flip the anchor and alignment for right-to-left
   PRInt8 popupAnchor(mPopupAnchor);
   PRInt8 popupAlign(mPopupAlignment);
   if (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
-    popupAnchor = -popupAnchor;
+    // no need to flip the centered anchor types
+    if (popupAnchor < POPUPALIGNMENT_LEFTCENTER) {
+      popupAnchor = -popupAnchor;
+    }
     popupAlign = -popupAlign;
   }
 
   // first, determine at which corner of the anchor the popup should appear
   nsPoint pnt;
   switch (popupAnchor) {
+    case POPUPALIGNMENT_LEFTCENTER:
+      pnt = nsPoint(anchorRect.x, anchorRect.y + anchorRect.height / 2);
+      anchorRect.y = pnt.y;
+      anchorRect.height = 0;
+      break;
+    case POPUPALIGNMENT_RIGHTCENTER:
+      pnt = nsPoint(anchorRect.XMost(), anchorRect.y + anchorRect.height / 2);
+      anchorRect.y = pnt.y;
+      anchorRect.height = 0;
+      break;
+    case POPUPALIGNMENT_TOPCENTER:
+      pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.y);
+      anchorRect.x = pnt.x;
+      anchorRect.width = 0;
+      break;
+    case POPUPALIGNMENT_BOTTOMCENTER:
+      pnt = nsPoint(anchorRect.x + anchorRect.width / 2, anchorRect.YMost());
+      anchorRect.x = pnt.x;
+      anchorRect.width = 0;
+      break;
     case POPUPALIGNMENT_TOPRIGHT:
       pnt = anchorRect.TopRight();
       break;
     case POPUPALIGNMENT_BOTTOMLEFT:
       pnt = anchorRect.BottomLeft();
       break;
     case POPUPALIGNMENT_BOTTOMRIGHT:
       pnt = anchorRect.BottomRight();
@@ -939,23 +977,36 @@ nsMenuPopupFrame::AdjustPositionForAncho
   // vertically as well.
   // If we are flipping in both directions, we want to set a flip style both
   // horizontally and vertically. However, we want to flip on the inside edge
   // of the anchor. Consider the example of a typical dropdown menu.
   // Vertically, we flip the popup on the outside edges of the anchor menu,
   // however horizontally, we want to to use the inside edges so the popup
   // still appears underneath the anchor menu instead of floating off the
   // side of the menu.
-  FlipStyle anchorEdge = mFlipBoth ? FlipStyle_Inside: FlipStyle_None;
-  aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
-  if (((popupAnchor > 0) == (popupAlign > 0)) ||
-      (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
-    aVFlip = FlipStyle_Outside;
-  else
-    aVFlip = anchorEdge;
+  if (popupAnchor >= POPUPALIGNMENT_LEFTCENTER) {
+    if (popupAnchor == POPUPALIGNMENT_LEFTCENTER ||
+        popupAnchor == POPUPALIGNMENT_RIGHTCENTER) {
+      aHFlip = FlipStyle_Outside;
+      aVFlip = FlipStyle_Inside;
+    }
+    else {
+      aHFlip = FlipStyle_Inside;
+      aVFlip = FlipStyle_Outside;
+    }
+  }
+  else {
+    FlipStyle anchorEdge = mFlipBoth ? FlipStyle_Inside : FlipStyle_None;
+    aHFlip = (popupAnchor == -popupAlign) ? FlipStyle_Outside : anchorEdge;
+    if (((popupAnchor > 0) == (popupAlign > 0)) ||
+        (popupAnchor == POPUPALIGNMENT_TOPLEFT && popupAlign == POPUPALIGNMENT_TOPLEFT))
+      aVFlip = FlipStyle_Outside;
+    else
+      aVFlip = anchorEdge;
+  }
 
   return pnt;
 }
 
 nscoord
 nsMenuPopupFrame::FlipOrResize(nscoord& aScreenPoint, nscoord aSize, 
                                nscoord aScreenBegin, nscoord aScreenEnd,
                                nscoord aAnchorBegin, nscoord aAnchorEnd,
--- a/layout/xul/base/src/nsMenuPopupFrame.h
+++ b/layout/xul/base/src/nsMenuPopupFrame.h
@@ -104,16 +104,21 @@ enum FlipStyle {
 // values are selected so that the direction can be flipped just by
 // changing the sign
 #define POPUPALIGNMENT_NONE 0
 #define POPUPALIGNMENT_TOPLEFT 1
 #define POPUPALIGNMENT_TOPRIGHT -1
 #define POPUPALIGNMENT_BOTTOMLEFT 2
 #define POPUPALIGNMENT_BOTTOMRIGHT -2
 
+#define POPUPALIGNMENT_LEFTCENTER 16
+#define POPUPALIGNMENT_RIGHTCENTER 17
+#define POPUPALIGNMENT_TOPCENTER 18
+#define POPUPALIGNMENT_BOTTOMCENTER 19
+
 #define INC_TYP_INTERVAL  1000  // 1s. If the interval between two keypresses is shorter than this, 
                                 //   treat as a continue typing
 // XXX, kyle.yuan@sun.com, there are 4 definitions for the same purpose:
 //  nsMenuPopupFrame.h, nsListControlFrame.cpp, listbox.xml, tree.xml
 //  need to find a good place to put them together.
 //  if someone changes one, please also change the other.
 
 nsIFrame* NS_NewMenuPopupFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
@@ -349,17 +354,17 @@ protected:
   virtual void GetLayoutFlags(PRUint32& aFlags);
 
   void InitPositionFromAnchorAlign(const nsAString& aAnchor,
                                    const nsAString& aAlign);
 
   // return the position where the popup should be, when it should be
   // anchored at anchorRect. aHFlip and aVFlip will be set if the popup may be
   // flipped in that direction if there is not enough space available.
-  nsPoint AdjustPositionForAnchorAlign(const nsRect& anchorRect,
+  nsPoint AdjustPositionForAnchorAlign(nsRect& anchorRect,
                                        FlipStyle& aHFlip, FlipStyle& aVFlip);
 
   // check if the popup will fit into the available space and resize it. This
   // method handles only one axis at a time so is called twice, once for
   // horizontal and once for vertical. All arguments are specified for this
   // one axis. All coordinates are in app units relative to the screen.
   //   aScreenPoint - the point where the popup should appear
   //   aSize - the size of the popup
--- a/toolkit/content/tests/widgets/popup_shared.js
+++ b/toolkit/content/tests/widgets/popup_shared.js
@@ -321,16 +321,43 @@ function compareEdge(anchor, popup, edge
   var anchorrect = anchor.getBoundingClientRect();
   var popuprect = popup.getBoundingClientRect();
   var check1 = false, check2 = false;
 
   ok((Math.round(popuprect.right) - Math.round(popuprect.left)) &&
      (Math.round(popuprect.bottom) - Math.round(popuprect.top)),
      testname + " size");
 
+  var spaceIdx = edge.indexOf(" ");
+  if (spaceIdx > 0) {
+    let cornerX, cornerY;
+    let [anchor, align] = edge.split(" ");
+    switch (anchor) {
+      case "topleft": cornerX = anchorrect.left; cornerY = anchorrect.top; break;
+      case "topcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.top; break;
+      case "topright": cornerX = anchorrect.right; cornerY = anchorrect.top; break;
+      case "leftcenter": cornerX = anchorrect.left; cornerY = anchorrect.top + anchorrect.height / 2; break;
+      case "rightcenter": cornerX = anchorrect.right; cornerY = anchorrect.top + anchorrect.height / 2; break;
+      case "bottomleft": cornerX = anchorrect.left; cornerY = anchorrect.bottom; break;
+      case "bottomcenter": cornerX = anchorrect.left + anchorrect.width / 2; cornerY = anchorrect.bottom; break;
+      case "bottomright": cornerX = anchorrect.right; cornerY = anchorrect.bottom; break;
+    }
+
+    switch (align) {
+      case "topleft": cornerX += offsetX; cornerY += offsetY; break;
+      case "topright": cornerX += -popuprect.width + offsetX; cornerY += offsetY; break;
+      case "bottomleft": cornerX += offsetX; cornerY += -popuprect.height + offsetY; break;
+      case "bottomright": cornerX += -popuprect.width + offsetX; cornerY += -popuprect.height + offsetY; break;
+    }
+
+    is(Math.round(popuprect.left), Math.round(cornerX), testname + " x position");
+    is(Math.round(popuprect.top), Math.round(cornerY), testname + " y position");
+    return;
+  }
+
   if (edge == "after_pointer") {
     is(Math.round(popuprect.left), Math.round(anchorrect.left) + offsetX, testname + " x position");
     is(Math.round(popuprect.top), Math.round(anchorrect.top) + offsetY + 21, testname + " y position");
     return;
   }
 
   if (edge == "overlap") {
     ok(Math.round(anchorrect.left) + offsetY == Math.round(popuprect.left) &&
--- a/toolkit/content/tests/widgets/popup_trigger.js
+++ b/toolkit/content/tests/widgets/popup_trigger.js
@@ -165,17 +165,21 @@ var popupTests = [
 },
 {
   // these tests check to ensure that passing an anchor and position
   // puts the popup in the right place
   testname: "open popup anchored",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   autohide: "thepopup",
   steps: ["before_start", "before_end", "after_start", "after_end",
-          "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"],
+          "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap",
+          "topleft topleft", "topcenter topleft", "topright topleft",
+          "leftcenter topright", "rightcenter topright",
+          "bottomleft bottomleft", "bottomcenter bottomleft", "bottomright bottomleft",
+          "topleft bottomright", "bottomcenter bottomright", "rightcenter topright"],
   test: function(testname, step) {
     gExpectedTriggerNode = "notset";
     gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false);
   },
   result: function(testname, step) {
     // no triggerNode because it was opened without passing an event
     gExpectedTriggerNode = null;
     is(gMenuPopup.anchorNode, gTrigger, testname + " anchorNode");
@@ -185,26 +189,32 @@ var popupTests = [
   }
 },
 {
   // these tests check the same but with a 10 pixel margin on the popup
   testname: "open popup anchored with margin",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   autohide: "thepopup",
   steps: ["before_start", "before_end", "after_start", "after_end",
-          "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"],
+          "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap",
+          "topleft topleft", "topcenter topleft", "topright topleft",
+          "leftcenter topright", "rightcenter topright",
+          "bottomleft bottomleft", "bottomcenter bottomleft", "bottomright bottomleft",
+          "topleft bottomright", "bottomcenter bottomright", "rightcenter topright"],
   test: function(testname, step) {
     gMenuPopup.setAttribute("style", "margin: 10px;");
     gMenuPopup.openPopup(gTrigger, step, 0, 0, false, false);
   },
   result: function(testname, step) {
     var rightmod = step == "before_end" || step == "after_end" ||
-                   step == "start_before" || step == "start_after";
+                   step == "start_before" || step == "start_after" ||
+                   step.match(/topright$/) || step.match(/bottomright$/);
     var bottommod = step == "before_start" || step == "before_end" ||
-                    step == "start_after" || step == "end_after";
+                    step == "start_after" || step == "end_after" ||
+                   step.match(/bottomleft$/) || step.match(/bottomright$/);
     compareEdge(gTrigger, gMenuPopup, step, rightmod ? -10 : 10, bottommod ? -10 : 10, testname);
     gMenuPopup.removeAttribute("style");
   }
 },
 {
   // these tests check the same but with a -8 pixel margin on the popup
   testname: "open popup anchored with negative margin",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
@@ -226,17 +236,18 @@ var popupTests = [
 },
 {
   // these tests check to ensure that the position attribute can be used
   // to set the position of a popup instead of passing it as an argument
   testname: "open popup anchored with attribute",
   events: [ "popupshowing thepopup", "popupshown thepopup" ],
   autohide: "thepopup",
   steps: ["before_start", "before_end", "after_start", "after_end",
-          "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap"],
+          "start_before", "start_after", "end_before", "end_after", "after_pointer", "overlap",
+          "topcenter topleft", "topright bottomright", "leftcenter topright"],
   test: function(testname, step) {
     gMenuPopup.setAttribute("position", step);
     gMenuPopup.openPopup(gTrigger, "", 0, 0, false, false);
   },
   result: function(testname, step) { compareEdge(gTrigger, gMenuPopup, step, 0, 0, testname); }
 },
 {
   // this test checks to ensure that the attributes override flag to openPopup