/* * 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(parent->hasFrame(this)); child->clearCursor(); } mIsCursor = false; } bool CachedNode::Clip(const WebCore::IntRect& outer, WebCore::IntRect* inner, WTF::Vector* 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* 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 = mNavableRects; ASSERT(partMax > 0); WebCore::IntRect bounds = mCursorRing[0]; for (int partIndex = 1; 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 (mNavableRects <= 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(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* 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 = mNavableRects; int innerMax = other->mNavableRects; 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* 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 mNavableRects=%d;\n", b->mNavableRects); 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 }