| /* |
| * 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 APPLE COMPUTER, INC. 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 "CachedHistory.h" |
| #include "CachedNode.h" |
| #include "SkBitmap.h" |
| #include "SkBounder.h" |
| #include "SkCanvas.h" |
| #include "SkPixelRef.h" |
| #include "SkRegion.h" |
| |
| #include "CachedRoot.h" |
| |
| #ifdef DUMP_NAV_CACHE_USING_PRINTF |
| extern android::Mutex gWriteLogMutex; |
| #endif |
| |
| namespace android { |
| |
| class CommonCheck : public SkBounder { |
| public: |
| enum Type { |
| kNo_Type, |
| kDrawBitmap_Type, |
| kDrawGlyph_Type, |
| kDrawPaint_Type, |
| kDrawPath_Type, |
| kDrawPicture_Type, |
| kDrawPoints_Type, |
| kDrawPosText_Type, |
| kDrawPosTextH_Type, |
| kDrawRect_Type, |
| kDrawSprite_Type, |
| kDrawText_Type, |
| kDrawTextOnPath_Type |
| }; |
| |
| static bool isTextType(Type t) { |
| return t == kDrawPosTextH_Type || t == kDrawText_Type; |
| } |
| |
| CommonCheck() : mType(kNo_Type), mAllOpaque(true), mIsOpaque(true) { |
| setEmpty(); |
| } |
| |
| bool doRect(Type type) { |
| mType = type; |
| return doIRect(mUnion); |
| } |
| |
| bool joinGlyphs(const SkIRect& rect) { |
| bool isGlyph = mType == kDrawGlyph_Type; |
| if (isGlyph) |
| mUnion.join(rect); |
| return isGlyph; |
| } |
| |
| void setAllOpaque(bool opaque) { mAllOpaque = opaque; } |
| void setEmpty() { mUnion.setEmpty(); } |
| void setIsOpaque(bool opaque) { mIsOpaque = opaque; } |
| void setType(Type type) { mType = type; } |
| |
| Type mType; |
| SkIRect mUnion; |
| bool mAllOpaque; |
| bool mIsOpaque; |
| }; |
| |
| #if DEBUG_NAV_UI |
| static const char* TypeNames[] = { |
| "kNo_Type", |
| "kDrawBitmap_Type", |
| "kDrawGlyph_Type", |
| "kDrawPaint_Type", |
| "kDrawPath_Type", |
| "kDrawPicture_Type", |
| "kDrawPoints_Type", |
| "kDrawPosText_Type", |
| "kDrawPosTextH_Type", |
| "kDrawRect_Type", |
| "kDrawSprite_Type", |
| "kDrawText_Type", |
| "kDrawTextOnPath_Type" |
| }; |
| #endif |
| |
| #define kMargin 16 |
| #define kSlop 2 |
| |
| class BoundsCheck : public CommonCheck { |
| public: |
| BoundsCheck() { |
| mAllDrawnIn.setEmpty(); |
| mLastAll.setEmpty(); |
| mLastOver.setEmpty(); |
| } |
| |
| static int Area(SkIRect test) { |
| return test.width() * test.height(); |
| } |
| |
| void checkLast() { |
| if (mAllDrawnIn.isEmpty()) |
| return; |
| if (mLastAll.isEmpty() || Area(mLastAll) < Area(mAllDrawnIn)) { |
| mLastAll = mAllDrawnIn; |
| mDrawnOver.setEmpty(); |
| } |
| mAllDrawnIn.setEmpty(); |
| } |
| |
| bool hidden() { |
| return (mLastAll.isEmpty() && mLastOver.isEmpty()) || |
| mDrawnOver.contains(mBounds); |
| } |
| |
| virtual bool onIRect(const SkIRect& rect) { |
| if (joinGlyphs(rect)) |
| return false; |
| bool interestingType = mType == kDrawBitmap_Type || |
| mType == kDrawRect_Type || isTextType(mType); |
| if (SkIRect::Intersects(mBounds, rect) == false) { |
| DBG_NAV_LOGD("BoundsCheck (no intersect) rect={%d,%d,%d,%d}" |
| " mType=%s", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, |
| TypeNames[mType]); |
| if (interestingType) |
| checkLast(); |
| return false; |
| } |
| if (interestingType == false) |
| return false; |
| if (mBoundsSlop.contains(rect) || |
| (mBounds.fLeft == rect.fLeft && mBounds.fRight == rect.fRight && |
| mBounds.fTop >= rect.fTop && mBounds.fBottom <= rect.fBottom) || |
| (mBounds.fTop == rect.fTop && mBounds.fBottom == rect.fBottom && |
| mBounds.fLeft >= rect.fLeft && mBounds.fRight <= rect.fRight)) { |
| mDrawnOver.setEmpty(); |
| mAllDrawnIn.join(rect); |
| DBG_NAV_LOGD("BoundsCheck (contains) rect={%d,%d,%d,%d}" |
| " mAllDrawnIn={%d,%d,%d,%d} mType=%s", |
| rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, |
| mAllDrawnIn.fLeft, mAllDrawnIn.fTop, mAllDrawnIn.fRight, |
| mAllDrawnIn.fBottom, TypeNames[mType]); |
| } else { |
| checkLast(); |
| if (!isTextType(mType)) { |
| if ( |
| #if 0 |
| // should the opaqueness of the bitmap disallow its ability to draw over? |
| // not sure that this test is needed |
| (mType != kDrawBitmap_Type || |
| (mIsOpaque && mAllOpaque)) && |
| #endif |
| mLastAll.isEmpty() == false) |
| mDrawnOver.op(rect, SkRegion::kUnion_Op); |
| } else { |
| // FIXME |
| // sometimes the text is not drawn entirely inside the cursor area, even though |
| // it is the correct text. Until I figure out why, I allow text drawn at the |
| // end that is not covered up by something else to represent the link |
| // example that triggers this that should be figured out: |
| // http://cdn.labpixies.com/campaigns/blackjack/blackjack.html?lang=en&country=US&libs=assets/feature/core |
| // ( http://tinyurl.com/ywsyzb ) |
| mLastOver = rect; |
| } |
| #if DEBUG_NAV_UI |
| const SkIRect& drawnOver = mDrawnOver.getBounds(); |
| DBG_NAV_LOGD("(overlaps) rect={%d,%d,%d,%d}" |
| " mDrawnOver={%d,%d,%d,%d} mType=%s mIsOpaque=%s mAllOpaque=%s", |
| rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, |
| drawnOver.fLeft, drawnOver.fTop, drawnOver.fRight, drawnOver.fBottom, |
| TypeNames[mType], mIsOpaque ? "true" : "false", |
| mAllOpaque ? "true" : "false"); |
| #endif |
| } |
| return false; |
| } |
| |
| SkIRect mBounds; |
| SkIRect mBoundsSlop; |
| SkRegion mDrawnOver; |
| SkIRect mLastOver; |
| SkIRect mAllDrawnIn; |
| SkIRect mLastAll; |
| }; |
| |
| class BoundsCanvas : public SkCanvas { |
| public: |
| |
| BoundsCanvas(CommonCheck* bounder) : mBounder(*bounder) { |
| mTransparentLayer = 0; |
| setBounder(bounder); |
| } |
| |
| virtual ~BoundsCanvas() { |
| setBounder(NULL); |
| } |
| |
| virtual void drawPaint(const SkPaint& paint) { |
| mBounder.setType(CommonCheck::kDrawPaint_Type); |
| SkCanvas::drawPaint(paint); |
| } |
| |
| virtual void drawPoints(PointMode mode, size_t count, const SkPoint pts[], |
| const SkPaint& paint) { |
| mBounder.setType(CommonCheck::kDrawPoints_Type); |
| SkCanvas::drawPoints(mode, count, pts, paint); |
| } |
| |
| virtual void drawRect(const SkRect& rect, const SkPaint& paint) { |
| mBounder.setType(CommonCheck::kDrawRect_Type); |
| SkCanvas::drawRect(rect, paint); |
| } |
| |
| virtual void drawPath(const SkPath& path, const SkPaint& paint) { |
| mBounder.setType(CommonCheck::kDrawPath_Type); |
| SkCanvas::drawPath(path, paint); |
| } |
| |
| virtual void commonDrawBitmap(const SkBitmap& bitmap, |
| const SkMatrix& matrix, const SkPaint& paint) { |
| mBounder.setType(CommonCheck::kDrawBitmap_Type); |
| mBounder.setIsOpaque(bitmap.isOpaque()); |
| SkCanvas::commonDrawBitmap(bitmap, matrix, paint); |
| } |
| |
| virtual void drawSprite(const SkBitmap& bitmap, int left, int top, |
| const SkPaint* paint = NULL) { |
| mBounder.setType(CommonCheck::kDrawSprite_Type); |
| mBounder.setIsOpaque(bitmap.isOpaque()); |
| SkCanvas::drawSprite(bitmap, left, top, paint); |
| } |
| |
| virtual void drawText(const void* text, size_t byteLength, SkScalar x, |
| SkScalar y, const SkPaint& paint) { |
| mBounder.setEmpty(); |
| mBounder.setType(CommonCheck::kDrawGlyph_Type); |
| SkCanvas::drawText(text, byteLength, x, y, paint); |
| mBounder.doRect(CommonCheck::kDrawText_Type); |
| } |
| |
| virtual void drawPosText(const void* text, size_t byteLength, |
| const SkPoint pos[], const SkPaint& paint) { |
| mBounder.setEmpty(); |
| mBounder.setType(CommonCheck::kDrawGlyph_Type); |
| SkCanvas::drawPosText(text, byteLength, pos, paint); |
| mBounder.doRect(CommonCheck::kDrawPosText_Type); |
| } |
| |
| virtual void drawPosTextH(const void* text, size_t byteLength, |
| const SkScalar xpos[], SkScalar constY, |
| const SkPaint& paint) { |
| mBounder.setEmpty(); |
| mBounder.setType(CommonCheck::kDrawGlyph_Type); |
| SkCanvas::drawPosTextH(text, byteLength, xpos, constY, paint); |
| if (mBounder.mUnion.isEmpty()) |
| return; |
| SkPaint::FontMetrics metrics; |
| paint.getFontMetrics(&metrics); |
| SkPoint upDown[2] = { {xpos[0], constY + metrics.fAscent}, |
| {xpos[0], constY + metrics.fDescent} }; |
| const SkMatrix& matrix = getTotalMatrix(); |
| matrix.mapPoints(upDown, 2); |
| if (upDown[0].fX == upDown[1].fX) { |
| mBounder.mUnion.fTop = SkScalarFloor(upDown[0].fY); |
| mBounder.mUnion.fBottom = SkScalarFloor(upDown[1].fY); |
| } |
| mBounder.doRect(CommonCheck::kDrawPosTextH_Type); |
| } |
| |
| virtual void drawTextOnPath(const void* text, size_t byteLength, |
| const SkPath& path, const SkMatrix* matrix, |
| const SkPaint& paint) { |
| mBounder.setEmpty(); |
| mBounder.setType(CommonCheck::kDrawGlyph_Type); |
| SkCanvas::drawTextOnPath(text, byteLength, path, matrix, paint); |
| mBounder.doRect(CommonCheck::kDrawTextOnPath_Type); |
| } |
| |
| virtual void drawPicture(SkPicture& picture) { |
| mBounder.setType(CommonCheck::kDrawPicture_Type); |
| SkCanvas::drawPicture(picture); |
| } |
| |
| virtual int saveLayer(const SkRect* bounds, const SkPaint* paint, |
| SaveFlags flags) { |
| int depth = SkCanvas::saveLayer(bounds, paint, flags); |
| if (mTransparentLayer == 0 && paint && paint->getAlpha() < 255) { |
| mTransparentLayer = depth; |
| mBounder.setAllOpaque(false); |
| } |
| return depth; |
| } |
| |
| virtual void restore() { |
| int depth = getSaveCount(); |
| if (depth == mTransparentLayer) { |
| mTransparentLayer = 0; |
| mBounder.setAllOpaque(true); |
| } |
| SkCanvas::restore(); |
| } |
| |
| int mTransparentLayer; |
| CommonCheck& mBounder; |
| }; |
| |
| /* |
| LeftCheck examines the text in a picture, within a viewable rectangle, |
| and returns via left() the position of the left edge of the paragraph. |
| It first looks at the left edge of the test point, then looks above and below |
| it for more lines of text to determine the div's left edge. |
| */ |
| class LeftCheck : public CommonCheck { |
| public: |
| LeftCheck(int x, int y) : mX(x), mY(y), mHitLeft(INT_MAX), |
| mMostLeft(INT_MAX) { |
| mHit.set(x - (HIT_SLOP << 1), y - HIT_SLOP, x, y + HIT_SLOP); |
| mPartial.setEmpty(); |
| mBounds.setEmpty(); |
| mPartialType = kNo_Type; |
| } |
| |
| int left() { |
| if (isTextType(mType)) |
| doRect(); // process the final line of text |
| return mMostLeft != INT_MAX ? mMostLeft : mX >> 1; |
| } |
| |
| // FIXME: this is identical to CenterCheck::onIRect() |
| // refactor so that LeftCheck and CenterCheck inherit common functions |
| virtual bool onIRect(const SkIRect& rect) { |
| bool opaqueBitmap = mType == kDrawBitmap_Type && mIsOpaque; |
| if (opaqueBitmap && rect.contains(mX, mY)) { |
| mMostLeft = rect.fLeft; |
| return false; |
| } |
| if (joinGlyphs(rect)) // assembles glyphs into a text string |
| return false; |
| if (!isTextType(mType) && !opaqueBitmap) |
| return false; |
| /* Text on one line may be broken into several parts. Reassemble |
| the text into a rectangle before considering it. */ |
| if (rect.fTop < mPartial.fBottom |
| && rect.fBottom > mPartial.fTop |
| && mPartial.fRight + SLOP >= rect.fLeft |
| && (mPartialType != kDrawBitmap_Type |
| || mPartial.height() <= rect.height() + HIT_SLOP)) { |
| DBG_NAV_LOGD("LeftCheck join mPartial=(%d, %d, %d, %d)" |
| " rect=(%d, %d, %d, %d)", |
| mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, |
| rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); |
| mPartial.join(rect); |
| return false; |
| } |
| if (mPartial.isEmpty() == false) { |
| doRect(); // process the previous line of text |
| #if DEBUG_NAV_UI |
| if (mHitLeft == INT_MAX) |
| DBG_NAV_LOGD("LeftCheck disabled rect=(%d, %d, %d, %d)", |
| rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); |
| #endif |
| } |
| mPartial = rect; |
| mPartialType = mType; |
| return false; |
| } |
| |
| void doRect() |
| { |
| /* Record the outer bounds of the lines of text that intersect the |
| touch coordinates, given some slop */ |
| if (SkIRect::Intersects(mPartial, mHit)) { |
| if (mHitLeft > mPartial.fLeft) |
| mHitLeft = mPartial.fLeft; |
| DBG_NAV_LOGD("LeftCheck mHitLeft=%d", mHitLeft); |
| } else if (mHitLeft == INT_MAX) |
| return; // wait for intersect success |
| /* If text is too far away vertically, don't consider it */ |
| if (!mBounds.isEmpty() && (mPartial.fTop > mBounds.fBottom + SLOP |
| || mPartial.fBottom < mBounds.fTop - SLOP)) { |
| DBG_NAV_LOGD("LeftCheck stop mPartial=(%d, %d, %d, %d)" |
| " mBounds=(%d, %d, %d, %d)", |
| mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, |
| mBounds.fLeft, mBounds.fTop, mBounds.fRight, mBounds.fBottom); |
| mHitLeft = INT_MAX; // and disable future comparisons |
| return; |
| } |
| /* If the considered text is completely to the left or right of the |
| touch coordinates, skip it, turn off further detection */ |
| if (mPartial.fLeft > mX || mPartial.fRight < mX) { |
| DBG_NAV_LOGD("LeftCheck stop mX=%d mPartial=(%d, %d, %d, %d)", mX, |
| mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom); |
| mHitLeft = INT_MAX; |
| return; |
| } |
| /* record the smallest margins on the left and right */ |
| if (mMostLeft > mPartial.fLeft) { |
| DBG_NAV_LOGD("LeftCheck new mMostLeft=%d (old=%d)", mPartial.fLeft, |
| mMostLeft); |
| mMostLeft = mPartial.fLeft; |
| } |
| if (mBounds.isEmpty()) |
| mBounds = mPartial; |
| else if (mPartial.fBottom > mBounds.fBottom) { |
| DBG_NAV_LOGD("LeftCheck new bottom=%d (old=%d)", mPartial.fBottom, |
| mBounds.fBottom); |
| mBounds.fBottom = mPartial.fBottom; |
| } |
| } |
| |
| static const int HIT_SLOP = 5; // space between text parts and lines |
| static const int SLOP = 30; // space between text parts and lines |
| /* const */ SkIRect mHit; // sloppy hit rectangle |
| SkIRect mBounds; // reference bounds |
| SkIRect mPartial; // accumulated text bounds, per line |
| const int mX; // touch location |
| const int mY; |
| int mHitLeft; // touched text extremes |
| int mMostLeft; // paragraph extremes |
| Type mPartialType; |
| }; |
| |
| /* |
| CenterCheck examines the text in a picture, within a viewable rectangle, |
| and returns via center() the optimal amount to scroll in x to display the |
| paragraph of text. |
| |
| The caller of CenterCheck has configured (but not allocated) a bitmap |
| the height and three times the width of the view. The picture is drawn centered |
| in the bitmap, so text that would be revealed, if the view was scrolled up to |
| a view-width to the left or right, is considered. |
| */ |
| class CenterCheck : public CommonCheck { |
| public: |
| CenterCheck(int x, int y, int width) : mX(x), mY(y), |
| mHitLeft(x), mHitRight(x), mMostLeft(INT_MAX), mMostRight(-INT_MAX), |
| mViewLeft(width), mViewRight(width << 1) { |
| mHit.set(x - CENTER_SLOP, y - CENTER_SLOP, |
| x + CENTER_SLOP, y + CENTER_SLOP); |
| mPartial.setEmpty(); |
| } |
| |
| int center() { |
| doRect(); // process the final line of text |
| /* If the touch coordinates aren't near any text, return 0 */ |
| if (mHitLeft == mHitRight) { |
| DBG_NAV_LOGD("abort: mHitLeft=%d ==mHitRight", mHitLeft); |
| return 0; |
| } |
| int leftOver = mHitLeft - mViewLeft; |
| int rightOver = mHitRight - mViewRight; |
| int center; |
| /* If the touched text is too large to entirely fit on the screen, |
| center it. */ |
| if (leftOver < 0 && rightOver > 0) { |
| center = (leftOver + rightOver) >> 1; |
| DBG_NAV_LOGD("overlap: leftOver=%d rightOver=%d center=%d", |
| leftOver, rightOver, center); |
| return center; |
| } |
| center = (mMostLeft + mMostRight) >> 1; // the paragraph center |
| if (leftOver > 0 && rightOver >= 0) { // off to the right |
| if (center > mMostLeft) // move to center loses left-most text? |
| center = mMostLeft; |
| } else if (rightOver < 0 && leftOver <= 0) { // off to the left |
| if (center < mMostRight) // move to center loses right-most text? |
| center = mMostRight; |
| } else { |
| #ifdef DONT_CENTER_IF_ALREADY_VISIBLE |
| center = 0; // paragraph is already fully visible |
| #endif |
| } |
| DBG_NAV_LOGD("scroll: leftOver=%d rightOver=%d center=%d", |
| leftOver, rightOver, center); |
| return center; |
| } |
| |
| protected: |
| virtual bool onIRect(const SkIRect& rect) { |
| if (joinGlyphs(rect)) // assembles glyphs into a text string |
| return false; |
| if (!isTextType(mType)) |
| return false; |
| /* Text on one line may be broken into several parts. Reassemble |
| the text into a rectangle before considering it. */ |
| if (rect.fTop < mPartial.fBottom && rect.fBottom > |
| mPartial.fTop && mPartial.fRight + CENTER_SLOP >= rect.fLeft) { |
| DBG_NAV_LOGD("join mPartial=(%d, %d, %d, %d) rect=(%d, %d, %d, %d)", |
| mPartial.fLeft, mPartial.fTop, mPartial.fRight, mPartial.fBottom, |
| rect.fLeft, rect.fTop, rect.fRight, rect.fBottom); |
| mPartial.join(rect); |
| return false; |
| } |
| if (mPartial.isEmpty() == false) |
| doRect(); // process the previous line of text |
| mPartial = rect; |
| return false; |
| } |
| |
| void doRect() |
| { |
| /* Record the outer bounds of the lines of text that was 'hit' by the |
| touch coordinates, given some slop */ |
| if (SkIRect::Intersects(mPartial, mHit)) { |
| if (mHitLeft > mPartial.fLeft) |
| mHitLeft = mPartial.fLeft; |
| if (mHitRight < mPartial.fRight) |
| mHitRight = mPartial.fRight; |
| DBG_NAV_LOGD("mHitLeft=%d mHitRight=%d", mHitLeft, mHitRight); |
| } |
| /* If the considered text is completely to the left or right of the |
| touch coordinates, skip it */ |
| if (mPartial.fLeft > mX || mPartial.fRight < mX) |
| return; |
| int leftOver = mPartial.fLeft - mViewLeft; |
| int rightOver = mPartial.fRight - mViewRight; |
| /* If leftOver <= 0, the text starts off the screen. |
| If rightOver >= 0, the text ends off the screen. |
| */ |
| if (leftOver <= 0 && rightOver >= 0) // discard wider than screen |
| return; |
| #ifdef DONT_CENTER_IF_ALREADY_VISIBLE |
| if (leftOver > 0 && rightOver < 0) // discard already visible |
| return; |
| #endif |
| /* record the smallest margins on the left and right */ |
| if (mMostLeft > leftOver) |
| mMostLeft = leftOver; |
| if (mMostRight < rightOver) |
| mMostRight = rightOver; |
| DBG_NAV_LOGD("leftOver=%d rightOver=%d mMostLeft=%d mMostRight=%d", |
| leftOver, rightOver, mMostLeft, mMostRight); |
| } |
| |
| static const int CENTER_SLOP = 10; // space between text parts and lines |
| /* const */ SkIRect mHit; // sloppy hit rectangle |
| SkIRect mPartial; // accumulated text bounds, per line |
| const int mX; // touch location |
| const int mY; |
| int mHitLeft; // touched text extremes |
| int mHitRight; |
| int mMostLeft; // paragraph extremes |
| int mMostRight; |
| const int mViewLeft; // middle third of 3x-wide view |
| const int mViewRight; |
| }; |
| |
| class ImageCanvas : public SkCanvas { |
| public: |
| ImageCanvas(SkBounder* bounder) : mURI(NULL) { |
| setBounder(bounder); |
| } |
| |
| // Currently webkit's bitmap draws always seem to be cull'd before this entry |
| // point is called, so we assume that any bitmap that gets here is inside our |
| // tiny clip (may not be true in the future) |
| virtual void commonDrawBitmap(const SkBitmap& bitmap, |
| const SkMatrix& , const SkPaint& ) { |
| SkPixelRef* pixelRef = bitmap.pixelRef(); |
| if (pixelRef != NULL) { |
| mURI = pixelRef->getURI(); |
| } |
| } |
| |
| const char* mURI; |
| }; |
| |
| class ImageCheck : public SkBounder { |
| public: |
| virtual bool onIRect(const SkIRect& rect) { |
| return false; |
| } |
| }; |
| |
| class JiggleCheck : public CommonCheck { |
| public: |
| JiggleCheck(int delta, int width) : mDelta(delta), mMaxX(width) { |
| mMaxJiggle = 0; |
| mMinX = mMinJiggle = abs(delta); |
| mMaxWidth = width + mMinX; |
| } |
| |
| int jiggle() { |
| if (mMinJiggle > mMaxJiggle) |
| return mDelta; |
| int avg = (mMinJiggle + mMaxJiggle + 1) >> 1; |
| return mDelta < 0 ? -avg : avg; |
| } |
| |
| virtual bool onIRect(const SkIRect& rect) { |
| if (joinGlyphs(rect)) |
| return false; |
| if (mType != kDrawBitmap_Type && !isTextType(mType)) |
| return false; |
| int min, max; |
| if (mDelta < 0) { |
| min = mMinX - rect.fLeft; |
| max = mMaxWidth - rect.fRight; |
| } else { |
| min = rect.fRight - mMaxX; |
| max = rect.fLeft; |
| } |
| if (min <= 0) |
| return false; |
| if (max >= mMinX) |
| return false; |
| if (mMinJiggle > min) |
| mMinJiggle = min; |
| if (mMaxJiggle < max) |
| mMaxJiggle = max; |
| return false; |
| } |
| |
| int mDelta; |
| int mMaxJiggle; |
| int mMaxX; |
| int mMinJiggle; |
| int mMinX; |
| int mMaxWidth; |
| }; |
| |
| class RingCheck : public CommonCheck { |
| public: |
| RingCheck(const WTF::Vector<WebCore::IntRect>& rings, |
| const WebCore::IntPoint& location) : mSuccess(true) { |
| const WebCore::IntRect* r; |
| for (r = rings.begin(); r != rings.end(); r++) { |
| SkIRect fatter = {r->x(), r->y(), r->right(), r->bottom()}; |
| fatter.inset(-CURSOR_RING_HIT_TEST_RADIUS, -CURSOR_RING_HIT_TEST_RADIUS); |
| DBG_NAV_LOGD("fat=(%d,%d,r=%d,b=%d)", fatter.fLeft, fatter.fTop, |
| fatter.fRight, fatter.fBottom); |
| mRings.op(fatter, SkRegion::kUnion_Op); |
| } |
| DBG_NAV_LOGD("translate=(%d,%d)", -location.x(), -location.y()); |
| mRings.translate(-location.x(), -location.y()); |
| } |
| |
| virtual bool onIRect(const SkIRect& rect) { |
| if (mSuccess && mType == kDrawGlyph_Type) { |
| DBG_NAV_LOGD("contains (%d,%d,r=%d,b=%d) == %s", |
| rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, |
| mRings.contains(rect) ? "true" : "false"); |
| mSuccess &= mRings.contains(rect); |
| } |
| return false; |
| } |
| |
| bool success() { return mSuccess; } |
| SkRegion mRings; |
| bool mSuccess; |
| }; |
| |
| bool CachedRoot::adjustForScroll(BestData* best, CachedFrame::Direction direction, |
| WebCore::IntPoint* scrollPtr, bool findClosest) |
| { |
| WebCore::IntRect newOutset; |
| const CachedNode* newNode = best->mNode; |
| // see if there's a middle node |
| // if the middle node is in the visited list, |
| // or if none was computed and the newNode is in the visited list, |
| // treat result as NULL |
| if (newNode != NULL && findClosest) { |
| if (best->bounds().intersects(mHistory->mPriorBounds) == false && |
| checkBetween(best, direction)) |
| newNode = best->mNode; |
| if (findClosest && maskIfHidden(best)) { |
| innerMove(document(), best, direction, scrollPtr, false); |
| return true; |
| } |
| newNode->cursorRingBounds(&newOutset); |
| } |
| int delta; |
| bool newNodeInView = scrollDelta(newOutset, direction, &delta); |
| if (delta && scrollPtr && (newNode == NULL || newNodeInView == false || |
| (best->mNavOutside && best->mWorkingOutside))) |
| *scrollPtr = WebCore::IntPoint(direction & UP_DOWN ? 0 : delta, |
| direction & UP_DOWN ? delta : 0); |
| return false; |
| } |
| |
| |
| int CachedRoot::checkForCenter(int x, int y) const |
| { |
| int width = mViewBounds.width(); |
| CenterCheck centerCheck(x + width - mViewBounds.x(), y - mViewBounds.y(), |
| width); |
| BoundsCanvas checker(¢erCheck); |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, width * 3, |
| mViewBounds.height()); |
| checker.setBitmapDevice(bitmap); |
| checker.translate(SkIntToScalar(width - mViewBounds.x()), |
| SkIntToScalar(-mViewBounds.y())); |
| checker.drawPicture(*mPicture); |
| return centerCheck.center(); |
| } |
| |
| void CachedRoot::checkForJiggle(int* xDeltaPtr) const |
| { |
| int xDelta = *xDeltaPtr; |
| JiggleCheck jiggleCheck(xDelta, mViewBounds.width()); |
| BoundsCanvas checker(&jiggleCheck); |
| SkBitmap bitmap; |
| int absDelta = abs(xDelta); |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, mViewBounds.width() + |
| absDelta, mViewBounds.height()); |
| checker.setBitmapDevice(bitmap); |
| checker.translate(SkIntToScalar(-mViewBounds.x() - |
| (xDelta < 0 ? xDelta : 0)), SkIntToScalar(-mViewBounds.y())); |
| checker.drawPicture(*mPicture); |
| *xDeltaPtr = jiggleCheck.jiggle(); |
| } |
| |
| bool CachedRoot::checkRings(const WTF::Vector<WebCore::IntRect>& rings, |
| const WebCore::IntRect& bounds) const |
| { |
| if (!mPicture) |
| return false; |
| RingCheck ringCheck(rings, bounds.location()); |
| BoundsCanvas checker(&ringCheck); |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, bounds.width(), |
| bounds.height()); |
| checker.setBitmapDevice(bitmap); |
| checker.translate(SkIntToScalar(-bounds.x()), SkIntToScalar(-bounds.y())); |
| checker.drawPicture(*mPicture); |
| DBG_NAV_LOGD("bounds=(%d,%d,r=%d,b=%d) success=%s", |
| bounds.x(), bounds.y(), bounds.right(), bounds.bottom(), |
| ringCheck.success() ? "true" : "false"); |
| return ringCheck.success(); |
| } |
| |
| CachedRoot::ImeAction CachedRoot::cursorTextFieldAction() const |
| { |
| const CachedFrame* cursorFrame; |
| const CachedNode* cursor = currentCursor(&cursorFrame); |
| if (!cursor) { |
| // Error case. The cursor has no action, because there is no node under |
| // the cursor |
| return FAILURE; |
| } |
| const CachedNode* firstTextfield = nextTextField(0, 0, false); |
| if (!firstTextfield) { |
| // Error case. There are no textfields in this tree. |
| return FAILURE; |
| } |
| // Now find the next textfield/area starting with the cursor |
| if (cursorFrame->nextTextField(cursor, 0, true)) { |
| // There is a textfield/area after the cursor, so the textfield under |
| // the cursor should have the NEXT action |
| return NEXT; |
| } |
| // If this line is reached, we know that the textfield under the cursor is |
| // the last one. If it is also the first, then it is the only one, so make |
| // the action GO. Otherwise, the action is DONE. |
| return (firstTextfield == cursor) ? GO : DONE; |
| } |
| |
| const CachedNode* CachedRoot::findAt(const WebCore::IntRect& rect, |
| const CachedFrame** framePtr, int* x, int* y, bool checkForHidden) const |
| { |
| int best = INT_MAX; |
| bool inside = false; |
| (const_cast<CachedRoot*>(this))->resetClippedOut(); |
| const CachedNode* directHit = NULL; |
| const CachedNode* node = findBestAt(rect, &best, &inside, &directHit, |
| framePtr, x, y, checkForHidden); |
| DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), |
| node == NULL ? NULL : node->nodePointer()); |
| if (node == NULL) { |
| node = findBestHitAt(rect, &best, framePtr, x, y); |
| DBG_NAV_LOGD("node=%d (%p)", node == NULL ? 0 : node->index(), |
| node == NULL ? NULL : node->nodePointer()); |
| } |
| if (node == NULL) { |
| *framePtr = findBestFrameAt(rect.x() + (rect.width() >> 1), |
| rect.y() + (rect.height() >> 1)); |
| } |
| return node; |
| } |
| |
| WebCore::IntPoint CachedRoot::cursorLocation() const |
| { |
| const WebCore::IntRect& bounds = mHistory->mNavBounds; |
| return WebCore::IntPoint(bounds.x() + (bounds.width() >> 1), |
| bounds.y() + (bounds.height() >> 1)); |
| } |
| |
| WebCore::IntPoint CachedRoot::focusLocation() const |
| { |
| return WebCore::IntPoint(mFocusBounds.x() + (mFocusBounds.width() >> 1), |
| mFocusBounds.y() + (mFocusBounds.height() >> 1)); |
| } |
| |
| // These reset the values because we only want to get the selection the first time. |
| // After that, the selection is no longer accurate. |
| int CachedRoot::getAndResetSelectionEnd() |
| { |
| int end = mSelectionEnd; |
| mSelectionEnd = -1; |
| return end; |
| } |
| |
| int CachedRoot::getAndResetSelectionStart() |
| { |
| int start = mSelectionStart; |
| mSelectionStart = -1; |
| return start; |
| } |
| |
| int CachedRoot::getBlockLeftEdge(int x, int y, float scale) const |
| { |
| DBG_NAV_LOGD("x=%d y=%d scale=%g mViewBounds=(%d,%d,%d,%d)", x, y, scale, |
| mViewBounds.x(), mViewBounds.y(), mViewBounds.width(), |
| mViewBounds.height()); |
| // if (x, y) is in a textArea or textField, return that |
| const int slop = 1; |
| WebCore::IntRect rect = WebCore::IntRect(x - slop, y - slop, |
| slop * 2, slop * 2); |
| const CachedFrame* frame; |
| int fx, fy; |
| const CachedNode* node = findAt(rect, &frame, &fx, &fy, true); |
| if (node && (node->isTextArea() || node->isTextField() || node->isPlugin())) { |
| DBG_NAV_LOGD("x=%d (%s)", node->bounds().x(), |
| node->isTextArea() || node->isTextField() ? "text" : "plugin"); |
| return node->bounds().x(); |
| } |
| int halfW = (int) (mViewBounds.width() * scale * 0.5f); |
| int fullW = halfW << 1; |
| int halfH = (int) (mViewBounds.height() * scale * 0.5f); |
| int fullH = halfH << 1; |
| LeftCheck leftCheck(fullW, halfH); |
| BoundsCanvas checker(&leftCheck); |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, fullW, fullH); |
| checker.setBitmapDevice(bitmap); |
| checker.translate(SkIntToScalar(fullW - x), SkIntToScalar(halfH - y)); |
| checker.drawPicture(*mPicture); |
| int result = x + leftCheck.left() - fullW; |
| DBG_NAV_LOGD("halfW=%d halfH=%d mMostLeft=%d x=%d", |
| halfW, halfH, leftCheck.mMostLeft, result); |
| return result; |
| } |
| |
| void CachedRoot::getSimulatedMousePosition(WebCore::IntPoint* point) const |
| { |
| #ifndef NDEBUG |
| ASSERT(CachedFrame::mDebug.mInUse); |
| #endif |
| const WebCore::IntRect& mouseBounds = mHistory->mMouseBounds; |
| int x = mouseBounds.x(); |
| int y = mouseBounds.y(); |
| int width = mouseBounds.width(); |
| int height = mouseBounds.height(); |
| point->setX(x + (width >> 1)); // default to box center |
| point->setY(y + (height >> 1)); |
| const CachedNode* cursor = currentCursor(); |
| if (cursor && cursor->bounds().contains(mHistory->mMouseBounds)) { |
| if (cursor->isTextField()) // if text field, return end of line |
| point->setX(x + width - 1); |
| else if (cursor->isTextArea()) { // if text area, return start |
| point->setX(x + 1); |
| point->setY(y + 1); |
| } |
| } |
| #if DEBUG_NAV_UI |
| const WebCore::IntRect& navBounds = mHistory->mNavBounds; |
| DBG_NAV_LOGD("mHistory->mNavBounds={%d,%d,%d,%d} " |
| "mHistory->mMouseBounds={%d,%d,%d,%d} point={%d,%d}", |
| navBounds.x(), navBounds.y(), navBounds.width(), navBounds.height(), |
| mouseBounds.x(), mouseBounds.y(), mouseBounds.width(), |
| mouseBounds.height(), point->x(), point->y()); |
| #endif |
| } |
| |
| void CachedRoot::init(WebCore::Frame* frame, CachedHistory* history) |
| { |
| CachedFrame::init(this, -1, frame); |
| reset(); |
| mHistory = history; |
| mPicture = NULL; |
| } |
| |
| bool CachedRoot::innerDown(const CachedNode* test, BestData* bestData) const |
| { |
| ASSERT(minWorkingVertical() >= mViewBounds.x()); |
| ASSERT(maxWorkingVertical() <= mViewBounds.right()); |
| setupScrolledBounds(); |
| // (line up) |
| mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); |
| int testTop = mScrolledBounds.y(); |
| int viewBottom = mViewBounds.bottom(); |
| const WebCore::IntRect& navBounds = mHistory->mNavBounds; |
| if (navBounds.isEmpty() == false && |
| navBounds.bottom() > viewBottom && viewBottom < mContents.height()) |
| return false; |
| if (navBounds.isEmpty() == false) { |
| int navTop = navBounds.y(); |
| int scrollBottom; |
| if (testTop < navTop && navTop < (scrollBottom = mScrolledBounds.bottom())) { |
| mScrolledBounds.setHeight(scrollBottom - navTop); |
| mScrolledBounds.setY(navTop); |
| } |
| } |
| frameDown(test, NULL, bestData, currentCursor()); |
| return true; |
| } |
| |
| bool CachedRoot::innerLeft(const CachedNode* test, BestData* bestData) const |
| { |
| ASSERT(minWorkingHorizontal() >= mViewBounds.y()); |
| ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); |
| setupScrolledBounds(); |
| mScrolledBounds.setX(mScrolledBounds.x() - mMaxXScroll); |
| mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); |
| int testRight = mScrolledBounds.right(); |
| int viewLeft = mViewBounds.x(); |
| const WebCore::IntRect& navBounds = mHistory->mNavBounds; |
| if (navBounds.isEmpty() == false && |
| navBounds.x() < viewLeft && viewLeft > mContents.x()) |
| return false; |
| if (navBounds.isEmpty() == false) { |
| int navRight = navBounds.right(); |
| int scrollLeft; |
| if (testRight > navRight && navRight > (scrollLeft = mScrolledBounds.x())) |
| mScrolledBounds.setWidth(navRight - scrollLeft); |
| } |
| frameLeft(test, NULL, bestData, currentCursor()); |
| return true; |
| } |
| |
| |
| void CachedRoot::innerMove(const CachedNode* node, BestData* bestData, |
| Direction direction, WebCore::IntPoint* scroll, bool firstCall) |
| { |
| bestData->reset(); |
| bool outOfCursor = mCursorIndex == CURSOR_CLEARED; |
| DBG_NAV_LOGD("mHistory->didFirstLayout()=%s && mCursorIndex=%d", |
| mHistory->didFirstLayout() ? "true" : "false", mCursorIndex); |
| if (mHistory->didFirstLayout() && mCursorIndex < CURSOR_SET) { |
| mHistory->reset(); |
| outOfCursor = true; |
| } |
| const CachedNode* cursor = currentCursor(); |
| mHistory->setWorking(direction, cursor, mViewBounds); |
| bool findClosest = false; |
| if (mScrollOnly == false) { |
| switch (direction) { |
| case LEFT: |
| if (outOfCursor) |
| mHistory->mNavBounds = WebCore::IntRect(mViewBounds.right(), |
| mViewBounds.y(), 1, mViewBounds.height()); |
| findClosest = innerLeft(node, bestData); |
| break; |
| case RIGHT: |
| if (outOfCursor) |
| mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x() - 1, |
| mViewBounds.y(), 1, mViewBounds.height()); |
| findClosest = innerRight(node, bestData); |
| break; |
| case UP: |
| if (outOfCursor) |
| mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), |
| mViewBounds.bottom(), mViewBounds.width(), 1); |
| findClosest = innerUp(node, bestData); |
| break; |
| case DOWN: |
| if (outOfCursor) |
| mHistory->mNavBounds = WebCore::IntRect(mViewBounds.x(), |
| mViewBounds.y() - 1, mViewBounds.width(), 1); |
| findClosest = innerDown(node, bestData); |
| break; |
| case UNINITIALIZED: |
| default: |
| ASSERT(0); |
| } |
| } |
| if (firstCall) |
| mHistory->mPriorBounds = mHistory->mNavBounds; // bounds always advances, even if new node is ultimately NULL |
| bestData->mMouseBounds = bestData->mNodeBounds; |
| if (adjustForScroll(bestData, direction, scroll, findClosest)) |
| return; |
| if (bestData->mNode != NULL) { |
| mHistory->addToVisited(bestData->mNode, direction); |
| mHistory->mNavBounds = bestData->mNodeBounds; |
| mHistory->mMouseBounds = bestData->mMouseBounds; |
| } else if (scroll->x() != 0 || scroll->y() != 0) { |
| WebCore::IntRect newBounds = mHistory->mNavBounds; |
| int offsetX = scroll->x(); |
| int offsetY = scroll->y(); |
| newBounds.move(offsetX, offsetY); |
| if (mViewBounds.x() > newBounds.x()) |
| offsetX = mViewBounds.x() - mHistory->mNavBounds.x(); |
| else if (mViewBounds.right() < newBounds.right()) |
| offsetX = mViewBounds.right() - mHistory->mNavBounds.right(); |
| if (mViewBounds.y() > newBounds.y()) |
| offsetY = mViewBounds.y() - mHistory->mNavBounds.y(); |
| else if (mViewBounds.bottom() < newBounds.bottom()) |
| offsetY = mViewBounds.bottom() - mHistory->mNavBounds.bottom(); |
| mHistory->mNavBounds.move(offsetX, offsetY); |
| } |
| mHistory->setDidFirstLayout(false); |
| } |
| |
| bool CachedRoot::innerRight(const CachedNode* test, BestData* bestData) const |
| { |
| ASSERT(minWorkingHorizontal() >= mViewBounds.y()); |
| ASSERT(maxWorkingHorizontal() <= mViewBounds.bottom()); |
| setupScrolledBounds(); |
| // (align) |
| mScrolledBounds.setWidth(mScrolledBounds.width() + mMaxXScroll); |
| int testLeft = mScrolledBounds.x(); |
| int viewRight = mViewBounds.right(); |
| const WebCore::IntRect& navBounds = mHistory->mNavBounds; |
| if (navBounds.isEmpty() == false && |
| navBounds.right() > viewRight && viewRight < mContents.width()) |
| return false; |
| if (navBounds.isEmpty() == false) { |
| int navLeft = navBounds.x(); |
| int scrollRight; |
| if (testLeft < navLeft && navLeft < (scrollRight = mScrolledBounds.right())) { |
| mScrolledBounds.setWidth(scrollRight - navLeft); |
| mScrolledBounds.setX(navLeft); |
| } |
| } |
| frameRight(test, NULL, bestData, currentCursor()); |
| return true; |
| } |
| |
| bool CachedRoot::innerUp(const CachedNode* test, BestData* bestData) const |
| { |
| ASSERT(minWorkingVertical() >= mViewBounds.x()); |
| ASSERT(maxWorkingVertical() <= mViewBounds.right()); |
| setupScrolledBounds(); |
| mScrolledBounds.setY(mScrolledBounds.y() - mMaxYScroll); |
| mScrolledBounds.setHeight(mScrolledBounds.height() + mMaxYScroll); |
| int testBottom = mScrolledBounds.bottom(); |
| int viewTop = mViewBounds.y(); |
| const WebCore::IntRect& navBounds = mHistory->mNavBounds; |
| if (navBounds.isEmpty() == false && |
| navBounds.y() < viewTop && viewTop > mContents.y()) |
| return false; |
| if (navBounds.isEmpty() == false) { |
| int navBottom = navBounds.bottom(); |
| int scrollTop; |
| if (testBottom > navBottom && navBottom > (scrollTop = mScrolledBounds.y())) |
| mScrolledBounds.setHeight(navBottom - scrollTop); |
| } |
| frameUp(test, NULL, bestData, currentCursor()); |
| return true; |
| } |
| |
| WebCore::String CachedRoot::imageURI(int x, int y) const |
| { |
| ImageCheck imageCheck; |
| ImageCanvas checker(&imageCheck); |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, 1, 1); |
| checker.setBitmapDevice(bitmap); |
| checker.translate(SkIntToScalar(-x), SkIntToScalar(-y)); |
| checker.drawPicture(*mPicture); |
| return WebCore::String(checker.mURI); |
| } |
| |
| bool CachedRoot::maskIfHidden(BestData* best) const |
| { |
| if (mPicture == NULL) { |
| DBG_NAV_LOG("missing picture"); |
| return false; |
| } |
| const CachedNode* bestNode = best->mNode; |
| if (bestNode->isUnclipped()) |
| return false; |
| // given the picture matching this nav cache |
| // create an SkBitmap with dimensions of the cursor intersected w/ extended view |
| const WebCore::IntRect& nodeBounds = bestNode->getBounds(); |
| WebCore::IntRect bounds = nodeBounds; |
| bounds.intersect(mScrolledBounds); |
| int leftMargin = bounds.x() == nodeBounds.x() ? kMargin : 0; |
| int topMargin = bounds.y() == nodeBounds.y() ? kMargin : 0; |
| int rightMargin = bounds.right() == nodeBounds.right() ? kMargin : 0; |
| int bottomMargin = bounds.bottom() == nodeBounds.bottom() ? kMargin : 0; |
| bool unclipped = (leftMargin & topMargin & rightMargin & bottomMargin) != 0; |
| WebCore::IntRect marginBounds = nodeBounds; |
| marginBounds.inflate(kMargin); |
| marginBounds.intersect(mScrolledBounds); |
| BoundsCheck boundsCheck; |
| BoundsCanvas checker(&boundsCheck); |
| boundsCheck.mBounds.set(leftMargin, topMargin, |
| leftMargin + bounds.width(), topMargin + bounds.height()); |
| boundsCheck.mBoundsSlop = boundsCheck.mBounds; |
| boundsCheck.mBoundsSlop.inset(-kSlop, -kSlop); |
| SkBitmap bitmap; |
| bitmap.setConfig(SkBitmap::kARGB_8888_Config, marginBounds.width(), |
| marginBounds.height()); |
| checker.setBitmapDevice(bitmap); |
| // insert probes to be called when the data corresponding to this ring is drawn |
| // need to know if ring was generated by text, image, or parent (like div) |
| // ? need to know (like imdb menu bar) to give up sometimes (when?) |
| checker.translate(SkIntToScalar(leftMargin - bounds.x()), |
| SkIntToScalar(topMargin - bounds.y())); |
| checker.drawPicture(*mPicture); |
| boundsCheck.checkLast(); |
| // was it not drawn or clipped out? |
| if (boundsCheck.hidden()) { // if hidden, return false so that nav can try again |
| CachedNode* node = const_cast<CachedNode*>(best->mNode); |
| #if DEBUG_NAV_UI |
| const SkIRect& m = boundsCheck.mBounds; |
| const SkIRect& s = boundsCheck.mBoundsSlop; |
| DBG_NAV_LOGD("hidden node:%p (%d) mBounds={%d,%d,%d,%d} mBoundsSlop=" |
| "{%d,%d,%d,%d}", node, node->index(), |
| m.fLeft, m.fTop, m.fRight, m.fBottom, |
| s.fLeft, s.fTop, s.fRight, s.fBottom); |
| const SkIRect& o = boundsCheck.mDrawnOver.getBounds(); |
| const SkIRect& l = boundsCheck.mLastAll; |
| const SkIRect& u = boundsCheck.mUnion; |
| DBG_NAV_LOGD("hidden mDrawnOver={%d,%d,%d,%d} mLastAll={%d,%d,%d,%d}" |
| " mUnion={%d,%d,%d,%d}", |
| o.fLeft, o.fTop, o.fRight, o.fBottom, |
| l.fLeft, l.fTop, l.fRight, l.fBottom, |
| u.fLeft, u.fTop, u.fRight, u.fBottom); |
| const SkIRect& a = boundsCheck.mAllDrawnIn; |
| const WebCore::IntRect& c = mScrolledBounds; |
| const WebCore::IntRect& b = nodeBounds; |
| DBG_NAV_LOGD("hidden mAllDrawnIn={%d,%d,%d,%d}" |
| " mScrolledBounds={%d,%d,%d,%d} nodeBounds={%d,%d,%d,%d}", |
| a.fLeft, a.fTop, a.fRight, a.fBottom, |
| c.x(), c.y(), c.right(), c.bottom(), |
| b.x(), b.y(), b.right(), b.bottom()); |
| DBG_NAV_LOGD("bits.mWidth=%d bits.mHeight=%d transX=%d transY=%d", |
| marginBounds.width(),marginBounds.height(), |
| kMargin - bounds.x(), kMargin - bounds.y()); |
| #endif |
| node->setDisabled(true); |
| node->setClippedOut(unclipped == false); |
| return true; |
| } |
| // was it partially occluded by later drawing? |
| // if partially occluded, modify the bounds so that the mouse click has a better x,y |
| const SkIRect& over = boundsCheck.mDrawnOver.getBounds(); |
| if (over.isEmpty() == false) { |
| #if DEBUG_NAV_UI |
| SkIRect orig = boundsCheck.mBounds; |
| #endif |
| SkIRect& base = boundsCheck.mBounds; |
| if (base.fLeft < over.fRight && base.fRight > over.fRight) |
| base.fLeft = over.fRight; |
| else if (base.fRight > over.fLeft && base.fLeft < over.fLeft) |
| base.fRight = over.fLeft; |
| if (base.fTop < over.fBottom && base.fBottom > over.fBottom) |
| base.fTop = over.fBottom; |
| else if (base.fBottom > over.fTop && base.fTop < over.fTop) |
| base.fBottom = over.fTop; |
| #if DEBUG_NAV_UI |
| const SkIRect& modded = boundsCheck.mBounds; |
| DBG_NAV_LOGD("partially occluded node:%p (%d) old:{%d,%d,%d,%d}" |
| " new:{%d,%d,%d,%d}", best->mNode, best->mNode->index(), |
| orig.fLeft, orig.fTop, orig.fRight, orig.fBottom, |
| base.fLeft, base.fTop, base.fRight, base.fBottom); |
| #endif |
| best->mMouseBounds = WebCore::IntRect(bounds.x() + base.fLeft - kMargin, |
| bounds.y() + base.fTop - kMargin, base.width(), base.height()); |
| } |
| return false; |
| } |
| |
| const CachedNode* CachedRoot::moveCursor(Direction direction, const CachedFrame** framePtr, |
| WebCore::IntPoint* scroll) |
| { |
| #ifndef NDEBUG |
| ASSERT(CachedFrame::mDebug.mInUse); |
| #endif |
| CachedRoot* frame = this; |
| const CachedNode* node = frame->document(); |
| if (node == NULL) |
| return NULL; |
| if (mViewBounds.isEmpty()) |
| return NULL; |
| resetClippedOut(); |
| setData(); |
| BestData bestData; |
| innerMove(node, &bestData, direction, scroll, true); |
| *framePtr = bestData.mFrame; |
| return const_cast<CachedNode*>(bestData.mNode); |
| } |
| |
| void CachedRoot::reset() |
| { |
| #ifndef NDEBUG |
| ASSERT(CachedFrame::mDebug.mInUse); |
| #endif |
| mContents = mViewBounds = WebCore::IntRect(0, 0, 0, 0); |
| mMaxXScroll = mMaxYScroll = 0; |
| mSelectionStart = mSelectionEnd = -1; |
| mScrollOnly = false; |
| } |
| |
| bool CachedRoot::scrollDelta(WebCore::IntRect& newOutset, Direction direction, int* delta) |
| { |
| switch (direction) { |
| case LEFT: |
| *delta = -mMaxXScroll; |
| return newOutset.x() >= mViewBounds.x(); |
| case RIGHT: |
| *delta = mMaxXScroll; |
| return newOutset.right() <= mViewBounds.right(); |
| case UP: |
| *delta = -mMaxYScroll; |
| return newOutset.y() >= mViewBounds.y(); |
| case DOWN: |
| *delta = mMaxYScroll; |
| return newOutset.bottom() <= mViewBounds.bottom(); |
| default: |
| *delta = 0; |
| ASSERT(0); |
| } |
| return false; |
| } |
| |
| void CachedRoot::setCachedFocus(CachedFrame* frame, CachedNode* node) |
| { |
| mFocusBounds = WebCore::IntRect(0, 0, 0, 0); |
| if (node == NULL) |
| return; |
| node->setIsFocus(true); |
| mFocusBounds = node->bounds(); |
| frame->setFocusIndex(node - frame->document()); |
| CachedFrame* parent; |
| while ((parent = frame->parent()) != NULL) { |
| parent->setFocusIndex(frame->indexInParent()); |
| frame = parent; |
| } |
| #if DEBUG_NAV_UI |
| const CachedNode* focus = currentFocus(); |
| WebCore::IntRect bounds = WebCore::IntRect(0, 0, 0, 0); |
| if (focus) |
| bounds = focus->bounds(); |
| DBG_NAV_LOGD("new focus %d (nodePointer=%p) bounds={%d,%d,%d,%d}", |
| focus ? focus->index() : 0, |
| focus ? focus->nodePointer() : NULL, bounds.x(), bounds.y(), |
| bounds.width(), bounds.height()); |
| #endif |
| } |
| |
| void CachedRoot::setCursor(CachedFrame* frame, CachedNode* node) |
| { |
| #if DEBUG_NAV_UI |
| const CachedNode* cursor = currentCursor(); |
| WebCore::IntRect bounds; |
| if (cursor) |
| bounds = cursor->bounds(); |
| DBG_NAV_LOGD("old cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", |
| cursor ? cursor->index() : 0, |
| cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), |
| bounds.width(), bounds.height()); |
| #endif |
| clearCursor(); |
| if (node == NULL) |
| return; |
| node->setIsCursor(true); |
| node->show(); |
| frame->setCursorIndex(node - frame->document()); |
| CachedFrame* parent; |
| while ((parent = frame->parent()) != NULL) { |
| parent->setCursorIndex(frame->indexInParent()); |
| frame = parent; |
| } |
| #if DEBUG_NAV_UI |
| cursor = currentCursor(); |
| bounds = WebCore::IntRect(0, 0, 0, 0); |
| if (cursor) |
| bounds = cursor->bounds(); |
| DBG_NAV_LOGD("new cursor %d (nodePointer=%p) bounds={%d,%d,%d,%d}", |
| cursor ? cursor->index() : 0, |
| cursor ? cursor->nodePointer() : NULL, bounds.x(), bounds.y(), |
| bounds.width(), bounds.height()); |
| #endif |
| } |
| |
| #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()); } |
| |
| CachedRoot* CachedRoot::Debug::base() const { |
| CachedRoot* nav = (CachedRoot*) ((char*) this - OFFSETOF(CachedRoot, mDebug)); |
| return nav; |
| } |
| |
| void CachedRoot::Debug::print() const |
| { |
| #ifdef DUMP_NAV_CACHE_USING_PRINTF |
| gWriteLogMutex.lock(); |
| ASSERT(gNavCacheLogFile == NULL); |
| gNavCacheLogFile = fopen(NAV_CACHE_LOG_FILE, "a"); |
| #endif |
| CachedRoot* b = base(); |
| b->CachedFrame::mDebug.print(); |
| b->mHistory->mDebug.print(b); |
| DUMP_NAV_LOGD("// int mMaxXScroll=%d, mMaxYScroll=%d;\n", |
| b->mMaxXScroll, b->mMaxYScroll); |
| #ifdef DUMP_NAV_CACHE_USING_PRINTF |
| if (gNavCacheLogFile) |
| fclose(gNavCacheLogFile); |
| gNavCacheLogFile = NULL; |
| gWriteLogMutex.unlock(); |
| #endif |
| } |
| |
| #endif |
| |
| } |