blob: e5008753c1800ec4407690279dec69d8ce343a8a [file] [log] [blame]
/*
* Copyright 2007, The Android Open Source Project
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "CachedPrefix.h"
#include "android_graphics.h"
#include "CachedFrame.h"
#include "CachedHistory.h"
#include "Node.h"
#include "PlatformString.h"
#include "CachedNode.h"
namespace android {
WebCore::IntRect CachedNode::bounds(const CachedFrame* frame) const
{
return mIsInLayer ? frame->adjustBounds(this, mBounds) : mBounds;
}
void CachedNode::clearCursor(CachedFrame* parent)
{
if (isFrame()) {
CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this));
child->clearCursor();
}
mIsCursor = false;
}
bool CachedNode::Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner,
WTF::Vector<WebCore::IntRect>* rings)
{
if (outer.contains(*inner))
return true;
// DBG_NAV_LOGD("outer:{%d,%d,%d,%d} does not contain inner:{%d,%d,%d,%d}",
// outer.x(), outer.y(), outer.width(), outer.height(),
// inner->x(), inner->y(), inner->width(), inner->height());
bool intersects = outer.intersects(*inner);
size_t size = intersects ? rings->size() : 0;
*inner = WebCore::IntRect(0, 0, 0, 0);
if (intersects) {
WebCore::IntRect * const start = rings->begin();
WebCore::IntRect* ring = start + size - 1;
do {
ring->intersect(outer);
if (ring->isEmpty()) {
if ((size_t) (ring - start) != --size)
*ring = start[size];
} else
inner->unite(*ring);
} while (ring-- != start);
}
rings->shrink(size);
// DBG_NAV_LOGD("size:%d", size);
return size != 0;
}
bool CachedNode::clip(const WebCore::IntRect& bounds)
{
return Clip(bounds, &mBounds, &mCursorRing);
}
void CachedNode::cursorRings(const CachedFrame* frame,
WTF::Vector<WebCore::IntRect>* rings) const
{
rings->clear();
for (unsigned index = 0; index < mCursorRing.size(); index++)
rings->append(ring(frame, index));
}
WebCore::IntRect CachedNode::cursorRingBounds(const CachedFrame* frame) const
{
int partMax = navableRects();
WebCore::IntRect bounds;
for (int partIndex = 0; partIndex < partMax; partIndex++)
bounds.unite(mCursorRing[partIndex]);
bounds.inflate(CURSOR_RING_HIT_TEST_RADIUS);
return mIsInLayer ? frame->adjustBounds(this, bounds) : bounds;
}
#define OVERLAP 3
void CachedNode::fixUpCursorRects(const CachedFrame* frame)
{
if (mFixedUpCursorRects)
return;
mFixedUpCursorRects = true;
// if the hit-test rect doesn't intersect any other rect, use it
if (mHitBounds != mBounds && mHitBounds.contains(mBounds) &&
frame->checkRings(this, mHitBounds)) {
DBG_NAV_LOGD("use mHitBounds (%d,%d,%d,%d)", mHitBounds.x(),
mHitBounds.y(), mHitBounds.width(), mHitBounds.height());
mUseHitBounds = true;
return;
}
if (navableRects() <= 1)
return;
// if there is more than 1 rect, and the bounds doesn't intersect
// any other cursor ring bounds, use it
IntRect sloppyBounds = mBounds;
sloppyBounds.inflate(2); // give it a couple of extra pixels
if (frame->checkRings(this, sloppyBounds)) {
DBG_NAV_LOGD("use mBounds (%d,%d,%d,%d)", mBounds.x(),
mBounds.y(), mBounds.width(), mBounds.height());
mUseBounds = true;
return;
}
#if DEBUG_NAV_UI
{
WebCore::IntRect* boundsPtr = mCursorRing.begin() - 1;
const WebCore::IntRect* const boundsEnd = mCursorRing.begin() + mCursorRing.size();
while (++boundsPtr < boundsEnd)
LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, boundsPtr - mCursorRing.begin(),
boundsPtr->x(), boundsPtr->y(), boundsPtr->width(), boundsPtr->height());
}
#endif
// q: need to know when rects are for drawing and hit-testing, but not mouse down calcs?
bool again;
do {
again = false;
size_t size = mCursorRing.size();
WebCore::IntRect* unitBoundsPtr = mCursorRing.begin() - 1;
const WebCore::IntRect* const unitBoundsEnd = mCursorRing.begin() + size;
while (++unitBoundsPtr < unitBoundsEnd) {
// any other unitBounds to the left or right of this one?
int unitTop = unitBoundsPtr->y();
int unitBottom = unitBoundsPtr->maxY();
int unitLeft = unitBoundsPtr->x();
int unitRight = unitBoundsPtr->maxX();
WebCore::IntRect* testBoundsPtr = mCursorRing.begin() - 1;
while (++testBoundsPtr < unitBoundsEnd) {
if (unitBoundsPtr == testBoundsPtr)
continue;
int testTop = testBoundsPtr->y();
int testBottom = testBoundsPtr->maxY();
int testLeft = testBoundsPtr->x();
int testRight = testBoundsPtr->maxX();
int candidateTop = unitTop > testTop ? unitTop : testTop;
int candidateBottom = unitBottom < testBottom ? unitBottom : testBottom;
int candidateLeft = unitRight < testLeft ? unitRight : testRight;
int candidateRight = unitRight > testLeft ? unitLeft : testLeft;
bool leftRight = true;
if (candidateTop + OVERLAP >= candidateBottom ||
candidateLeft + OVERLAP >= candidateRight) {
candidateTop = unitBottom < testTop ? unitBottom : testBottom;
candidateBottom = unitBottom > testTop ? unitTop : testTop;
candidateLeft = unitLeft > testLeft ? unitLeft : testLeft;
candidateRight = unitRight < testRight ? unitRight : testRight;
if (candidateTop + OVERLAP >= candidateBottom ||
candidateLeft + OVERLAP >= candidateRight)
continue;
leftRight = false;
}
// construct candidate to add
WebCore::IntRect candidate = WebCore::IntRect(candidateLeft, candidateTop,
candidateRight - candidateLeft, candidateBottom - candidateTop);
// does a different unit bounds intersect the candidate? if so, don't add
WebCore::IntRect* checkBoundsPtr = mCursorRing.begin() - 1;
while (++checkBoundsPtr < unitBoundsEnd) {
if (checkBoundsPtr->intersects(candidate) == false)
continue;
if (leftRight) {
if (candidateTop >= checkBoundsPtr->y() &&
candidateBottom > checkBoundsPtr->maxY())
candidateTop = checkBoundsPtr->maxY();
else if (candidateTop < checkBoundsPtr->y() &&
candidateBottom <= checkBoundsPtr->maxY())
candidateBottom = checkBoundsPtr->y();
else
goto nextCheck;
} else {
if (candidateLeft >= checkBoundsPtr->x() &&
candidateRight > checkBoundsPtr->maxX())
candidateLeft = checkBoundsPtr->maxX();
else if (candidateLeft < checkBoundsPtr->x() &&
candidateRight <= checkBoundsPtr->maxX())
candidateRight = checkBoundsPtr->x();
else
goto nextCheck;
}
}
candidate = WebCore::IntRect(candidateLeft, candidateTop,
candidateRight - candidateLeft, candidateBottom - candidateTop);
ASSERT(candidate.isEmpty() == false);
#if DEBUG_NAV_UI
LOGD("%s %d:(%d, %d, %d, %d)\n", __FUNCTION__, mCursorRing.size(),
candidate.x(), candidate.y(), candidate.width(), candidate.height());
#endif
mCursorRing.append(candidate);
again = true;
goto tryAgain;
nextCheck:
continue;
}
}
tryAgain:
;
} while (again);
}
void CachedNode::hideCursor(CachedFrame* parent)
{
if (isFrame()) {
CachedFrame* child = const_cast<CachedFrame*>(parent->hasFrame(this));
child->hideCursor();
}
mIsHidden = true;
}
WebCore::IntRect CachedNode::hitBounds(const CachedFrame* frame) const
{
return mIsInLayer ? frame->adjustBounds(this, mHitBounds) : mHitBounds;
}
void CachedNode::init(WebCore::Node* node)
{
bzero(this, sizeof(CachedNode));
mExport = WTF::String();
mNode = node;
mParentIndex = mDataIndex = -1;
mType = android::NORMAL_CACHEDNODETYPE;
}
bool CachedNode::isTextField(const CachedFrame* frame) const
{
const CachedInput* input = frame->textInput(this);
return input ? input->isTextField() : false;
}
void CachedNode::localCursorRings(const CachedFrame* frame,
WTF::Vector<WebCore::IntRect>* rings) const
{
rings->clear();
for (unsigned index = 0; index < mCursorRing.size(); index++)
rings->append(localRing(frame, index));
}
WebCore::IntRect CachedNode::localBounds(const CachedFrame* frame) const
{
return mIsInLayer ? frame->localBounds(this, mBounds) : mBounds;
}
WebCore::IntRect CachedNode::localHitBounds(const CachedFrame* frame) const
{
return mIsInLayer ? frame->localBounds(this, mHitBounds) : mHitBounds;
}
WebCore::IntRect CachedNode::localRing(const CachedFrame* frame,
size_t part) const
{
const WebCore::IntRect& rect = mCursorRing.at(part);
return mIsInLayer ? frame->localBounds(this, rect) : rect;
}
void CachedNode::move(int x, int y)
{
mBounds.move(x, y);
// mHitTestBounds will be moved by caller
WebCore::IntRect* first = mCursorRing.begin();
WebCore::IntRect* last = first + mCursorRing.size();
--first;
while (++first != last)
first->move(x, y);
}
bool CachedNode::partRectsContains(const CachedNode* other) const
{
int outerIndex = 0;
int outerMax = navableRects();
int innerMax = other->navableRects();
do {
const WebCore::IntRect& outerBounds = mCursorRing[outerIndex];
int innerIndex = 0;
do {
const WebCore::IntRect& innerBounds = other->mCursorRing[innerIndex];
if (innerBounds.contains(outerBounds))
return true;
} while (++innerIndex < innerMax);
} while (++outerIndex < outerMax);
return false;
}
WebCore::IntRect CachedNode::ring(const CachedFrame* frame, size_t part) const
{
const WebCore::IntRect& rect = mCursorRing.at(part);
return mIsInLayer ? frame->adjustBounds(this, rect) : rect;
}
#if DUMP_NAV_CACHE
#define DEBUG_PRINT_BOOL(field) \
DUMP_NAV_LOGD("// bool " #field "=%s;\n", b->field ? "true" : "false")
#define DEBUG_PRINT_RECT(field) \
{ const WebCore::IntRect& r = b->field; \
DUMP_NAV_LOGD("// IntRect " #field "={%d, %d, %d, %d};\n", \
r.x(), r.y(), r.width(), r.height()); }
CachedNode* CachedNode::Debug::base() const {
CachedNode* nav = (CachedNode*) ((char*) this - OFFSETOF(CachedNode, mDebug));
return nav;
}
const char* CachedNode::Debug::condition(Condition t) const
{
switch (t) {
case NOT_REJECTED: return "NOT_REJECTED"; break;
case BUTTED_UP: return "BUTTED_UP"; break;
case CENTER_FURTHER: return "CENTER_FURTHER"; break;
case CLOSER: return "CLOSER"; break;
case CLOSER_IN_CURSOR: return "CLOSER_IN_CURSOR"; break;
case CLOSER_OVERLAP: return "CLOSER_OVERLAP"; break;
case CLOSER_TOP: return "CLOSER_TOP"; break;
case NAVABLE: return "NAVABLE"; break;
case FURTHER: return "FURTHER"; break;
case IN_UMBRA: return "IN_UMBRA"; break;
case IN_WORKING: return "IN_WORKING"; break;
case LEFTMOST: return "LEFTMOST"; break;
case OVERLAP_OR_EDGE_FURTHER: return "OVERLAP_OR_EDGE_FURTHER"; break;
case PREFERRED: return "PREFERRED"; break;
case ANCHOR_IN_ANCHOR: return "ANCHOR_IN_ANCHOR"; break;
case BEST_DIRECTION: return "BEST_DIRECTION"; break;
case CHILD: return "CHILD"; break;
case DISABLED: return "DISABLED"; break;
case HIGHER_TAB_INDEX: return "HIGHER_TAB_INDEX"; break;
case IN_CURSOR: return "IN_CURSOR"; break;
case IN_CURSOR_CHILDREN: return "IN_CURSOR_CHILDREN"; break;
case NOT_ENCLOSING_CURSOR: return "NOT_ENCLOSING_CURSOR"; break;
case NOT_CURSOR_NODE: return "NOT_CURSOR_NODE"; break;
case OUTSIDE_OF_BEST: return "OUTSIDE_OF_BEST"; break;
case OUTSIDE_OF_ORIGINAL: return "OUTSIDE_OF_ORIGINAL"; break;
default: return "???";
}
}
const char* CachedNode::Debug::type(android::CachedNodeType t) const
{
switch (t) {
case NORMAL_CACHEDNODETYPE: return "NORMAL"; break;
case ADDRESS_CACHEDNODETYPE: return "ADDRESS"; break;
case EMAIL_CACHEDNODETYPE: return "EMAIL"; break;
case PHONE_CACHEDNODETYPE: return "PHONE"; break;
case ANCHOR_CACHEDNODETYPE: return "ANCHOR"; break;
case AREA_CACHEDNODETYPE: return "AREA"; break;
case FRAME_CACHEDNODETYPE: return "FRAME"; break;
case PLUGIN_CACHEDNODETYPE: return "PLUGIN"; break;
case TEXT_INPUT_CACHEDNODETYPE: return "INPUT"; break;
case SELECT_CACHEDNODETYPE: return "SELECT"; break;
case CONTENT_EDITABLE_CACHEDNODETYPE: return "CONTENT_EDITABLE"; break;
default: return "???";
}
}
void CachedNode::Debug::print() const
{
CachedNode* b = base();
char scratch[256];
size_t index = snprintf(scratch, sizeof(scratch), "// char* mExport=\"");
const UChar* ch = b->mExport.characters();
while (ch && *ch && index < sizeof(scratch)) {
UChar c = *ch++;
if (c < ' ' || c >= 0x7f) c = ' ';
scratch[index++] = c;
}
DUMP_NAV_LOGD("%.*s\"\n", index, scratch);
DEBUG_PRINT_RECT(mBounds);
DEBUG_PRINT_RECT(mHitBounds);
DEBUG_PRINT_RECT(mOriginalAbsoluteBounds);
const WTF::Vector<WebCore::IntRect>* rects = &b->mCursorRing;
size_t size = rects->size();
DUMP_NAV_LOGD("// IntRect cursorRings={ // size=%d\n", size);
for (size_t i = 0; i < size; i++) {
const WebCore::IntRect& rect = (*rects)[i];
DUMP_NAV_LOGD(" // {%d, %d, %d, %d}, // %d\n", rect.x(), rect.y(),
rect.width(), rect.height(), i);
}
DUMP_NAV_LOGD("// };\n");
DUMP_NAV_LOGD("// void* mNode=%p; // (%d) \n", b->mNode, mNodeIndex);
DUMP_NAV_LOGD("// void* mParentGroup=%p; // (%d) \n", b->mParentGroup, mParentGroupIndex);
DUMP_NAV_LOGD("// int mDataIndex=%d;\n", b->mDataIndex);
DUMP_NAV_LOGD("// int mIndex=%d;\n", b->mIndex);
DUMP_NAV_LOGD("// int navableRects()=%d;\n", b->navableRects());
DUMP_NAV_LOGD("// int mParentIndex=%d;\n", b->mParentIndex);
DUMP_NAV_LOGD("// int mTabIndex=%d;\n", b->mTabIndex);
DUMP_NAV_LOGD("// int mColorIndex=%d;\n", b->mColorIndex);
DUMP_NAV_LOGD("// Condition mCondition=%s;\n", condition(b->mCondition));
DUMP_NAV_LOGD("// Type mType=%s;\n", type(b->mType));
DEBUG_PRINT_BOOL(mClippedOut);
DEBUG_PRINT_BOOL(mDisabled);
DEBUG_PRINT_BOOL(mFixedUpCursorRects);
DEBUG_PRINT_BOOL(mHasCursorRing);
DEBUG_PRINT_BOOL(mHasMouseOver);
DEBUG_PRINT_BOOL(mIsCursor);
DEBUG_PRINT_BOOL(mIsFocus);
DEBUG_PRINT_BOOL(mIsHidden);
DEBUG_PRINT_BOOL(mIsInLayer);
DEBUG_PRINT_BOOL(mIsParentAnchor);
DEBUG_PRINT_BOOL(mIsTransparent);
DEBUG_PRINT_BOOL(mIsUnclipped);
DEBUG_PRINT_BOOL(mLast);
DEBUG_PRINT_BOOL(mUseBounds);
DEBUG_PRINT_BOOL(mUseHitBounds);
DEBUG_PRINT_BOOL(mSingleImage);
}
#endif
}