Mozilla/mozilla/extensions/spatialnavigation/src/nsSpatialNavigationUtils.cpp
Olli.Pettay%helsinki.fi 7a1f7182a1 Bug 363089, Remove nsIDOMEventReceiver (this time passes mochitest), r+sr=jst
git-svn-id: svn://10.0.0.236/trunk@226373 18797224-902f-48f8-a5cc-f745e15eee43
2007-05-14 09:13:09 +00:00

853 lines
20 KiB
C++
Executable File

/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Spatial Navigation
*
* The Initial Developer of the Original Code is
* Douglas F. Turner II <dougt@meer.net>
* Portions created by the Initial Developer are Copyright (C) 2004-2005
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsSpatialNavigationPrivate.h"
#ifdef DEBUG_outputframes
#include "nsIFrameDebug.h"
#endif
static PRBool is_space(char c)
{
return (c == ' ' || c == '\f' || c == '\n' ||
c == '\r' || c == '\t' || c == '\v');
}
nscoord* lo_parse_coord_list(char *str, PRInt32* value_cnt)
{
char *tptr;
char *n_str;
PRInt32 i, cnt;
PRInt32 *value_list;
/*
* Nothing in an empty list
*/
*value_cnt = 0;
if (!str || *str == '\0')
{
return nsnull;
}
/*
* Skip beginning whitespace, all whitespace is empty list.
*/
n_str = str;
while (is_space(*n_str))
{
n_str++;
}
if (*n_str == '\0')
{
return nsnull;
}
/*
* Make a pass where any two numbers separated by just whitespace
* are given a comma separator. Count entries while passing.
*/
cnt = 0;
while (*n_str != '\0')
{
PRBool has_comma;
/*
* Skip to a separator
*/
tptr = n_str;
while (!is_space(*tptr) && *tptr != ',' && *tptr != '\0')
{
tptr++;
}
n_str = tptr;
/*
* If no more entries, break out here
*/
if (*n_str == '\0')
{
break;
}
/*
* Skip to the end of the separator, noting if we have a
* comma.
*/
has_comma = PR_FALSE;
while (is_space(*tptr) || *tptr == ',')
{
if (*tptr == ',')
{
if (has_comma == PR_FALSE)
{
has_comma = PR_TRUE;
}
else
{
break;
}
}
tptr++;
}
/*
* If this was trailing whitespace we skipped, we are done.
*/
if ((*tptr == '\0')&&(has_comma == PR_FALSE))
{
break;
}
/*
* Else if the separator is all whitespace, and this is not the
* end of the string, add a comma to the separator.
*/
else if (has_comma == PR_FALSE)
{
*n_str = ',';
}
/*
* count the entry skipped.
*/
cnt++;
n_str = tptr;
}
/*
* count the last entry in the list.
*/
cnt++;
/*
* Allocate space for the coordinate array.
*/
value_list = new nscoord[cnt];
if (!value_list)
{
return nsnull;
}
/*
* Second pass to copy integer values into list.
*/
tptr = str;
for (i=0; i<cnt; i++)
{
char *ptr;
ptr = strchr(tptr, ',');
if (ptr)
{
*ptr = '\0';
}
/*
* Strip whitespace in front of number because I don't
* trust atoi to do it on all platforms.
*/
while (is_space(*tptr))
{
tptr++;
}
if (*tptr == '\0')
{
value_list[i] = 0;
}
else
{
value_list[i] = (nscoord) ::atoi(tptr);
}
if (ptr)
{
*ptr = ',';
tptr = ptr + 1;
}
}
*value_cnt = cnt;
return value_list;
}
nsresult createFrameTraversal(nsPresContext* aPresContext,
PRInt32 aType,
PRBool aVisual,
PRBool aLockInScrollView,
PRBool aFollowOOFs,
nsIBidirectionalEnumerator** outTraversal)
{
nsresult result;
if (!aPresContext)
return NS_ERROR_FAILURE;
nsIPresShell* presShell = aPresContext->PresShell();
if (!presShell)
return NS_ERROR_FAILURE;
nsIFrame* frame = presShell->GetRootFrame();
if (!frame)
return NS_ERROR_FAILURE;
#ifdef DEBUG_outputframes
nsIFrameDebug* fd;
frame->QueryInterface(NS_GET_IID(nsIFrameDebug), (void**) &fd);
if (fd)
fd->List(frame->GetPresContext(), stdout, 0);
#endif
nsCOMPtr<nsIBidirectionalEnumerator> frameTraversal;
static NS_DEFINE_CID(kFrameTraversalCID, NS_FRAMETRAVERSAL_CID);
nsCOMPtr<nsIFrameTraversal> trav(do_CreateInstance(kFrameTraversalCID,&result));
if (NS_FAILED(result))
return result;
result = trav->NewFrameTraversal(getter_AddRefs(frameTraversal),
aPresContext, frame,
aType, aVisual, aLockInScrollView, aFollowOOFs);
if (NS_FAILED(result))
return result;
NS_IF_ADDREF(*outTraversal = frameTraversal);
return NS_OK;
}
nsresult getEventTargetFromWindow(nsIDOMWindow* aWindow, nsIDOM3EventTarget** aEventTarget, nsIDOMEventGroup** aSystemGroup)
{
*aEventTarget = nsnull;
nsCOMPtr<nsPIDOMWindow> privateWindow = do_QueryInterface(aWindow);
if (!privateWindow)
return NS_ERROR_UNEXPECTED; // assert
nsPIDOMEventTarget *chromeEventHandler = privateWindow->GetChromeEventHandler();
if (!chromeEventHandler)
return NS_ERROR_UNEXPECTED; // assert
nsCOMPtr<nsIDOMEventGroup> systemGroup;
chromeEventHandler->GetSystemEventGroup(getter_AddRefs(systemGroup));
nsCOMPtr<nsIDOM3EventTarget> target(do_QueryInterface(chromeEventHandler));
if (!target || !systemGroup)
return NS_ERROR_FAILURE;
NS_ADDREF(*aEventTarget = target);
NS_ADDREF(*aSystemGroup = systemGroup);
return NS_OK;
}
void getContentFromFrame(nsIContent* c, nsIContent** outContent)
{
*outContent = nsnull;
nsCOMPtr<nsIContent> result;
nsCOMPtr<nsIDOMDocument> contentDocument;
nsCOMPtr<nsIDOMHTMLFrameElement> domFrameElement = do_QueryInterface(c);
if (domFrameElement) {
domFrameElement->GetContentDocument(getter_AddRefs(contentDocument));
}
else {
nsCOMPtr<nsIDOMHTMLIFrameElement> domIFrameElement = do_QueryInterface(c);
if (domIFrameElement)
domIFrameElement->GetContentDocument(getter_AddRefs(contentDocument));
}
if (contentDocument) {
nsCOMPtr<nsIDOMElement> documentElement;
contentDocument->GetDocumentElement(getter_AddRefs(documentElement));
result = do_QueryInterface(documentElement);
}
NS_IF_ADDREF(*outContent = result);
}
nsresult getFrameForContent(nsIContent* aContent, nsIFrame** aFrame)
{
*aFrame = nsnull;
if (!aContent)
return NS_ERROR_FAILURE;
nsIDocument* doc = aContent->GetDocument();
if (!doc)
return NS_ERROR_FAILURE;
nsIPresShell *presShell = doc->GetPrimaryShell();
nsIFrame* frame = presShell->GetPrimaryFrameFor(aContent);
if (!frame)
return NS_ERROR_FAILURE;
*aFrame = frame;
return NS_OK;
}
PRBool
isContentOfType(nsIContent* content, const char* type)
{
if (!content)
return PR_FALSE;
if (content->IsNodeOfType(nsINode::eELEMENT))
{
nsIAtom* atom = content->NodeInfo()->NameAtom();
if (atom)
return atom->EqualsUTF8(nsDependentCString(type));
}
return PR_FALSE;
}
PRBool
isArea(nsIContent* content)
{
if (!content || !content->IsNodeOfType(nsINode::eHTML))
return PR_FALSE;
return isContentOfType(content, "area");
}
PRBool
isMap(nsIFrame* frame)
{
nsIContent* content = frame->GetContent();
if (!content || !content->IsNodeOfType(nsINode::eHTML))
return PR_FALSE;
return isContentOfType(content, "map");
}
PRBool
isTargetable(PRBool focusDocuments, nsIFrame* frame)
{
nsIContent* currentContent = frame->GetContent();
if (!currentContent)
return PR_FALSE;
if (!currentContent->IsNodeOfType(nsINode::eHTML))
return PR_FALSE;
if (isContentOfType(currentContent, "map"))
return PR_TRUE;
if (isContentOfType(currentContent, "button"))
return PR_TRUE;
if (isContentOfType(currentContent, "a"))
{
// an anchor isn't targetable unless it has a non-null href.
nsCOMPtr<nsIDOMHTMLAnchorElement> anchorElement = do_QueryInterface(currentContent);
nsAutoString uri;
anchorElement->GetHref(uri);
if (uri.IsEmpty()) {
return PR_FALSE;
}
return PR_TRUE;
}
nsCOMPtr<nsIFrameFrame> frameFrame(do_QueryInterface(frame));
if (frameFrame)
return PR_TRUE;
nsCOMPtr<nsIDOMHTMLIFrameElement> iFrameElement = do_QueryInterface(currentContent);
if (iFrameElement)
return PR_TRUE;
if (focusDocuments) {
nsCOMPtr<nsIDOMHTMLHtmlElement> hhElement(do_QueryInterface(currentContent));
if (hhElement)
return PR_TRUE;
}
// need to figure out how to determine if a element is
// either disabled, hidden, or it is inaccessible due to
// its parent being one of these.
PRBool disabled = PR_FALSE;
nsCOMPtr<nsIDOMHTMLSelectElement> selectElement = do_QueryInterface(currentContent);
if (selectElement && NS_SUCCEEDED(selectElement->GetDisabled(&disabled)))
return !disabled;
nsCOMPtr<nsIDOMHTMLOptionElement> optionElement = do_QueryInterface(currentContent);
if (optionElement && NS_SUCCEEDED(optionElement->GetDisabled(&disabled)))
return !disabled;
nsAutoString inputType;
nsCOMPtr<nsIDOMHTMLInputElement> inputElement = do_QueryInterface(currentContent);
if (inputElement && NS_SUCCEEDED(inputElement->GetDisabled(&disabled)) && NS_SUCCEEDED(inputElement->GetType(inputType)))
return !disabled && (! inputType.EqualsIgnoreCase("hidden"));
return PR_FALSE;
}
nsRect makeRectRelativeToGlobalView(nsIFrame *aFrame)
{
nsRect result;
result.SetRect(0,0,0,0);
nsPoint offset;
if (!aFrame)
return result;
result = aFrame->GetRect();
nsIView* view;
aFrame->GetOffsetFromView(offset, &view);
nsIView* rootView = nsnull;
if (view) {
nsIViewManager* viewManager = view->GetViewManager();
NS_ASSERTION(viewManager, "View must have a viewmanager");
viewManager->GetRootView(rootView);
}
while (view) {
offset += view->GetPosition();
if (view == rootView) {
break;
}
view = view->GetParent();
}
result.MoveTo(offset);
return result;
}
void poly2Rect(int sides, nscoord* coord, nsRect* rect)
{
nscoord x1, x2, y1, y2, xtmp, ytmp;
x1 = x2 = coord[0];
y1 = y2 = coord[1];
for (PRInt32 i = 2; i < sides; i += 2)
{
xtmp = coord[i];
ytmp = coord[i+1];
x1 = x1 < xtmp ? x1 : xtmp;
y1 = y1 < ytmp ? y1 : ytmp;
x2 = x2 > xtmp ? x2 : xtmp;
y2 = y2 > ytmp ? y2 : ytmp;
}
rect->SetRect(x1, y1, x2, y2);
}
void getRectOfAreaElement(nsIFrame* f, nsIDOMHTMLAreaElement* e, nsRect* r)
{
if (!f || !e || !r)
return;
// RECT !!
nsRect frameRect = makeRectRelativeToGlobalView(f);
if (frameRect.width == 0)
frameRect.width = 1;
if (frameRect.height == 0)
frameRect.height = 1;
nsPoint offset;
offset.x = frameRect.x;
offset.y = frameRect.y;
nsAutoString coordstr;
e->GetCoords(coordstr);
NS_ConvertUTF16toUTF8 cp (coordstr);
PRInt32 count;
nscoord* coords = lo_parse_coord_list((char*) cp.get(), &count);
// FIX what about other shapes?
if (count == 4)
{
// x y width height
frameRect.SetRect(coords[0], coords[1], coords[2] - coords[0], coords[3] - coords[1]);
frameRect.x += offset.x;
frameRect.y += offset.y;
}
else if (count >=6)
poly2Rect(count, coords, &frameRect);
#ifdef DEBUG_dougt
else
printf("area count not supported!! %d\n", count);
#endif
// find the center of the rect.
frameRect.x = frameRect.x + (frameRect.width / 2) - 1;
frameRect.width = 2;
frameRect.y = frameRect.y + (frameRect.height / 2) - 1;
frameRect.height = 2;
*r = frameRect;
}
// we want to determine if going in the |direction|
// direction from focusedRect will intercept the frameRect
PRBool isRectInDirection(int direction, nsRect& focusedRect, nsRect& frameRect)
{
if (direction == eNavLeft)
{
return (frameRect.x < focusedRect.x);
}
if (direction == eNavRight)
{
return (frameRect.x + frameRect.width > focusedRect.x + focusedRect.width);
}
if (direction == eNavUp)
{
return (frameRect.y < focusedRect.y);
}
if (direction == eNavDown)
{
return (frameRect.y + frameRect.height > focusedRect.y + focusedRect.height);
}
return PR_FALSE;
}
PRInt64 spatialDistance(int direction, nsRect& a, nsRect& b)
{
PRBool inlineNavigation = PR_FALSE;
nsPoint m, n;
if (direction == eNavLeft)
{
// |---|
// |---|
//
// |---| |---|
// |---| |---|
//
// |---|
// |---|
//
if (a.y > b.y + b.height)
{
// the b rect is above a.
m.x = a.x;
m.y = a.y;
n.x = b.x + b.width;
n.y = b.y + b.height;
}
else if (a.y + a.height < b.y)
{
// the b rect is below a.
m.x = a.x;
m.y = a.y + a.height;
n.x = b.x + b.width;
n.y = b.y;
}
else
{
m.x = a.x;
m.y = 0;
n.x = b.x + b.width;
n.y = 0;
// m.x = (a.x + (a.width / 2));
// m.y = (a.y + (a.height / 2));
// n.x = (b.x + (b.width / 2));
// n.y = (b.y + (b.height / 2));
}
}
else if (direction == eNavRight)
{
// |---|
// |---|
//
// |---| |---|
// |---| |---|
//
// |---|
// |---|
//
if (a.y > b.y + b.height)
{
// the b rect is above a.
m.x = a.x + a.width;
m.y = a.y;
n.x = b.x;
n.y = b.y + b.height;
}
else if (a.y + a.height < b.y)
{
// the b rect is below a.
m.x = a.x + a.width;
m.y = a.y + a.height;
n.x = b.x;
n.y = b.y;
}
else
{
m.x = a.x + a.width;
m.y = 0;
n.x = b.x;
n.y = 0;
// m.x = (a.x + (a.width / 2));
// m.y = (a.y + (a.height / 2));
// n.x = (b.x + (b.width / 2));
// n.y = (b.y + (b.height / 2));
}
}
else if (direction == eNavUp)
{
// |---| |---| |---|
// |---| |---| |---|
//
// |---|
// |---|
//
if (a.x > b.x + b.width)
{
// the b rect is to the left of a.
m.x = a.x;
m.y = a.y;
n.x = b.x + b.width;
n.y = b.y + b.height;
}
else if (a.x + a.width < b.x)
{
// the b rect is to the right of a
m.x = a.x + a.width;
m.y = a.y;
n.x = b.x;
n.y = b.y + b.height;
}
else
{
// both b and a share some common x's.
m.x = 0;
m.y = a.y;
n.x = 0;
n.y = b.y + b.height;
// m.x = (a.x + (a.width / 2));
// m.y = (a.y + (a.height / 2));
// n.x = (b.x + (b.width / 2));
// n.y = (b.y + (b.height / 2));
}
}
else if (direction == eNavDown)
{
// |---|
// |---|
//
// |---| |---| |---|
// |---| |---| |---|
//
if (a.x > b.x + b.width)
{
// the b rect is to the left of a.
m.x = a.x;
m.y = a.y + a.height;
n.x = b.x + b.width;
n.y = b.y;
}
else if (a.x + a.width < b.x)
{
// the b rect is to the right of a
m.x = a.x + a.width;
m.y = a.y + a.height;
n.x = b.x;
n.y = b.y;
}
else
{
// both b and a share some common x's.
m.x = 0;
m.y = a.y + a.height;
n.x = 0;
n.y = b.y;
// m.x = (a.x + (a.width / 2));
// m.y = (a.y + (a.height / 2));
// n.x = (b.x + (b.width / 2));
// n.y = (b.y + (b.height / 2));
}
}
// a is always the currently focused rect.
nsRect scopedRect = a;
scopedRect.Inflate(gRectFudge, gRectFudge);
if (direction == eNavLeft)
{
scopedRect.x = 0;
scopedRect.width = nscoord_MAX;
if (scopedRect.Intersects(b))
inlineNavigation = PR_TRUE;
}
else if (direction == eNavRight)
{
scopedRect.width = nscoord_MAX;
if (scopedRect.Intersects(b))
inlineNavigation = PR_TRUE;
}
else if (direction == eNavUp)
{
scopedRect.y = 0;
scopedRect.height = nscoord_MAX;
if (scopedRect.Intersects(b))
inlineNavigation = PR_TRUE;
}
else if (direction == eNavDown)
{
scopedRect.height = nscoord_MAX;
if (scopedRect.Intersects(b))
inlineNavigation = PR_TRUE;
}
PRInt64 d = ((m.x-n.x)*(m.x-n.x)) + ((m.y-n.y)*(m.y-n.y));
if(d<0)
d=d*(-1);
if (inlineNavigation)
d /= gDirectionalBias;
return d;
}
void GetWindowFromDocument(nsIDOMDocument* aDocument, nsIDOMWindowInternal** aWindow)
{
nsCOMPtr<nsIDOMDocumentView> docview = do_QueryInterface(aDocument);
nsCOMPtr<nsIDOMAbstractView> view;
docview->GetDefaultView(getter_AddRefs(view));
if (!view) return;
nsCOMPtr<nsIDOMWindowInternal> window = do_QueryInterface(view);
NS_IF_ADDREF(*aWindow = window);
}
PRBool IsPartiallyVisible(nsIPresShell* shell, nsIFrame* frame)
{
// We need to know if at least a kMinPixels around the object is visible
// Otherwise it will be marked STATE_OFFSCREEN and STATE_INVISIBLE
const PRUint16 kMinPixels = 12;
// Set up the variables we need, return false if we can't get at them all
nsIViewManager* viewManager = shell->GetViewManager();
if (!viewManager)
return PR_FALSE;
// If visibility:hidden or visibility:collapsed then mark with STATE_INVISIBLE
if (!frame->GetStyleVisibility()->IsVisible())
{
return PR_FALSE;
}
nsPresContext *presContext = shell->GetPresContext();
if (!presContext)
return PR_FALSE;
// Get the bounds of the current frame, relative to the current view.
// We don't use the more accurate GetBoundsRect, because that is more expensive
// and the STATE_OFFSCREEN flag that this is used for only needs to be a rough indicator
nsRect relFrameRect = frame->GetRect();
nsPoint frameOffset;
nsIView *containingView = frame->GetViewExternal();
if (!containingView) {
frame->GetOffsetFromView(frameOffset, &containingView);
if (!containingView)
return PR_FALSE; // no view -- not visible
relFrameRect.x = frameOffset.x;
relFrameRect.y = frameOffset.y;
}
PRUint16 minPixels = PRUint16(nsPresContext::CSSPixelsToAppUnits(kMinPixels));
nsRectVisibility rectVisibility;
viewManager->GetRectVisibility(containingView, relFrameRect,
minPixels,
&rectVisibility);
if (rectVisibility == nsRectVisibility_kVisible ||
(rectVisibility == nsRectVisibility_kZeroAreaRect && frame->GetNextInFlow())) {
// This view says it is visible, but we need to check the parent view chain :(
// Note: zero area rects can occur in the first frame of a multi-frame text flow,
// in which case the next frame exists because the text flow is visible
while ((containingView = containingView->GetParent()) != nsnull) {
if (containingView->GetVisibility() == nsViewVisibility_kHide) {
return PR_FALSE;
}
}
return PR_TRUE;
}
return PR_FALSE;
}
const static PRInt32 gScrollOffset = (26*3);
void ScrollWindow(int direction, nsIDOMWindow* contentWindow)
{
if (!contentWindow)
return;
if (direction == eNavLeft)
contentWindow->ScrollBy(-1* gScrollOffset, 0);
else if (direction == eNavRight)
contentWindow->ScrollBy(gScrollOffset, 0);
else if (direction == eNavUp)
contentWindow->ScrollBy(0, -1 * gScrollOffset);
else
contentWindow->ScrollBy(0, gScrollOffset);
}