diff options
Diffstat (limited to 'WebKit')
| -rw-r--r-- | WebKit/Android.mk | 1 | ||||
| -rw-r--r-- | WebKit/android/RenderSkinAndroid.cpp | 4 | ||||
| -rw-r--r-- | WebKit/android/RenderSkinCombo.cpp | 94 | ||||
| -rw-r--r-- | WebKit/android/RenderSkinCombo.h | 20 | ||||
| -rw-r--r-- | WebKit/android/RenderSkinMediaButton.cpp | 171 | ||||
| -rw-r--r-- | WebKit/android/RenderSkinMediaButton.h | 61 | ||||
| -rw-r--r-- | WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp | 554 | ||||
| -rw-r--r-- | WebKit/android/jni/WebCoreJniOnLoad.cpp | 6 | ||||
| -rw-r--r-- | WebKit/android/jni/WebViewCore.cpp | 27 | ||||
| -rw-r--r-- | WebKit/android/jni/WebViewCore.h | 3 | ||||
| -rw-r--r-- | WebKit/android/nav/CacheBuilder.cpp | 15 | ||||
| -rw-r--r-- | WebKit/android/nav/CacheBuilder.h | 2 | ||||
| -rw-r--r-- | WebKit/android/nav/CachedInput.cpp | 4 | ||||
| -rw-r--r-- | WebKit/android/nav/CachedInput.h | 16 | ||||
| -rw-r--r-- | WebKit/android/nav/FindCanvas.h | 1 | ||||
| -rw-r--r-- | WebKit/android/nav/SelectText.cpp | 1468 | ||||
| -rw-r--r-- | WebKit/android/nav/SelectText.h | 59 | ||||
| -rw-r--r-- | WebKit/android/nav/WebView.cpp | 208 | ||||
| -rw-r--r-- | WebKit/android/plugins/ANPKeyCodes.h | 34 | ||||
| -rw-r--r-- | WebKit/android/plugins/ANPSurfaceInterface.cpp | 2 |
20 files changed, 2229 insertions, 521 deletions
diff --git a/WebKit/Android.mk b/WebKit/Android.mk index 2c12ed3..47f37ca 100644 --- a/WebKit/Android.mk +++ b/WebKit/Android.mk @@ -31,6 +31,7 @@ LOCAL_SRC_FILES := \ android/RenderSkinAndroid.cpp \ android/RenderSkinButton.cpp \ android/RenderSkinCombo.cpp \ + android/RenderSkinMediaButton.cpp \ android/RenderSkinRadio.cpp \ android/TimeCounter.cpp \ \ diff --git a/WebKit/android/RenderSkinAndroid.cpp b/WebKit/android/RenderSkinAndroid.cpp index d148262..00f2b96 100644 --- a/WebKit/android/RenderSkinAndroid.cpp +++ b/WebKit/android/RenderSkinAndroid.cpp @@ -29,6 +29,7 @@ #include "RenderSkinAndroid.h" #include "RenderSkinButton.h" #include "RenderSkinCombo.h" +#include "RenderSkinMediaButton.h" #include "RenderSkinRadio.h" #include "SkImageDecoder.h" @@ -45,7 +46,8 @@ RenderSkinAndroid::RenderSkinAndroid() void RenderSkinAndroid::Init(android::AssetManager* am, String drawableDirectory) { RenderSkinButton::Init(am, drawableDirectory); - RenderSkinCombo::Init(am); + RenderSkinCombo::Init(am, drawableDirectory); + RenderSkinMediaButton::Init(am, drawableDirectory); RenderSkinRadio::Init(am, drawableDirectory); } diff --git a/WebKit/android/RenderSkinCombo.cpp b/WebKit/android/RenderSkinCombo.cpp index 6f88ee3..4378371 100644 --- a/WebKit/android/RenderSkinCombo.cpp +++ b/WebKit/android/RenderSkinCombo.cpp @@ -26,6 +26,7 @@ #include "config.h" #include "RenderSkinCombo.h" +#include "CString.h" #include "Document.h" #include "Element.h" #include "Node.h" @@ -36,43 +37,85 @@ namespace WebCore { -static SkBitmap s_bitmap[2]; // Collection of assets for a combo box -static bool s_decoded; // True if all assets were decoded -static const int s_margin = 2; -static const SkIRect s_mar = { s_margin, s_margin, - RenderSkinCombo::extraWidth(), s_margin }; -static SkIRect s_subset; +// Indicates if the entire asset is being drawn, or if the border is being +// excluded and just the arrow drawn. +enum BorderStyle { + FullAsset, + NoBorder +}; +// There are 2.5 different concepts of a 'border' here, which results +// in rather a lot of magic constants. In each case, there are 2 +// numbers, one for medium res and one for high-res. All sizes are in pixels. -RenderSkinCombo::RenderSkinCombo() -{ -} +// Firstly, we have the extra padding that webkit needs to know about, +// which defines how much bigger this element is made by the +// asset. This is actually a bit broader than the actual border on the +// asset, to make things look less cramped. The border is the same +// width on all sides, except on the right when it's significantly +// wider to allow for the arrow. +const int RenderSkinCombo::arrowMargin[2] = {22, 34}; +const int RenderSkinCombo::padMargin[2] = {2, 5}; + +// Then we have the borders used for the 9-patch stretch. The +// rectangle at the centre of these borders is entirely below and to +// the left of the arrow in the asset. Hence the border widths are the +// same for the bottom and left, but are different for the top. The +// right hand border width happens to be the same as arrowMargin +// defined above. +static const int stretchMargin[2] = {3, 5}; // border width for the bottom and left of the 9-patch +static const int stretchTop[2] = {15, 23}; // border width for the top of the 9-patch + +// Finally, if the border is defined by the CSS, we only draw the +// arrow and not the border. We do this by drawing the relevant subset +// of the bitmap, which must now be precisely determined by what's in +// the asset with no extra padding to make things look properly +// spaced. The border to remove at the top, right and bottom of the +// image is the same as stretchMargin above, but we need to know the width +// of the arrow. +static const int arrowWidth[2] = {22, 31}; + +RenderSkinCombo::Resolution RenderSkinCombo::resolution = MedRes; + +const SkIRect RenderSkinCombo::margin[2][2] = {{{ stretchMargin[MedRes], stretchTop[MedRes], + RenderSkinCombo::arrowMargin[MedRes] + stretchMargin[MedRes], stretchMargin[MedRes] }, + {0, stretchTop[MedRes], 0, stretchMargin[MedRes]}}, + {{ stretchMargin[HighRes], stretchTop[HighRes], + RenderSkinCombo::arrowMargin[HighRes] + stretchMargin[HighRes], stretchMargin[HighRes] }, + {0, stretchTop[HighRes], 0, stretchMargin[HighRes]}}}; +static SkBitmap bitmaps[2][2]; // Collection of assets for a combo box +static bool isDecoded; // True if all assets were decoded -void RenderSkinCombo::Init(android::AssetManager* am) +void RenderSkinCombo::Init(android::AssetManager* am, String drawableDirectory) { - if (s_decoded) + if (isDecoded) return; - // Maybe short circuiting is fine, since I don't even draw if one state is not decoded properly - // but is that necessary in the final version? - s_decoded = RenderSkinAndroid::DecodeBitmap(am, "images/combobox-noHighlight.png", &s_bitmap[kNormal]); - s_decoded = RenderSkinAndroid::DecodeBitmap(am, "images/combobox-disabled.png", &s_bitmap[kDisabled]) && s_decoded; - - int width = s_bitmap[kNormal].width(); - int height = s_bitmap[kNormal].height(); - s_subset.set(width - RenderSkinCombo::extraWidth() + s_margin, 0, width, height); + + if (drawableDirectory[drawableDirectory.length() - 5] == 'h') + resolution = HighRes; + + isDecoded = RenderSkinAndroid::DecodeBitmap(am, (drawableDirectory + "combobox_nohighlight.png").utf8().data(), &bitmaps[kNormal][FullAsset]); + isDecoded &= RenderSkinAndroid::DecodeBitmap(am, (drawableDirectory + "combobox_disabled.png").utf8().data(), &bitmaps[kDisabled][FullAsset]); + + int width = bitmaps[kNormal][FullAsset].width(); + int height = bitmaps[kNormal][FullAsset].height(); + SkIRect subset; + subset.set(width - arrowWidth[resolution], 0, width, height); + bitmaps[kNormal][FullAsset].extractSubset(&bitmaps[kNormal][NoBorder], subset); + bitmaps[kDisabled][FullAsset].extractSubset(&bitmaps[kDisabled][NoBorder], subset); } bool RenderSkinCombo::Draw(SkCanvas* canvas, Node* element, int x, int y, int width, int height) { - if (!s_decoded) + if (!isDecoded) return true; State state = (element->isElementNode() && static_cast<Element*>(element)->isEnabledFormControl()) ? kNormal : kDisabled; - if (height < (s_margin<<1) + 1) { - height = (s_margin<<1) + 1; - } + height = std::max(height, (stretchMargin[resolution]<<1) + 1); + SkRect bounds; + BorderStyle drawBorder = FullAsset; bounds.set(SkIntToScalar(x+1), SkIntToScalar(y+1), SkIntToScalar(x + width-1), SkIntToScalar(y + height-1)); RenderStyle* style = element->renderStyle(); @@ -90,10 +133,9 @@ bool RenderSkinCombo::Draw(SkCanvas* canvas, Node* element, int x, int y, int wi bounds.fRight -= SkIntToScalar(style->borderRightWidth()); bounds.fTop += SkIntToScalar(style->borderTopWidth()); bounds.fBottom -= SkIntToScalar(style->borderBottomWidth()); - canvas->drawBitmapRect(s_bitmap[state], &s_subset, bounds); - } else { - SkNinePatch::DrawNine(canvas, bounds, s_bitmap[state], s_mar); + drawBorder = NoBorder; } + SkNinePatch::DrawNine(canvas, bounds, bitmaps[state][drawBorder], margin[resolution][drawBorder]); return false; } diff --git a/WebKit/android/RenderSkinCombo.h b/WebKit/android/RenderSkinCombo.h index 91c9367..38cd048 100644 --- a/WebKit/android/RenderSkinCombo.h +++ b/WebKit/android/RenderSkinCombo.h @@ -37,13 +37,10 @@ namespace WebCore { class RenderSkinCombo : public RenderSkinAndroid { public: - RenderSkinCombo(); - virtual ~RenderSkinCombo() {} - /** * Initialize the class before use. Uses the AssetManager to initialize any bitmaps the class may use. */ - static void Init(android::AssetManager*); + static void Init(android::AssetManager*, String drawableDirectory); /** * Draw the provided Node on the SkCanvas, using the dimensions provided by @@ -53,11 +50,18 @@ public: static bool Draw(SkCanvas* , Node* , int x, int y, int w, int h); // The image is wider than the RenderObject, so this accounts for that. - static int extraWidth() { return arrowMargin; } - + static int extraWidth() { return arrowMargin[resolution]; } + static int padding() { return padMargin[resolution]; } + + enum Resolution { + MedRes, + HighRes + }; private: - - static const int arrowMargin = 22; + static Resolution resolution; + const static int arrowMargin[2]; + const static int padMargin[2]; + const static SkIRect margin[2][2]; }; } // WebCore diff --git a/WebKit/android/RenderSkinMediaButton.cpp b/WebKit/android/RenderSkinMediaButton.cpp new file mode 100644 index 0000000..149be4e --- /dev/null +++ b/WebKit/android/RenderSkinMediaButton.cpp @@ -0,0 +1,171 @@ +/* + * Copyright 2010, 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. + */ + +#define LOG_TAG "WebCore" + +#include "config.h" +#include "CString.h" +#include "android_graphics.h" +#include "Document.h" +#include "IntRect.h" +#include "Node.h" +#include "RenderSkinMediaButton.h" +#include "SkCanvas.h" +#include "SkNinePatch.h" +#include "SkRect.h" +#include <utils/Debug.h> +#include <utils/Log.h> + +struct PatchData { + const char* name; + int8_t outset, margin; +}; + +static const PatchData gFiles[] = + { + { "btn_media_player.9.png", 0, 0 }, // DEFAULT BGD BUTTON + { "ic_media_pause.png", 0, 0}, // PAUSE + { "ic_media_play.png", 0, 0 }, // PLAY + { "ic_media_pause.png", 0, 0 }, // MUTE + { "ic_media_rew.png", 0, 0 }, // REWIND + { "ic_media_ff.png", 0, 0 }, // FORWARD + { "btn_media_player_disabled.9.png", 0, 0 }, // BACKGROUND_SLIDER + { "btn_media_player_pressed.9.png", 0, 0 }, // SLIDER_TRACK + { "btn_media_player.9.png", 0, 0 } // SLIDER_THUMB + }; + +static SkBitmap gButton[sizeof(gFiles)/sizeof(gFiles[0])]; +static bool gDecoded; +static bool gHighRes; + +namespace WebCore { + +void RenderSkinMediaButton::Init(android::AssetManager* am, String drawableDirectory) +{ + static bool gInited; + if (gInited) + return; + + gInited = true; + gDecoded = true; + gHighRes = drawableDirectory[drawableDirectory.length() - 5] == 'h'; + for (size_t i = 0; i < sizeof(gFiles)/sizeof(gFiles[0]); i++) { + String path = drawableDirectory + gFiles[i].name; + if (!RenderSkinAndroid::DecodeBitmap(am, path.utf8().data(), &gButton[i])) { + gDecoded = false; + LOGD("RenderSkinButton::Init: button assets failed to decode\n\tBrowser buttons will not draw"); + break; + } + } +} + +void RenderSkinMediaButton::Draw(SkCanvas* canvas, const IntRect& r, int buttonType) +{ + // If we failed to decode, do nothing. This way the browser still works, + // and webkit will still draw the label and layout space for us. + if (!gDecoded) { + return; + } + + bool drawsNinePatch = true; + bool drawsImage = true; + bool drawsBackgroundColor = false; + + int ninePatchIndex = 0; + int imageIndex = 0; + + SkRect bounds(r); + SkScalar imageMargin = 8; + SkPaint paint; + SkColor backgroundColor = SkColorSetARGB(255, 200, 200, 200); + SkColor trackBackgroundColor = SkColorSetARGB(255, 100, 100, 100); + + switch (buttonType) { + case PAUSE: + case PLAY: + case MUTE: + case REWIND: + case FORWARD: + { + imageIndex = buttonType + 1; + drawsBackgroundColor = true; + paint.setColor(backgroundColor); + break; + } + case BACKGROUND_SLIDER: + { + drawsImage = false; + drawsNinePatch = false; + drawsBackgroundColor = true; + paint.setColor(backgroundColor); + break; + } + case SLIDER_TRACK: + { + drawsImage = false; + drawsNinePatch = false; + drawsBackgroundColor = true; + paint.setColor(trackBackgroundColor); + bounds.fTop += 8; + bounds.fBottom -= 8; + break; + } + case SLIDER_THUMB: + { + drawsImage = false; + ninePatchIndex = buttonType + 1; + break; + } + default: + drawsImage = false; + drawsNinePatch = false; + } + + if (drawsBackgroundColor) { + canvas->drawRect(r, paint); + } + + if (drawsNinePatch) { + const PatchData& pd = gFiles[ninePatchIndex]; + int marginValue = pd.margin + pd.outset; + + SkIRect margin; + margin.set(marginValue, marginValue, marginValue, marginValue); + SkNinePatch::DrawNine(canvas, bounds, gButton[0], margin); + } + + if (drawsImage) { + SkScalar SIZE = gButton[imageIndex].width(); + SkScalar width = r.width(); + SkScalar scale = SkScalarDiv(width - 2*imageMargin, SIZE); + int saveScaleCount = canvas->save(); + canvas->translate(bounds.fLeft + imageMargin, bounds.fTop + imageMargin); + canvas->scale(scale, scale); + canvas->drawBitmap(gButton[imageIndex], 0, 0, &paint); + canvas->restoreToCount(saveScaleCount); + } +} + +} //WebCore diff --git a/WebKit/android/RenderSkinMediaButton.h b/WebKit/android/RenderSkinMediaButton.h new file mode 100644 index 0000000..b4e99f4 --- /dev/null +++ b/WebKit/android/RenderSkinMediaButton.h @@ -0,0 +1,61 @@ +/* + * Copyright 2010, 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. + */ + +#ifndef RenderSkinMediaButton_h +#define RenderSkinMediaButton_h + +#include "RenderSkinAndroid.h" + +class SkCanvas; + +namespace WebCore { +class IntRect; +class RenderSkinMediaButton +{ +public: + /** + * Initialize the class before use. Uses the AssetManager to initialize any + * bitmaps the class may use. + */ + static void Init(android::AssetManager*, String drawableDirectory); + /** + * Draw the skin to the canvas, using the rectangle for its bounds and the + * State to determine which skin to use, i.e. focused or not focused. + */ + static void Draw(SkCanvas* , const IntRect& , int buttonType); + /** + * Button types + */ + enum { PAUSE, PLAY, MUTE, REWIND, FORWARD, BACKGROUND_SLIDER, SLIDER_TRACK, SLIDER_THUMB }; + /** + * Slider dimensions + */ + static int sliderThumbWidth() { return 10; } + static int sliderThumbHeight() { return 30; } + +}; + +} // WebCore +#endif // RenderSkinMediaButton_h diff --git a/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp b/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp index bfb5305..3a39730 100644 --- a/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp +++ b/WebKit/android/WebCoreSupport/MediaPlayerPrivateAndroid.cpp @@ -30,7 +30,6 @@ #include "GraphicsContext.h" #include "SkiaUtils.h" -#include "TimeRanges.h" #include "WebCoreJni.h" #include "WebViewCore.h" @@ -44,16 +43,22 @@ using namespace android; namespace WebCore { static const char* g_ProxyJavaClass = "android/webkit/HTML5VideoViewProxy"; +static const char* g_ProxyJavaClassAudio = "android/webkit/HTML5Audio"; struct MediaPlayerPrivate::JavaGlue { jobject m_javaProxy; - jmethodID m_getInstance; jmethodID m_play; jmethodID m_teardown; - jmethodID m_loadPoster; jmethodID m_seek; jmethodID m_pause; + // Audio + jmethodID m_newInstance; + jmethodID m_setDataSource; + jmethodID m_getMaxTimeSeekable; + // Video + jmethodID m_getInstance; + jmethodID m_loadPoster; }; MediaPlayerPrivate::~MediaPlayerPrivate() @@ -65,7 +70,6 @@ MediaPlayerPrivate::~MediaPlayerPrivate() env->DeleteGlobalRef(m_glue->m_javaProxy); } } - delete m_glue; } @@ -74,29 +78,6 @@ void MediaPlayerPrivate::registerMediaEngine(MediaEngineRegistrar registrar) registrar(create, getSupportedTypes, supportsType); } -void MediaPlayerPrivate::load(const String& url) -{ - // Just save the URl. - m_url = url; -} - -void MediaPlayerPrivate::cancelLoad() -{ -} - -void MediaPlayerPrivate::play() -{ - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env || !m_glue->m_javaProxy || !m_url.length()) - return; - - m_paused = false; - jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl); - env->DeleteLocalRef(jUrl); - checkException(env); -} - void MediaPlayerPrivate::pause() { JNIEnv* env = JSC::Bindings::getJNIEnv(); @@ -108,22 +89,6 @@ void MediaPlayerPrivate::pause() checkException(env); } -IntSize MediaPlayerPrivate::naturalSize() const -{ - return m_naturalSize; -} - -bool MediaPlayerPrivate::hasAudio() const -{ - // TODO - return false; -} - -bool MediaPlayerPrivate::hasVideo() const -{ - return m_hasVideo; -} - void MediaPlayerPrivate::setVisible(bool visible) { m_isVisible = visible; @@ -131,99 +96,19 @@ void MediaPlayerPrivate::setVisible(bool visible) createJavaPlayerIfNeeded(); } -float MediaPlayerPrivate::duration() const -{ - return m_duration; -} - -float MediaPlayerPrivate::currentTime() const -{ - return m_currentTime; -} - void MediaPlayerPrivate::seek(float time) { JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env || !m_glue->m_javaProxy || !m_url.length()) + if (!env || !m_url.length()) return; - m_currentTime = time; - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f)); + if (m_glue->m_javaProxy) { + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_seek, static_cast<jint>(time * 1000.0f)); + m_currentTime = time; + } checkException(env); } -bool MediaPlayerPrivate::seeking() const -{ - return false; -} - -void MediaPlayerPrivate::setEndTime(float) -{ -} - -void MediaPlayerPrivate::setRate(float) -{ -} - -bool MediaPlayerPrivate::paused() const -{ - return m_paused; -} - -void MediaPlayerPrivate::setVolume(float) -{ -} - -MediaPlayer::NetworkState MediaPlayerPrivate::networkState() const -{ - return m_networkState; -} - -MediaPlayer::ReadyState MediaPlayerPrivate::readyState() const -{ - return m_readyState; -} - -float MediaPlayerPrivate::maxTimeSeekable() const -{ - return 0; -} - -PassRefPtr<TimeRanges> MediaPlayerPrivate::buffered() const -{ - return TimeRanges::create(); -} - -int MediaPlayerPrivate::dataRate() const -{ - return 0; -} - -unsigned MediaPlayerPrivate::totalBytes() const -{ - return 0; -} - -unsigned MediaPlayerPrivate::bytesLoaded() const -{ - return 0; -} - -void MediaPlayerPrivate::setSize(const IntSize&) -{ -} - -void MediaPlayerPrivate::setPoster(const String& url) -{ - m_posterUrl = url; - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env || !m_glue->m_javaProxy || !m_posterUrl.length()) - return; - // Send the poster - jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); - env->DeleteLocalRef(jUrl); -} void MediaPlayerPrivate::prepareToPlay() { // We are about to start playing. Since our Java VideoView cannot @@ -237,42 +122,10 @@ void MediaPlayerPrivate::prepareToPlay() { m_player->readyStateChanged(); } -void MediaPlayerPrivate::paint(GraphicsContext* ctxt, const IntRect& r) -{ - if (ctxt->paintingDisabled()) - return; - - if (!m_isVisible) - return; - - if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) - return; - - SkCanvas* canvas = ctxt->platformContext()->mCanvas; - // We paint with the following rules in mind: - // - only downscale the poster, never upscale - // - maintain the natural aspect ratio of the poster - // - the poster should be centered in the target rect - float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height()); - int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width(); - int posterHeight = posterWidth / originalRatio; - int posterX = ((r.width() - posterWidth) / 2) + r.x(); - int posterY = ((r.height() - posterHeight) / 2) + r.y(); - IntRect targetRect(posterX, posterY, posterWidth, posterHeight); - canvas->drawBitmapRect(*m_poster, 0, targetRect, 0); -} - -MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) -{ - return new MediaPlayerPrivate(player); -} - -void MediaPlayerPrivate::getSupportedTypes(HashSet<String>&) -{ -} - MediaPlayer::SupportsType MediaPlayerPrivate::supportsType(const String& type, const String& codecs) { + if (WebViewCore::supportsMimeType(type)) + return MediaPlayer::MayBeSupported; return MediaPlayer::IsNotSupported; } @@ -290,72 +143,6 @@ MediaPlayerPrivate::MediaPlayerPrivate(MediaPlayer* player) m_naturalSizeUnknown(true), m_isVisible(false) { - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env) - return; - - jclass clazz = env->FindClass(g_ProxyJavaClass); - if (!clazz) - return; - - m_glue = new JavaGlue; - m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;"); - m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V"); - m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); - m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V"); - m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); - m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); - m_glue->m_javaProxy = NULL; - env->DeleteLocalRef(clazz); - // An exception is raised if any of the above fails. - checkException(env); -} - -void MediaPlayerPrivate::createJavaPlayerIfNeeded() -{ - // Check if we have been already created. - if (m_glue->m_javaProxy) - return; - - FrameView* frameView = m_player->frameView(); - if (!frameView) - return; - - JNIEnv* env = JSC::Bindings::getJNIEnv(); - if (!env) - return; - - jclass clazz = env->FindClass(g_ProxyJavaClass); - if (!clazz) - return; - - WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView); - ASSERT(webViewCore); - - // Get the HTML5VideoViewProxy instance - jobject obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this); - m_glue->m_javaProxy = env->NewGlobalRef(obj); - // Send the poster - jstring jUrl = 0; - if (m_posterUrl.length()) - jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); - // Sending a NULL jUrl allows the Java side to try to load the default poster. - env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); - if (jUrl) - env->DeleteLocalRef(jUrl); - // Clean up. - env->DeleteLocalRef(obj); - env->DeleteLocalRef(clazz); - checkException(env); -} - -void MediaPlayerPrivate::onPrepared(int duration, int width, int height) { - m_duration = duration / 1000.0f; - m_naturalSize = IntSize(width, height); - m_naturalSizeUnknown = false; - m_hasVideo = true; - m_player->durationChanged(); - m_player->sizeChanged(); } void MediaPlayerPrivate::onEnded() { @@ -368,18 +155,12 @@ void MediaPlayerPrivate::onEnded() { m_readyState = MediaPlayer::HaveNothing; } -void MediaPlayerPrivate::onPosterFetched(SkBitmap* poster) { - m_poster = poster; - if (m_naturalSizeUnknown) { - // We had to fake the size at startup, or else our paint - // method would not be called. If we haven't yet received - // the onPrepared event, update the intrinsic size to the size - // of the poster. That will be overriden when onPrepare comes. - // In case of an error, we should report the poster size, rather - // than our initial fake value. - m_naturalSize = IntSize(poster->width(), poster->height()); - m_player->sizeChanged(); - } +void MediaPlayerPrivate::onPaused() { + m_paused = true; + m_currentTime = 0; + m_hasVideo = false; + m_networkState = MediaPlayer::Idle; + m_readyState = MediaPlayer::HaveNothing; } void MediaPlayerPrivate::onTimeupdate(int position) { @@ -387,6 +168,264 @@ void MediaPlayerPrivate::onTimeupdate(int position) { m_player->timeChanged(); } +class MediaPlayerVideoPrivate : public MediaPlayerPrivate { +public: + void load(const String& url) { m_url = url; } + void play() { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length() || !m_glue->m_javaProxy) + return; + + m_paused = false; + jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play, jUrl); + env->DeleteLocalRef(jUrl); + + checkException(env); + } + bool canLoadPoster() const { return true; } + void setPoster(const String& url) { + m_posterUrl = url; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_glue->m_javaProxy || !m_posterUrl.length()) + return; + // Send the poster + jstring jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); + env->DeleteLocalRef(jUrl); + } + void paint(GraphicsContext* ctxt, const IntRect& r) { + if (ctxt->paintingDisabled()) + return; + + if (!m_isVisible) + return; + + if (!m_poster || (!m_poster->getPixels() && !m_poster->pixelRef())) + return; + + SkCanvas* canvas = ctxt->platformContext()->mCanvas; + // We paint with the following rules in mind: + // - only downscale the poster, never upscale + // - maintain the natural aspect ratio of the poster + // - the poster should be centered in the target rect + float originalRatio = static_cast<float>(m_poster->width()) / static_cast<float>(m_poster->height()); + int posterWidth = r.width() > m_poster->width() ? m_poster->width() : r.width(); + int posterHeight = posterWidth / originalRatio; + int posterX = ((r.width() - posterWidth) / 2) + r.x(); + int posterY = ((r.height() - posterHeight) / 2) + r.y(); + IntRect targetRect(posterX, posterY, posterWidth, posterHeight); + canvas->drawBitmapRect(*m_poster, 0, targetRect, 0); + } + + void onPosterFetched(SkBitmap* poster) { + m_poster = poster; + if (m_naturalSizeUnknown) { + // We had to fake the size at startup, or else our paint + // method would not be called. If we haven't yet received + // the onPrepared event, update the intrinsic size to the size + // of the poster. That will be overriden when onPrepare comes. + // In case of an error, we should report the poster size, rather + // than our initial fake value. + m_naturalSize = IntSize(poster->width(), poster->height()); + m_player->sizeChanged(); + } + } + + void onPrepared(int duration, int width, int height) { + m_duration = duration / 1000.0f; + m_naturalSize = IntSize(width, height); + m_naturalSizeUnknown = false; + m_hasVideo = true; + m_player->durationChanged(); + m_player->sizeChanged(); + } + + bool hasAudio() { return false; } // do not display the audio UI + bool hasVideo() { return m_hasVideo; } + + MediaPlayerVideoPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClass); + + if (!clazz) + return; + + m_glue = new JavaGlue; + m_glue->m_getInstance = env->GetStaticMethodID(clazz, "getInstance", "(Landroid/webkit/WebViewCore;I)Landroid/webkit/HTML5VideoViewProxy;"); + m_glue->m_loadPoster = env->GetMethodID(clazz, "loadPoster", "(Ljava/lang/String;)V"); + m_glue->m_play = env->GetMethodID(clazz, "play", "(Ljava/lang/String;)V"); + + m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); + m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); + m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); + m_glue->m_javaProxy = NULL; + env->DeleteLocalRef(clazz); + // An exception is raised if any of the above fails. + checkException(env); + } + + void createJavaPlayerIfNeeded() { + // Check if we have been already created. + if (m_glue->m_javaProxy) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClass); + + if (!clazz) + return; + + jobject obj = NULL; + + FrameView* frameView = m_player->frameView(); + if (!frameView) + return; + WebViewCore* webViewCore = WebViewCore::getWebViewCore(frameView); + ASSERT(webViewCore); + + // Get the HTML5VideoViewProxy instance + obj = env->CallStaticObjectMethod(clazz, m_glue->m_getInstance, webViewCore->getJavaObject().get(), this); + m_glue->m_javaProxy = env->NewGlobalRef(obj); + // Send the poster + jstring jUrl = 0; + if (m_posterUrl.length()) + jUrl = env->NewString((unsigned short *)m_posterUrl.characters(), m_posterUrl.length()); + // Sending a NULL jUrl allows the Java side to try to load the default poster. + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_loadPoster, jUrl); + if (jUrl) + env->DeleteLocalRef(jUrl); + + // Clean up. + if (obj) + env->DeleteLocalRef(obj); + env->DeleteLocalRef(clazz); + checkException(env); + } +}; + +class MediaPlayerAudioPrivate : public MediaPlayerPrivate { +public: + void load(const String& url) { + m_url = url; + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + createJavaPlayerIfNeeded(); + + if (!m_glue->m_javaProxy) + return; + + jstring jUrl = env->NewString((unsigned short *)m_url.characters(), m_url.length()); + // start loading the data asynchronously + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_setDataSource, jUrl); + env->DeleteLocalRef(jUrl); + checkException(env); + } + + void play() { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env || !m_url.length()) + return; + + createJavaPlayerIfNeeded(); + + if (!m_glue->m_javaProxy) + return; + + m_paused = false; + env->CallVoidMethod(m_glue->m_javaProxy, m_glue->m_play); + checkException(env); + } + + bool hasAudio() { return true; } + + float maxTimeSeekable() const { + if (m_glue->m_javaProxy) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (env) { + float maxTime = env->CallFloatMethod(m_glue->m_javaProxy, + m_glue->m_getMaxTimeSeekable); + checkException(env); + return maxTime; + } + } + return 0; + } + + MediaPlayerAudioPrivate(MediaPlayer* player) : MediaPlayerPrivate(player) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClassAudio); + + if (!clazz) + return; + + m_glue = new JavaGlue; + m_glue->m_newInstance = env->GetMethodID(clazz, "<init>", "(I)V"); + m_glue->m_setDataSource = env->GetMethodID(clazz, "setDataSource", "(Ljava/lang/String;)V"); + m_glue->m_play = env->GetMethodID(clazz, "play", "()V"); + m_glue->m_getMaxTimeSeekable = env->GetMethodID(clazz, "getMaxTimeSeekable", "()F"); + m_glue->m_teardown = env->GetMethodID(clazz, "teardown", "()V"); + m_glue->m_seek = env->GetMethodID(clazz, "seek", "(I)V"); + m_glue->m_pause = env->GetMethodID(clazz, "pause", "()V"); + m_glue->m_javaProxy = NULL; + env->DeleteLocalRef(clazz); + // An exception is raised if any of the above fails. + checkException(env); + } + + void createJavaPlayerIfNeeded() { + // Check if we have been already created. + if (m_glue->m_javaProxy) + return; + + JNIEnv* env = JSC::Bindings::getJNIEnv(); + if (!env) + return; + + jclass clazz = env->FindClass(g_ProxyJavaClassAudio); + + if (!clazz) + return; + + jobject obj = NULL; + + // Get the HTML5Audio instance + obj = env->NewObject(clazz, m_glue->m_newInstance, this); + m_glue->m_javaProxy = env->NewGlobalRef(obj); + + // Clean up. + if (obj) + env->DeleteLocalRef(obj); + env->DeleteLocalRef(clazz); + checkException(env); + } + + void onPrepared(int duration, int width, int height) { + m_duration = duration / 1000.0f; + m_player->durationChanged(); + m_player->sizeChanged(); + m_player->prepareToPlay(); + } +}; + +MediaPlayerPrivateInterface* MediaPlayerPrivate::create(MediaPlayer* player) +{ + if (player->mediaElementType() == MediaPlayer::Video) + return new MediaPlayerVideoPrivate(player); + return new MediaPlayerAudioPrivate(player); +} + } namespace android { @@ -405,6 +444,13 @@ static void OnEnded(JNIEnv* env, jobject obj, int pointer) { } } +static void OnPaused(JNIEnv* env, jobject obj, int pointer) { + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + player->onPaused(); + } +} + static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointer) { if (!pointer || !poster) return; @@ -416,6 +462,13 @@ static void OnPosterFetched(JNIEnv* env, jobject obj, jobject poster, int pointe player->onPosterFetched(posterNative); } +static void OnBuffering(JNIEnv* env, jobject obj, int percent, int pointer) { + if (pointer) { + WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); + //TODO: player->onBuffering(percent); + } +} + static void OnTimeupdate(JNIEnv* env, jobject obj, int position, int pointer) { if (pointer) { WebCore::MediaPlayerPrivate* player = reinterpret_cast<WebCore::MediaPlayerPrivate*>(pointer); @@ -431,17 +484,36 @@ static JNINativeMethod g_MediaPlayerMethods[] = { (void*) OnPrepared }, { "nativeOnEnded", "(I)V", (void*) OnEnded }, + { "nativeOnPaused", "(I)V", + (void*) OnPaused }, { "nativeOnPosterFetched", "(Landroid/graphics/Bitmap;I)V", (void*) OnPosterFetched }, { "nativeOnTimeupdate", "(II)V", (void*) OnTimeupdate }, }; -int register_mediaplayer(JNIEnv* env) +static JNINativeMethod g_MediaAudioPlayerMethods[] = { + { "nativeOnBuffering", "(II)V", + (void*) OnBuffering }, + { "nativeOnEnded", "(I)V", + (void*) OnEnded }, + { "nativeOnPrepared", "(IIII)V", + (void*) OnPrepared }, + { "nativeOnTimeupdate", "(II)V", + (void*) OnTimeupdate }, +}; + +int register_mediaplayer_video(JNIEnv* env) { return jniRegisterNativeMethods(env, g_ProxyJavaClass, g_MediaPlayerMethods, NELEM(g_MediaPlayerMethods)); } +int register_mediaplayer_audio(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, g_ProxyJavaClassAudio, + g_MediaAudioPlayerMethods, NELEM(g_MediaAudioPlayerMethods)); +} + } #endif // VIDEO diff --git a/WebKit/android/jni/WebCoreJniOnLoad.cpp b/WebKit/android/jni/WebCoreJniOnLoad.cpp index b5bf9dd..86248db 100644 --- a/WebKit/android/jni/WebCoreJniOnLoad.cpp +++ b/WebKit/android/jni/WebCoreJniOnLoad.cpp @@ -82,7 +82,8 @@ extern int register_webstorage(JNIEnv*); extern int register_geolocation_permissions(JNIEnv*); extern int register_mock_geolocation(JNIEnv*); #if ENABLE(VIDEO) -extern int register_mediaplayer(JNIEnv*); +extern int register_mediaplayer_audio(JNIEnv*); +extern int register_mediaplayer_video(JNIEnv*); #endif } @@ -107,7 +108,8 @@ static RegistrationMethod gWebCoreRegMethods[] = { { "GeolocationPermissions", android::register_geolocation_permissions }, { "MockGeolocation", android::register_mock_geolocation }, #if ENABLE(VIDEO) - { "HTML5VideoViewProxy", android::register_mediaplayer }, + { "HTML5Audio", android::register_mediaplayer_audio }, + { "HTML5VideoViewProxy", android::register_mediaplayer_video }, #endif }; diff --git a/WebKit/android/jni/WebViewCore.cpp b/WebKit/android/jni/WebViewCore.cpp index 70e96cd..3f3eb42 100644 --- a/WebKit/android/jni/WebViewCore.cpp +++ b/WebKit/android/jni/WebViewCore.cpp @@ -189,6 +189,24 @@ jobject WebViewCore::getApplicationContext() { return result; } + +struct WebViewCoreStaticMethods { + jmethodID m_supportsMimeType; +} gWebViewCoreStaticMethods; + +// Check whether a media mimeType is supported in Android media framework. +bool WebViewCore::supportsMimeType(const WebCore::String& mimeType) { + JNIEnv* env = JSC::Bindings::getJNIEnv(); + jstring jMimeType = env->NewString(mimeType.characters(), mimeType.length()); + jclass webViewCore = env->FindClass("android/webkit/WebViewCore"); + bool val = env->CallStaticBooleanMethod(webViewCore, + gWebViewCoreStaticMethods.m_supportsMimeType, jMimeType); + checkException(env); + env->DeleteLocalRef(jMimeType); + + return val; +} + // ---------------------------------------------------------------------------- #define GET_NATIVE_VIEW(env, obj) ((WebViewCore*)env->GetIntField(obj, gWebViewCoreFields.m_nativeClass)) @@ -1688,8 +1706,8 @@ void WebViewCore::deleteSelection(int start, int end, int textGeneration) EditorClientAndroid* client = static_cast<EditorClientAndroid*>( m_mainFrame->editor()->client()); client->setUiGeneratedSelectionChange(true); - PlatformKeyboardEvent down(kKeyCodeDel, 0, 0, true, false, false, false); - PlatformKeyboardEvent up(kKeyCodeDel, 0, 0, false, false, false, false); + PlatformKeyboardEvent down(AKEYCODE_DEL, 0, 0, true, false, false, false); + PlatformKeyboardEvent up(AKEYCODE_DEL, 0, 0, false, false, false, false); key(down); key(up); client->setUiGeneratedSelectionChange(false); @@ -3295,6 +3313,11 @@ int register_webviewcore(JNIEnv* env) LOG_ASSERT(gWebViewCoreFields.m_webView, "Unable to find android/webkit/WebViewCore.mWebView"); + gWebViewCoreStaticMethods.m_supportsMimeType = + env->GetStaticMethodID(widget, "supportsMimeType", "(Ljava/lang/String;)Z"); + LOG_ASSERT(gWebViewCoreStaticMethods.m_supportsMimeType == NULL, + "Could not find static method supportsMimeType from WebViewCore"); + return jniRegisterNativeMethods(env, "android/webkit/WebViewCore", gJavaWebViewCoreMethods, NELEM(gJavaWebViewCoreMethods)); } diff --git a/WebKit/android/jni/WebViewCore.h b/WebKit/android/jni/WebViewCore.h index 056dba1..2944599 100644 --- a/WebKit/android/jni/WebViewCore.h +++ b/WebKit/android/jni/WebViewCore.h @@ -578,6 +578,9 @@ namespace android { // if there exists at least on WebViewCore instance then we return the // application context, otherwise NULL is returned. static jobject getApplicationContext(); + + // Check whether a media mimeType is supported in Android media framework. + static bool supportsMimeType(const WebCore::String& mimeType); }; } // namespace android diff --git a/WebKit/android/nav/CacheBuilder.cpp b/WebKit/android/nav/CacheBuilder.cpp index 7ee2a16..f69b23d 100644 --- a/WebKit/android/nav/CacheBuilder.cpp +++ b/WebKit/android/nav/CacheBuilder.cpp @@ -960,7 +960,7 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, FocusTracker* last = &tracker.last(); int lastChildIndex = cachedFrame->size() - 1; while (node == last->mLastChild) { - if (CleanUpContainedNodes(cachedFrame, last, lastChildIndex)) + if (CleanUpContainedNodes(cachedRoot, cachedFrame, last, lastChildIndex)) cacheIndex--; tracker.removeLast(); lastChildIndex = last->mCachedNodeIndex; @@ -1232,6 +1232,10 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, || style->textAlign() == WebCore::RIGHT || style->textAlign() == WebCore::WEBKIT_RIGHT); } + cachedInput.setPaddingLeft(renderText->paddingLeft() + renderText->borderLeft()); + cachedInput.setPaddingTop(renderText->paddingTop() + renderText->borderTop()); + cachedInput.setPaddingRight(renderText->paddingRight() + renderText->borderRight()); + cachedInput.setPaddingBottom(renderText->paddingBottom() + renderText->borderBottom()); } takesFocus = true; bounds = absBounds; @@ -1344,14 +1348,14 @@ void CacheBuilder::BuildFrame(Frame* root, Frame* frame, while (tracker.size() > 1) { FocusTracker* last = &tracker.last(); int lastChildIndex = cachedFrame->size() - 1; - if (CleanUpContainedNodes(cachedFrame, last, lastChildIndex)) + if (CleanUpContainedNodes(cachedRoot, cachedFrame, last, lastChildIndex)) cacheIndex--; tracker.removeLast(); } } -bool CacheBuilder::CleanUpContainedNodes(CachedFrame* cachedFrame, - const FocusTracker* last, int lastChildIndex) +bool CacheBuilder::CleanUpContainedNodes(CachedRoot* cachedRoot, + CachedFrame* cachedFrame, const FocusTracker* last, int lastChildIndex) { // if outer is body, disable outer // or if there's more than one inner, disable outer @@ -1380,9 +1384,12 @@ bool CacheBuilder::CleanUpContainedNodes(CachedFrame* cachedFrame, HasTriggerEvent(lastNode) == false; if (onlyChildCached->parent() == lastCached) onlyChildCached->setParentIndex(lastCached->parentIndex()); + bool hasFocus = lastCached->isFocus() || onlyChildCached->isFocus(); if (outerIsMouseMoveOnly || onlyChild->isKeyboardFocusable(NULL)) *lastCached = *onlyChildCached; cachedFrame->removeLast(); + if (hasFocus) + cachedRoot->setCachedFocus(cachedFrame, cachedFrame->lastNode()); return true; } diff --git a/WebKit/android/nav/CacheBuilder.h b/WebKit/android/nav/CacheBuilder.h index 4ded58d..407d590 100644 --- a/WebKit/android/nav/CacheBuilder.h +++ b/WebKit/android/nav/CacheBuilder.h @@ -215,7 +215,7 @@ private: static bool NodeHasEventListeners(Node* node, AtomicString* eventTypes, int length); void BuildFrame(Frame* root, Frame* frame, CachedRoot* cachedRoot, CachedFrame* cachedFrame); - bool CleanUpContainedNodes(CachedFrame* cachedFrame, + bool CleanUpContainedNodes(CachedRoot* cachedRoot, CachedFrame* cachedFrame, const FocusTracker* last, int lastChildIndex); static bool ConstructTextRect(Text* textNode, InlineTextBox* textBox, int start, int relEnd, int x, int y, diff --git a/WebKit/android/nav/CachedInput.cpp b/WebKit/android/nav/CachedInput.cpp index 924bbca..cba5820 100644 --- a/WebKit/android/nav/CachedInput.cpp +++ b/WebKit/android/nav/CachedInput.cpp @@ -59,6 +59,10 @@ void CachedInput::Debug::print() const DUMP_NAV_LOGD("// int mMaxLength=%d;\n", b->mMaxLength); DUMP_NAV_LOGD("// int mTextSize=%d;\n", b->mTextSize); DUMP_NAV_LOGD("// int mInputType=%d;\n", b->mInputType); + DUMP_NAV_LOGD("// int mPaddingLeft=%d;\n", b->mPaddingLeft); + DUMP_NAV_LOGD("// int mPaddingTop=%d;\n", b->mPaddingTop); + DUMP_NAV_LOGD("// int mPaddingRight=%d;\n", b->mPaddingRight); + DUMP_NAV_LOGD("// int mPaddingBottom=%d;\n", b->mPaddingBottom); DEBUG_PRINT_BOOL(mIsRtlText); DEBUG_PRINT_BOOL(mIsTextField); } diff --git a/WebKit/android/nav/CachedInput.h b/WebKit/android/nav/CachedInput.h index 42cadf1..01b40f9 100644 --- a/WebKit/android/nav/CachedInput.h +++ b/WebKit/android/nav/CachedInput.h @@ -48,20 +48,32 @@ public: bool isTextField() const { return mIsTextField; } int maxLength() const { return mMaxLength; }; const WebCore::String& name() const { return mName; } + int paddingBottom() const { return mPaddingBottom; } + int paddingLeft() const { return mPaddingLeft; } + int paddingRight() const { return mPaddingRight; } + int paddingTop() const { return mPaddingTop; } void setFormPointer(void* form) { mForm = form; } void setInputType(WebCore::HTMLInputElement::InputType type) { mInputType = type; } void setIsRtlText(bool isRtlText) { mIsRtlText = isRtlText; } void setIsTextField(bool isTextField) { mIsTextField = isTextField; } void setMaxLength(int maxLength) { mMaxLength = maxLength; } void setName(const WebCore::String& name) { mName = name; } + void setPaddingBottom(int bottom) { mPaddingBottom = bottom; } + void setPaddingLeft(int left) { mPaddingLeft = left; } + void setPaddingRight(int right) { mPaddingRight = right; } + void setPaddingTop(int top) { mPaddingTop = top; } void setTextSize(int textSize) { mTextSize = textSize; } int textSize() const { return mTextSize; } private: void* mForm; - WebCore::String mName; + WebCore::HTMLInputElement::InputType mInputType; int mMaxLength; + WebCore::String mName; + int mPaddingBottom; + int mPaddingLeft; + int mPaddingRight; + int mPaddingTop; int mTextSize; - WebCore::HTMLInputElement::InputType mInputType; bool mIsRtlText : 1; bool mIsTextField : 1; #if DUMP_NAV_CACHE diff --git a/WebKit/android/nav/FindCanvas.h b/WebKit/android/nav/FindCanvas.h index b9dbeea..34929ec 100644 --- a/WebKit/android/nav/FindCanvas.h +++ b/WebKit/android/nav/FindCanvas.h @@ -220,6 +220,7 @@ public: virtual ~FindOnPage() { delete m_matches; } void clearCurrentLocation() { m_hasCurrentLocation = false; } IntRect currentMatchBounds() const; + int currentMatchIndex() const { return m_findIndex; } bool currentMatchIsInLayer() const; virtual void draw(SkCanvas* , LayerAndroid* ); void findNext(bool forward); diff --git a/WebKit/android/nav/SelectText.cpp b/WebKit/android/nav/SelectText.cpp index e471307..25f9482 100644 --- a/WebKit/android/nav/SelectText.cpp +++ b/WebKit/android/nav/SelectText.cpp @@ -23,15 +23,17 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#define LOG_TAG "webcoreglue" +#define LOG_TAG "webviewglue" #include "CachedPrefix.h" +#include "BidiResolver.h" #include "CachedRoot.h" #include "LayerAndroid.h" #include "SelectText.h" #include "SkBitmap.h" #include "SkBounder.h" #include "SkCanvas.h" +#include "SkGradientShader.h" #include "SkMatrix.h" #include "SkPicture.h" #include "SkPixelXorXfermode.h" @@ -39,26 +41,158 @@ #include "SkRect.h" #include "SkRegion.h" #include "SkUtils.h" +#include "TextRun.h" #ifdef DEBUG_NAV_UI #include "CString.h" #endif +#define VERBOSE_LOGGING 0 +// #define EXTRA_NOISY_LOGGING 1 + +// TextRunIterator has been copied verbatim from GraphicsContext.cpp +namespace WebCore { + +class TextRunIterator { +public: + TextRunIterator() + : m_textRun(0) + , m_offset(0) + { + } + + TextRunIterator(const TextRun* textRun, unsigned offset) + : m_textRun(textRun) + , m_offset(offset) + { + } + + TextRunIterator(const TextRunIterator& other) + : m_textRun(other.m_textRun) + , m_offset(other.m_offset) + { + } + + unsigned offset() const { return m_offset; } + void increment() { m_offset++; } + bool atEnd() const { return !m_textRun || m_offset >= m_textRun->length(); } + UChar current() const { return (*m_textRun)[m_offset]; } + WTF::Unicode::Direction direction() const { return atEnd() ? WTF::Unicode::OtherNeutral : WTF::Unicode::direction(current()); } + + bool operator==(const TextRunIterator& other) + { + return m_offset == other.m_offset && m_textRun == other.m_textRun; + } + + bool operator!=(const TextRunIterator& other) { return !operator==(other); } + +private: + const TextRun* m_textRun; + int m_offset; +}; + +// ReverseBidi is a trimmed-down version of GraphicsContext::drawBidiText() +void ReverseBidi(UChar* chars, int len) { + using namespace WTF::Unicode; + WTF::Vector<UChar> result; + result.reserveCapacity(len); + TextRun run(chars, len); + BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; + bidiResolver.setStatus(BidiStatus(LeftToRight, LeftToRight, LeftToRight, + BidiContext::create(0, LeftToRight, false))); + bidiResolver.setPosition(TextRunIterator(&run, 0)); + bidiResolver.createBidiRunsForLine(TextRunIterator(&run, len)); + if (!bidiResolver.runCount()) + return; + BidiCharacterRun* bidiRun = bidiResolver.firstRun(); + while (bidiRun) { + int bidiStart = bidiRun->start(); + int bidiStop = bidiRun->stop(); + int size = result.size(); + int bidiCount = bidiStop - bidiStart; + result.append(chars + bidiStart, bidiCount); + if (bidiRun->level() % 2) { + UChar* start = &result[size]; + UChar* end = start + bidiCount; + // reverse the order of any RTL substrings + while (start < end) { + UChar temp = *start; + *start++ = *--end; + *end = temp; + } + start = &result[size]; + end = start + bidiCount - 1; + // if the RTL substring had a surrogate pair, restore its order + while (start < end) { + UChar trail = *start++; + if (!U16_IS_SURROGATE(trail)) + continue; + start[-1] = *start; // lead + *start++ = trail; + } + } + bidiRun = bidiRun->next(); + } + bidiResolver.deleteRuns(); + memcpy(chars, &result[0], len * sizeof(UChar)); +} + +} + namespace android { +/* SpaceBounds and SpaceCanvas are used to measure the left and right side + * bearings of two consecutive glyphs to help determine if the glyphs were + * originally laid out with a space character between the glyphs. + */ +class SpaceBounds : public SkBounder { +public: + virtual bool onIRectGlyph(const SkIRect& , const SkBounder::GlyphRec& rec) + { + mFirstGlyph = mLastGlyph; + mLastGlyph = rec; + return false; + } + + SkBounder::GlyphRec mFirstGlyph; + SkBounder::GlyphRec mLastGlyph; +}; + +class SpaceCanvas : public SkCanvas { +public: + SpaceCanvas(const SkIRect& area) + { + setBounder(&mBounder); + SkBitmap bitmap; + bitmap.setConfig(SkBitmap::kARGB_8888_Config, area.width(), + area.height()); + setBitmapDevice(bitmap); + translate(SkIntToScalar(-area.fLeft), SkIntToScalar(-area.fTop)); + } + + SpaceBounds mBounder; +}; + +#define HYPHEN_MINUS 0x2D // ASCII hyphen +#define SOLIDUS 0x2F // ASCII slash +#define REVERSE_SOLIDUS 0x5C // ASCII backslash +#define HYPHEN 0x2010 // unicode hyphen, first in range of dashes +#define HORZ_BAR 0x2015 // unicode horizontal bar, last in range of dashes +#define TOUCH_SLOP 10 // additional distance from character rect when hit + class CommonCheck : public SkBounder { public: - CommonCheck() : mMatrix(NULL), mPaint(NULL) {} - - virtual void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, - const void* text) { - mMatrix = &matrix; - mPaint = &paint; - mText = static_cast<const uint16_t*>(text); - mY = y; - mBase = mBottom = mTop = INT_MAX; + CommonCheck(int width, int height) + : mHeight(height) + , mLastUni(0) + , mMatrix(0) + , mPaint(0) + , mWidth(width) + { + mLastGlyph.fGlyphID = static_cast<uint16_t>(-1); + reset(); } - + int base() { if (mBase == INT_MAX) { SkPoint result; @@ -78,7 +212,149 @@ public: } return mBottom; } - + +#if DEBUG_NAV_UI + // make current (possibily uncomputed) value visible for debugging + int bottomDebug() const + { + return mBottom; + } +#endif + + bool addNewLine(const SkBounder::GlyphRec& rec) + { + SkFixed lineSpacing = SkFixedAbs(mLastGlyph.fLSB.fY - rec.fLSB.fY); + SkFixed lineHeight = SkIntToFixed(bottom() - top()); + return lineSpacing >= lineHeight + (lineHeight >> 1); // 1.5 + } + + bool addSpace(const SkBounder::GlyphRec& rec) + { + bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY; + if (((mLastUni >= HYPHEN && mLastUni <= HORZ_BAR) + || mLastUni == HYPHEN_MINUS || mLastUni == SOLIDUS + || mLastUni == REVERSE_SOLIDUS) && newBaseLine) + { + return false; + } + return isSpace(rec); + } + + void finishGlyph() + { + mLastGlyph = mLastCandidate; + mLastUni = mLastUniCandidate; + } + + SkUnichar getUniChar(const SkBounder::GlyphRec& rec) + { + SkUnichar unichar; + SkPaint utfPaint = *mPaint; + utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); + utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &unichar); + return unichar; + } + + bool isSpace(const SkBounder::GlyphRec& rec) + { + DBG_NAV_LOGD("mLastGlyph=((%g, %g),(%g, %g), %d)" + " rec=((%g, %g),(%g, %g), %d)" + " mMinSpaceWidth=%g mLastUni=0x%04x '%c'", + SkFixedToScalar(mLastGlyph.fLSB.fX), + SkFixedToScalar(mLastGlyph.fLSB.fY), + SkFixedToScalar(mLastGlyph.fRSB.fX), + SkFixedToScalar(mLastGlyph.fRSB.fY), mLastGlyph.fGlyphID, + SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fLSB.fY), + SkFixedToScalar(rec.fRSB.fX), SkFixedToScalar(rec.fRSB.fY), + rec.fGlyphID, + SkFixedToScalar(mMinSpaceWidth), + mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?'); + bool newBaseLine = mLastGlyph.fLSB.fY != rec.fLSB.fY; + if (newBaseLine) + return true; + SkFixed gapOne = mLastGlyph.fLSB.fX - rec.fRSB.fX; + SkFixed gapTwo = rec.fLSB.fX - mLastGlyph.fRSB.fX; + if (gapOne < 0 && gapTwo < 0) + return false; // overlaps + uint16_t test[2]; + test[0] = mLastGlyph.fGlyphID; + test[1] = rec.fGlyphID; + SkIRect area; + area.set(0, 0, mWidth, mHeight); + SpaceCanvas spaceChecker(area); + spaceChecker.drawText(test, sizeof(test), + SkFixedToScalar(mLastGlyph.fLSB.fX), + SkFixedToScalar(mLastGlyph.fLSB.fY), *mPaint); + const SkBounder::GlyphRec& g1 = spaceChecker.mBounder.mFirstGlyph; + const SkBounder::GlyphRec& g2 = spaceChecker.mBounder.mLastGlyph; + DBG_NAV_LOGD("g1=(%g, %g, %g, %g) g2=(%g, %g, %g, %g)", + SkFixedToScalar(g1.fLSB.fX), SkFixedToScalar(g1.fLSB.fY), + SkFixedToScalar(g1.fRSB.fX), SkFixedToScalar(g1.fRSB.fY), + SkFixedToScalar(g2.fLSB.fX), SkFixedToScalar(g2.fLSB.fY), + SkFixedToScalar(g2.fRSB.fX), SkFixedToScalar(g2.fRSB.fY)); + gapOne = SkFixedAbs(gapOne); + gapTwo = SkFixedAbs(gapTwo); + SkFixed gap = gapOne < gapTwo ? gapOne : gapTwo; + SkFixed overlap = g2.fLSB.fX - g1.fRSB.fX; + if (overlap < 0) + gap -= overlap; + DBG_NAV_LOGD("gap=%g overlap=%g gapOne=%g gapTwo=%g minSpaceWidth()=%g", + SkFixedToScalar(gap), SkFixedToScalar(overlap), + SkFixedToScalar(gapOne), SkFixedToScalar(gapTwo), + SkFixedToScalar(minSpaceWidth())); + // FIXME: the -1/8 below takes care of slop beween the computed gap + // and the actual space width -- it's a rounding error from + // moving from fixed to float and back and could be much smaller. + return gap >= minSpaceWidth() - SK_Fixed1 / 8; + } + + SkFixed minSpaceWidth() + { + if (mMinSpaceWidth == SK_FixedMax) { + SkPaint charPaint = *mPaint; + charPaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); + SkScalar width = charPaint.measureText(" ", 1); + mMinSpaceWidth = SkScalarToFixed(width * mMatrix->getScaleX()); + DBG_NAV_LOGD("width=%g matrix sx/sy=(%g, %g) tx/ty=(%g, %g)" + " mMinSpaceWidth=%g", width, + mMatrix->getScaleX(), mMatrix->getScaleY(), + mMatrix->getTranslateX(), mMatrix->getTranslateY(), + SkFixedToScalar(mMinSpaceWidth)); + } + return mMinSpaceWidth; + } + + void recordGlyph(const SkBounder::GlyphRec& rec) + { + mLastCandidate = rec; + mLastUniCandidate = getUniChar(rec); + } + + void reset() + { + mMinSpaceWidth = SK_FixedMax; // mark as uninitialized + mBase = mBottom = mTop = INT_MAX; // mark as uninitialized + } + + void set(CommonCheck& check) + { + mLastGlyph = check.mLastGlyph; + mLastUni = check.mLastUni; + mMatrix = check.mMatrix; + mPaint = check.mPaint; + reset(); + } + + void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, + const void* text) + { + mMatrix = &matrix; + mPaint = &paint; + mText = static_cast<const uint16_t*>(text); + mY = y; + reset(); + } + int top() { if (mTop == INT_MAX) { SkPoint result; @@ -89,127 +365,568 @@ public: } return mTop; } - -protected: + +#if DEBUG_NAV_UI + // make current (possibily uncomputed) value visible for debugging + int topDebug() const + { + return mTop; + } +#endif + +protected: + int mHeight; + SkBounder::GlyphRec mLastCandidate; + SkBounder::GlyphRec mLastGlyph; + SkUnichar mLastUni; + SkUnichar mLastUniCandidate; const SkMatrix* mMatrix; const SkPaint* mPaint; const uint16_t* mText; + int mWidth; SkScalar mY; +private: int mBase; int mBottom; + SkFixed mMinSpaceWidth; int mTop; + friend class EdgeCheck; }; class FirstCheck : public CommonCheck { public: - FirstCheck(int x, int y) - : mDistance(INT_MAX), mFocusX(x), mFocusY(y) { - mBestBounds.setEmpty(); + FirstCheck(int x, int y, const SkIRect& area) + : INHERITED(area.width(), area.height()) + , mFocusX(x - area.fLeft) + , mFocusY(y - area.fTop) + , mRecordGlyph(false) + { + reset(); } - const SkIRect& bestBounds() { - DBG_NAV_LOGD("mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", + const SkIRect& adjustedBounds(const SkIRect& area, int* base) + { + *base = mBestBase + area.fTop; + mBestBounds.offset(area.fLeft, area.fTop); + DBG_NAV_LOGD("FirstCheck mBestBounds:(%d, %d, %d, %d) mTop=%d mBottom=%d", mBestBounds.fLeft, mBestBounds.fTop, mBestBounds.fRight, - mBestBounds.fBottom, mTop, mBottom); - return mBestBounds; + mBestBounds.fBottom, topDebug(), bottomDebug()); + return mBestBounds; } - - void offsetBounds(int dx, int dy) { - mBestBounds.offset(dx, dy); - } - - virtual bool onIRect(const SkIRect& rect) { - int dx = ((rect.fLeft + rect.fRight) >> 1) - mFocusX; - int dy = ((top() + bottom()) >> 1) - mFocusY; + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + /* compute distance from rectangle center. + * centerX = (rect.L + rect.R) / 2 + * multiply centerX and comparison x by 2 to retain better precision + */ + int dx = rect.fLeft + rect.fRight - (mFocusX << 1); + int dy = top() + bottom() - (mFocusY << 1); int distance = dx * dx + dy * dy; #ifdef EXTRA_NOISY_LOGGING if (distance < 500 || abs(distance - mDistance) < 500) - DBG_NAV_LOGD("distance=%d mDistance=%d", distance, mDistance); + DBG_NAV_LOGD("FirstCheck distance=%d mDistance=%d", distance, mDistance); #endif if (mDistance > distance) { - mDistance = distance; + mBestBase = base(); mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); -#ifdef EXTRA_NOISY_LOGGING - DBG_NAV_LOGD("mBestBounds={%d,%d,r=%d,b=%d}", - mBestBounds.fLeft, mBestBounds.fTop, - mBestBounds.fRight, mBestBounds.fBottom); -#endif + if (distance < 100) { + DBG_NAV_LOGD("FirstCheck mBestBounds={%d,%d,r=%d,b=%d} distance=%d", + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, distance >> 2); + } + mDistance = distance; + if (mRecordGlyph) + recordGlyph(rec); } return false; } + + void reset() + { + mBestBounds.setEmpty(); + mDistance = INT_MAX; + } + + void setRecordGlyph() + { + mRecordGlyph = true; + } + protected: + int mBestBase; SkIRect mBestBounds; int mDistance; int mFocusX; int mFocusY; + bool mRecordGlyph; +private: + typedef CommonCheck INHERITED; }; -class MultilineBuilder : public CommonCheck { +class EdgeCheck : public FirstCheck { public: - MultilineBuilder(const SkIRect& start, const SkIRect& end, int dx, int dy, - SkRegion* region) - : mStart(start), mEnd(end), mSelectRegion(region), mCapture(false) { + EdgeCheck(int x, int y, const SkIRect& area, CommonCheck& last, bool left) + : INHERITED(x, y, area) + , mLast(area.width(), area.height()) + , mLeft(left) + { + mLast.set(last); + mLastGlyph = last.mLastGlyph; + mLastUni = last.mLastUni; + } + + bool adjacent() + { + return !mLast.isSpace(mLastGlyph); + } + + const SkIRect& bestBounds(int* base) + { + *base = mBestBase; + return mBestBounds; + } + + virtual bool onIRectGlyph(const SkIRect& rect, + const SkBounder::GlyphRec& rec) + { + int dx = mLeft ? mFocusX - rect.fRight : rect.fLeft - mFocusX; + int dy = ((top() + bottom()) >> 1) - mFocusY; + if (mLeft ? mFocusX <= rect.fLeft : mFocusX >= rect.fRight) { + if (abs(dx) <= 10 && abs(dy) <= 10) { + DBG_NAV_LOGD("EdgeCheck fLeft=%d fRight=%d mFocusX=%d dx=%d dy=%d", + rect.fLeft, rect.fRight, mFocusX, dx, dy); + } + return false; + } + int distance = dx * dx + dy * dy; + if (mDistance > distance) { + if (rec.fLSB == mLastGlyph.fLSB && rec.fRSB == mLastGlyph.fRSB) { + DBG_NAV_LOGD("dup rec.fLSB.fX=%g rec.fRSB.fX=%g", + SkFixedToScalar(rec.fLSB.fX), SkFixedToScalar(rec.fRSB.fX)); + return false; + } + recordGlyph(rec); + mDistance = distance; + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + if (distance <= 100) { + DBG_NAV_LOGD("EdgeCheck mBestBounds={%d,%d,r=%d,b=%d} distance=%d", + mBestBounds.fLeft, mBestBounds.fTop, + mBestBounds.fRight, mBestBounds.fBottom, distance); + } + } + return false; + } + + void shiftStart(SkIRect bounds) + { + DBG_NAV_LOGD("EdgeCheck mFocusX=%d mLeft=%s bounds.fLeft=%d bounds.fRight=%d", + mFocusX, mLeft ? "true" : "false", bounds.fLeft, bounds.fRight); + reset(); + mFocusX = mLeft ? bounds.fLeft : bounds.fRight; + mLast.set(*this); + } + +protected: + CommonCheck mLast; + bool mLeft; +private: + typedef FirstCheck INHERITED; +}; + +class FindFirst : public CommonCheck { +public: + FindFirst(int width, int height) + : INHERITED(width, height) + { + mBestBounds.set(width, height, width, height); + } + + const SkIRect& bestBounds(int* base) + { + *base = mBestBase; + return mBestBounds; + } + + virtual bool onIRect(const SkIRect& rect) + { + if (mBestBounds.isEmpty()) { + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + } + return false; + } + +protected: + int mBestBase; + SkIRect mBestBounds; +private: + typedef CommonCheck INHERITED; +}; + +class FindLast : public FindFirst { +public: + FindLast(int width, int height) + : INHERITED(width, height) + { + mBestBounds.setEmpty(); + } + + virtual bool onIRect(const SkIRect& rect) + { + mBestBase = base(); + mBestBounds.set(rect.fLeft, top(), rect.fRight, bottom()); + return false; + } + +private: + typedef FindFirst INHERITED; +}; + +static bool baseLinesAgree(const SkIRect& rectA, int baseA, + const SkIRect& rectB, int baseB) +{ + return (rectA.fTop < baseB && rectA.fBottom >= baseB) + || (rectB.fTop < baseA && rectB.fBottom >= baseA); +} + +class BuilderCheck : public CommonCheck { +protected: + enum IntersectionType { + NO_INTERSECTION, // debugging printf expects this to equal zero + LAST_INTERSECTION, // debugging printf expects this to equal one + WAIT_FOR_INTERSECTION + }; + + BuilderCheck(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area) + : INHERITED(area.width(), area.height()) + , mCapture(false) + , mEnd(end) + , mEndBase(endBase) + , mStart(start) + , mStartBase(startBase) + { + mEnd.offset(-area.fLeft, -area.fTop); + mEndBase -= area.fTop; + mEndExtra.setEmpty(); mLast.setEmpty(); mLastBase = INT_MAX; - mStart.offset(-dx, -dy); - mEnd.offset(-dx, -dy); + mSelectRect.setEmpty(); + mStart.offset(-area.fLeft, -area.fTop); + mStartBase -= area.fTop; + mStartExtra.setEmpty(); + DBG_NAV_LOGD(" mStart=(%d,%d,r=%d,b=%d) mStartBase=%d" + " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d", + mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase, + mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase); } - virtual bool onIRect(const SkIRect& rect) { - bool captureLast = false; - if ((rect.fLeft == mStart.fLeft && rect.fRight == mStart.fRight && - top() == mStart.fTop && bottom() == mStart.fBottom) || - (rect.fLeft == mEnd.fLeft && rect.fRight == mEnd.fRight && - top() == mEnd.fTop && bottom() == mEnd.fBottom)) { - captureLast = mCapture; - mCapture ^= true; + int checkFlipRect(const SkIRect& full, int fullBase) { + mCollectFull = false; + // is the text to collect between the selection top and bottom? + if (fullBase < mStart.fTop || fullBase > mEnd.fBottom) { + if (VERBOSE_LOGGING && !mLast.isEmpty()) DBG_NAV_LOGD("%s 1" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase); + return mLastIntersects; } - if (mCapture || captureLast) { - SkIRect full; - full.set(rect.fLeft, top(), rect.fRight, bottom()); - if ((mLast.fTop < base() && mLast.fBottom >= base()) - || (mLastBase <= full.fBottom && mLastBase > full.fTop)) { - if (full.fLeft > mLast.fRight) - full.fLeft = mLast.fRight; - else if (full.fRight < mLast.fLeft) - full.fRight = mLast.fLeft; - } - mSelectRegion->op(full, SkRegion::kUnion_Op); - DBG_NAV_LOGD("MultilineBuilder full=(%d,%d,r=%d,b=%d)", - full.fLeft, full.fTop, full.fRight, full.fBottom); + // is the text to the left of the selection start? + if (baseLinesAgree(mStart, mStartBase, full, fullBase) + && full.fLeft < mStart.fLeft) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 2" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mStart=(%d,%d,r=%d,b=%d) mStartBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mStart.fLeft, mStart.fTop, mStart.fRight, mStart.fBottom, mStartBase); + mStartExtra.join(full); + return mLastIntersects; + } + // is the text to the right of the selection end? + if (baseLinesAgree(mEnd, mEndBase, full, fullBase) + && full.fRight > mEnd.fRight) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 3" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mEnd=(%d,%d,r=%d,b=%d) mEndBase=%d", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mEnd.fLeft, mEnd.fTop, mEnd.fRight, mEnd.fBottom, mEndBase); + mEndExtra.join(full); + return mLastIntersects; + } + int spaceGap = SkFixedRound(minSpaceWidth() * 3); + // should text to the left of the start be added to the selection bounds? + if (!mStartExtra.isEmpty()) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)" + " mStartExtra=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom); + if (mStartExtra.fRight + spaceGap >= mStart.fLeft) + mSelectRect.join(mStartExtra); + mStartExtra.setEmpty(); + } + // should text to the right of the end be added to the selection bounds? + if (!mEndExtra.isEmpty()) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)" + " mEndExtra=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom); + if (mEndExtra.fLeft - spaceGap <= mEnd.fRight) + mSelectRect.join(mEndExtra); + mEndExtra.setEmpty(); + } + bool sameBaseLine = baseLinesAgree(mLast, mLastBase, full, fullBase); + bool adjacent = (full.fLeft - mLast.fRight) < spaceGap; + // is this the first, or are there more characters on the same line? + if (mLast.isEmpty() || (sameBaseLine && adjacent)) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("WAIT_FOR_INTERSECTION" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " mSelectRect=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom); + mLast.join(full); + mLastIntersects = SkIRect::Intersects(mLast, mSelectRect); + return WAIT_FOR_INTERSECTION; + } + if (VERBOSE_LOGGING) DBG_NAV_LOGD("%s 4" + " mLast=(%d,%d,r=%d,b=%d) mLastBase=%d" + " full=(%d,%d,r=%d,b=%d) fullBase=%d" + " mSelectRect=(%d,%d,r=%d,b=%d)" + " mStartExtra=(%d,%d,r=%d,b=%d)" + " mEndExtra=(%d,%d,r=%d,b=%d)", + mLastIntersects ? "LAST_INTERSECTION" : "NO_INTERSECTION", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, mLastBase, + full.fLeft, full.fTop, full.fRight, full.fBottom, fullBase, + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom, + mStartExtra.fLeft, mStartExtra.fTop, mStartExtra.fRight, mStartExtra.fBottom, + mEndExtra.fLeft, mEndExtra.fTop, mEndExtra.fRight, mEndExtra.fBottom); + // after the caller determines what to do with the last collection, + // start the collection over with full and fullBase. + mCollectFull = true; + return mLastIntersects; + } + + bool resetLast(const SkIRect& full, int fullBase) + { + if (mCollectFull) { mLast = full; - mLastBase = base(); - if (mStart == mEnd) - mCapture = false; + mLastBase = fullBase; + mLastIntersects = SkIRect::Intersects(mLast, mSelectRect); + } else { + mLast.setEmpty(); + mLastBase = INT_MAX; + mLastIntersects = false; } - return false; + return mCollectFull; } -protected: - SkIRect mStart; + + void setFlippedState() + { + mSelectRect = mStart; + mSelectRect.join(mEnd); + DBG_NAV_LOGD("mSelectRect=(%d,%d,r=%d,b=%d)", + mSelectRect.fLeft, mSelectRect.fTop, mSelectRect.fRight, mSelectRect.fBottom); + mLast.setEmpty(); + mLastBase = INT_MAX; + mLastIntersects = NO_INTERSECTION; + } + + bool mCapture; + bool mCollectFull; SkIRect mEnd; + int mEndBase; + SkIRect mEndExtra; + bool mFlipped; SkIRect mLast; int mLastBase; + int mLastIntersects; + SkIRect mSelectRect; + SkIRect mStart; + SkIRect mStartExtra; + int mStartBase; +private: + typedef CommonCheck INHERITED; + +}; + +class MultilineBuilder : public BuilderCheck { +public: + MultilineBuilder(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area, SkRegion* region) + : INHERITED(start, startBase, end, endBase, area) + , mSelectRegion(region) + { + mFlipped = false; + } + + void addLastToRegion() { + if (VERBOSE_LOGGING) DBG_NAV_LOGD(" mLast=(%d,%d,r=%d,b=%d)", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom); + mSelectRegion->op(mLast, SkRegion::kUnion_Op); + } + + void finish() { + if (!mFlipped || !mLastIntersects) + return; + addLastToRegion(); + } + + // return true if capture end was not found after capture begin + bool flipped() { + DBG_NAV_LOGD("flipped=%s", mCapture ? "true" : "false"); + if (!mCapture) + return false; + mFlipped = true; + setFlippedState(); + mSelectRegion->setEmpty(); + return true; + } + + virtual bool onIRect(const SkIRect& rect) { + SkIRect full; + full.set(rect.fLeft, top(), rect.fRight, bottom()); + int fullBase = base(); + if (mFlipped) { + int intersectType = checkFlipRect(full, fullBase); + if (intersectType == LAST_INTERSECTION) + addLastToRegion(); + if (intersectType != WAIT_FOR_INTERSECTION) + resetLast(full, fullBase); + return false; + } + if (full == mStart) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mStart full=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom); + mCapture = true; + } + if (mCapture) { + bool sameLines = baseLinesAgree(mLast, mLastBase, full, fullBase); + if (sameLines) + mLast.join(full); + if (!sameLines || full == mEnd) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("finish mLast=(%d,%d,r=%d,b=%d)", + mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom); + addLastToRegion(); + mLast = full; + mLastBase = fullBase; + } + } + if (full == mEnd) { + if (VERBOSE_LOGGING) DBG_NAV_LOGD("full == mEnd full=(%d,%d,r=%d,b=%d)", + full.fLeft, full.fTop, full.fRight, full.fBottom); + mCapture = false; + } + return false; + } + +protected: SkRegion* mSelectRegion; - bool mCapture; +private: + typedef BuilderCheck INHERITED; }; -#define HYPHEN_MINUS 0x2D // ASCII hyphen -#define HYPHEN 0x2010 // unicode hyphen, first in range of dashes -#define HORZ_BAR 0x2015 // unicode horizontal bar, last in range of dashes +static inline bool compareBounds(const SkIRect* first, const SkIRect* second) +{ + return first->fTop < second->fTop; +} -class TextExtractor : public CommonCheck { +class TextExtractor : public BuilderCheck { public: - TextExtractor(const SkRegion& region) : mSelectRegion(region), - mSkipFirstSpace(true) { // don't start with a space + TextExtractor(const SkIRect& start, int startBase, const SkIRect& end, + int endBase, const SkIRect& area, bool flipped) + : INHERITED(start, startBase, end, endBase, area) + , mSelectStartIndex(-1) + , mSkipFirstSpace(true) // don't start with a space + { + mFlipped = flipped; + if (flipped) + setFlippedState(); } - virtual void setUp(const SkPaint& paint, const SkMatrix& matrix, SkScalar y, - const void* text) { - INHERITED::setUp(paint, matrix, y, text); - SkPaint charPaint = paint; - charPaint.setTextEncoding(SkPaint::kUTF8_TextEncoding); - mMinSpaceWidth = std::max(0, SkScalarToFixed( - charPaint.measureText(" ", 1)) - SK_Fixed1); + void addCharacter(const SkBounder::GlyphRec& rec) + { + if (mSelectStartIndex < 0) + mSelectStartIndex = mSelectText.count(); + if (!mSkipFirstSpace) { + if (addNewLine(rec)) { + DBG_NAV_LOG("write new line"); + *mSelectText.append() = '\n'; + *mSelectText.append() = '\n'; + } else if (addSpace(rec)) { + DBG_NAV_LOG("write space"); + *mSelectText.append() = ' '; + } + } else + mSkipFirstSpace = false; + recordGlyph(rec); + finishGlyph(); + if (VERBOSE_LOGGING) DBG_NAV_LOGD("glyphID=%d uni=%d '%c'", rec.fGlyphID, + mLastUni, mLastUni && mLastUni < 0x7f ? mLastUni : '?'); + if (mLastUni) { + uint16_t chars[2]; + size_t count = SkUTF16_FromUnichar(mLastUni, chars); + *mSelectText.append() = chars[0]; + if (count == 2) + *mSelectText.append() = chars[1]; + } + } + + void addLast() + { + *mSelectBounds.append() = mLast; + *mSelectStart.append() = mSelectStartIndex; + *mSelectEnd.append() = mSelectText.count(); + } + + /* Text characters are collected before it's been determined that the + characters are part of the selection. The bounds describe valid parts + of the selection, but the bounds are out of order. + + This sorts the characters by sorting the bounds, then copying the + characters that were captured. + */ + void finish() + { + if (mLastIntersects) + addLast(); + Vector<SkIRect*> sortedBounds; + SkTDArray<uint16_t> temp; + int index; + DBG_NAV_LOGD("mSelectBounds.count=%d text=%d", mSelectBounds.count(), + mSelectText.count()); + for (index = 0; index < mSelectBounds.count(); index++) + sortedBounds.append(&mSelectBounds[index]); + std::sort(sortedBounds.begin(), sortedBounds.end(), compareBounds); + int lastEnd = -1; + for (index = 0; index < mSelectBounds.count(); index++) { + int order = sortedBounds[index] - &mSelectBounds[0]; + int start = mSelectStart[order]; + int end = mSelectEnd[order]; + DBG_NAV_LOGD("order=%d start=%d end=%d top=%d", order, start, end, + mSelectBounds[order].fTop); + int count = temp.count(); + if (count > 0 && temp[count - 1] != '\n' && start != lastEnd) { + // always separate paragraphs when original text is out of order + DBG_NAV_LOG("write new line"); + *temp.append() = '\n'; + *temp.append() = '\n'; + } + temp.append(end - start, &mSelectText[start]); + lastEnd = end; + } + mSelectText.swap(temp); } virtual bool onIRectGlyph(const SkIRect& rect, @@ -217,72 +934,68 @@ public: { SkIRect full; full.set(rect.fLeft, top(), rect.fRight, bottom()); - if (mSelectRegion.contains(full)) { - if (!mSkipFirstSpace && (mLastUni < HYPHEN || mLastUni > HORZ_BAR) - && mLastUni != HYPHEN_MINUS - && (mLastGlyph.fLSB.fY != rec.fLSB.fY // new baseline - || mLastGlyph.fLSB.fX > rec.fLSB.fX // glyphs are LTR - || mLastGlyph.fRSB.fX + mMinSpaceWidth < rec.fLSB.fX)) { - DBG_NAV_LOGD("TextExtractor append space" - " mLast=(%d,%d,r=%d,b=%d) mLastGlyph=((%g,%g),(%g,%g),%d)" - " full=(%d,%d,r=%d,b=%d) rec=((%g,%g),(%g,%g),%d)" - " mMinSpaceWidth=%g", - mLast.fLeft, mLast.fTop, mLast.fRight, mLast.fBottom, - SkFixedToScalar(mLastGlyph.fLSB.fX), - SkFixedToScalar(mLastGlyph.fLSB.fY), - SkFixedToScalar(mLastGlyph.fRSB.fX), - SkFixedToScalar(mLastGlyph.fRSB.fY), mLastGlyph.fGlyphID, - full.fLeft, full.fTop, full.fRight, full.fBottom, - SkFixedToScalar(rec.fLSB.fX), - SkFixedToScalar(rec.fLSB.fY), - SkFixedToScalar(rec.fRSB.fX), - SkFixedToScalar(rec.fRSB.fY), rec.fGlyphID, - SkFixedToScalar(mMinSpaceWidth)); - *mSelectText.append() = ' '; - } else - mSkipFirstSpace = false; - DBG_NAV_LOGD("TextExtractor [%02x] append full=(%d,%d,r=%d,b=%d)", - rec.fGlyphID, full.fLeft, full.fTop, full.fRight, full.fBottom); - SkPaint utfPaint = *mPaint; - utfPaint.setTextEncoding(SkPaint::kUTF16_TextEncoding); - utfPaint.glyphsToUnichars(&rec.fGlyphID, 1, &mLastUni); - if (mLastUni) { - uint16_t chars[2]; - size_t count = SkUTF16_FromUnichar(mLastUni, chars); - *mSelectText.append() = chars[0]; - if (count == 2) - *mSelectText.append() = chars[1]; + int fullBase = base(); + if (mFlipped) { + int intersectType = checkFlipRect(full, fullBase); + if (WAIT_FOR_INTERSECTION == intersectType) + addCharacter(rec); // may not be copied + else { + if (LAST_INTERSECTION == intersectType) + addLast(); + else + mSkipFirstSpace = true; + mSelectStartIndex = -1; + if (resetLast(full, fullBase)) + addCharacter(rec); // may not be copied } - mLast = full; - mLastGlyph = rec; - } else { - mSkipFirstSpace = true; - DBG_NAV_LOGD("TextExtractor [%02x] skip full=(%d,%d,r=%d,b=%d)", - rec.fGlyphID, full.fLeft, full.fTop, full.fRight, full.fBottom); + return false; } + if (full == mStart) + mCapture = true; + if (mCapture) + addCharacter(rec); + else + mSkipFirstSpace = true; + if (full == mEnd) + mCapture = false; return false; } WebCore::String text() { + if (mFlipped) + finish(); + // the text has been copied in visual order. Reverse as needed if + // result contains right-to-left characters. + const uint16_t* start = mSelectText.begin(); + const uint16_t* end = mSelectText.end(); + while (start < end) { + SkUnichar ch = SkUTF16_NextUnichar(&start); + WTF::Unicode::Direction charDirection = WTF::Unicode::direction(ch); + if (WTF::Unicode::RightToLeftArabic == charDirection + || WTF::Unicode::RightToLeft == charDirection) { + WebCore::ReverseBidi(mSelectText.begin(), mSelectText.count()); + break; + } + } return WebCore::String(mSelectText.begin(), mSelectText.count()); } protected: - const SkRegion& mSelectRegion; + SkIRect mEmpty; + SkTDArray<SkIRect> mSelectBounds; + SkTDArray<int> mSelectEnd; + SkTDArray<int> mSelectStart; + int mSelectStartIndex; SkTDArray<uint16_t> mSelectText; - SkIRect mLast; - SkBounder::GlyphRec mLastGlyph; - SkUnichar mLastUni; - SkFixed mMinSpaceWidth; bool mSkipFirstSpace; private: - typedef CommonCheck INHERITED; + typedef BuilderCheck INHERITED; }; class TextCanvas : public SkCanvas { public: - TextCanvas(CommonCheck* bounder, const SkPicture& picture, const SkIRect& area) + TextCanvas(CommonCheck* bounder, const SkIRect& area) : mBounder(*bounder) { setBounder(bounder); SkBitmap bitmap; @@ -340,49 +1053,240 @@ public: CommonCheck& mBounder; }; -void CopyPaste::buildSelection(const SkPicture& picture, const SkIRect& area, - const SkIRect& selStart, const SkIRect& selEnd, SkRegion* region) { +static bool buildSelection(const SkPicture& picture, const SkIRect& area, + const SkIRect& selStart, int startBase, + const SkIRect& selEnd, int endBase, SkRegion* region) +{ DBG_NAV_LOGD("area=(%d, %d, %d, %d) selStart=(%d, %d, %d, %d)" - " selEnd=(%d, %d, %d, %d)", + " selEnd=(%d, %d, %d, %d)", area.fLeft, area.fTop, area.fRight, area.fBottom, selStart.fLeft, selStart.fTop, selStart.fRight, selStart.fBottom, selEnd.fLeft, selEnd.fTop, selEnd.fRight, selEnd.fBottom); - MultilineBuilder builder(selStart, selEnd, area.fLeft, area.fTop, region); - TextCanvas checker(&builder, picture, area); + MultilineBuilder builder(selStart, startBase, selEnd, endBase, area, region); + TextCanvas checker(&builder, area); checker.drawPicture(const_cast<SkPicture&>(picture)); + bool flipped = builder.flipped(); + if (flipped) { + TextCanvas checker(&builder, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + } + builder.finish(); region->translate(area.fLeft, area.fTop); + return flipped; } -SkIRect CopyPaste::findClosest(const SkPicture& picture, const SkIRect& area, - int x, int y) { - FirstCheck _check(x - area.fLeft, y - area.fTop); - DBG_NAV_LOGD("area=(%d, %d, %d, %d) x=%d y=%d", area.fLeft, area.fTop, - area.fRight, area.fBottom, x, y); - TextCanvas checker(&_check, picture, area); +static SkIRect findClosest(FirstCheck& _check, const SkPicture& picture, + const SkIRect& area, int* base) +{ + DBG_NAV_LOGD("area=(%d, %d, %d, %d)", area.fLeft, area.fTop, + area.fRight, area.fBottom); + TextCanvas checker(&_check, area); checker.drawPicture(const_cast<SkPicture&>(picture)); - _check.offsetBounds(area.fLeft, area.fTop); - return _check.bestBounds(); + _check.finishGlyph(); + return _check.adjustedBounds(area, base); } -WebCore::String CopyPaste::text(const SkPicture& picture, const SkIRect& area, - const SkRegion& region) { - SkRegion copy = region; - copy.translate(-area.fLeft, -area.fTop); - const SkIRect& bounds = copy.getBounds(); - DBG_NAV_LOGD("area=(%d, %d, %d, %d) region=(%d, %d, %d, %d)", - area.fLeft, area.fTop, area.fRight, area.fBottom, - bounds.fLeft, bounds.fTop, bounds.fRight, bounds.fBottom); - TextExtractor extractor(copy); - TextCanvas checker(&extractor, picture, area); +static SkIRect findEdge(const SkPicture& picture, const SkIRect& area, + int x, int y, bool left, int* base) +{ + SkIRect result; + result.setEmpty(); + FirstCheck center(x, y, area); + center.setRecordGlyph(); + int closestBase; + SkIRect closest = findClosest(center, picture, area, &closestBase); + closest.inset(-TOUCH_SLOP, -TOUCH_SLOP); + if (!closest.contains(x, y)) { + DBG_NAV_LOGD("closest=(%d, %d, %d, %d) area=(%d, %d, %d, %d) x/y=%d,%d", + closest.fLeft, closest.fTop, closest.fRight, closest.fBottom, + area.fLeft, area.fTop, area.fRight, area.fBottom, x, y); + return result; + } + EdgeCheck edge(x, y, area, center, left); + do { // detect left or right until there's a gap + DBG_NAV_LOGD("edge=%p picture=%p area=%d,%d,%d,%d", + &edge, &picture, area.fLeft, area.fTop, area.fRight, area.fBottom); + TextCanvas checker(&edge, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + edge.finishGlyph(); + if (!edge.adjacent()) { + DBG_NAV_LOG("adjacent break"); + break; + } + int nextBase; + const SkIRect& next = edge.bestBounds(&nextBase); + if (next.isEmpty()) { + DBG_NAV_LOG("empty"); + break; + } + if (result == next) { + DBG_NAV_LOG("result == next"); + break; + } + *base = nextBase; + result = next; + edge.shiftStart(result); + } while (true); + if (!result.isEmpty()) { + *base += area.fTop; + result.offset(area.fLeft, area.fTop); + } + return result; +} + +static SkIRect findFirst(const SkPicture& picture, int* base) +{ + FindFirst finder(picture.width(), picture.height()); + SkIRect area; + area.set(0, 0, picture.width(), picture.height()); + TextCanvas checker(&finder, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return finder.bestBounds(base); +} + +static SkIRect findLast(const SkPicture& picture, int* base) +{ + FindLast finder(picture.width(), picture.height()); + SkIRect area; + area.set(0, 0, picture.width(), picture.height()); + TextCanvas checker(&finder, area); + checker.drawPicture(const_cast<SkPicture&>(picture)); + return finder.bestBounds(base); +} + +static SkIRect findLeft(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base) +{ + return findEdge(picture, area, x, y, true, base); +} + +static SkIRect findRight(const SkPicture& picture, const SkIRect& area, + int x, int y, int* base) +{ + return findEdge(picture, area, x, y, false, base); +} + +static WebCore::String text(const SkPicture& picture, const SkIRect& area, + const SkIRect& start, int startBase, const SkIRect& end, + int endBase, bool flipped) +{ + TextExtractor extractor(start, startBase, end, endBase, area, flipped); + TextCanvas checker(&extractor, area); checker.drawPicture(const_cast<SkPicture&>(picture)); return extractor.text(); } +#define CONTROL_OFFSET 0 +#define CONTROL_NOTCH 19 +#define CONTROL_HEIGHT 35 +#define CONTROL_WIDTH 21 +#define STROKE_WIDTH 2.0f +#define STROKE_OUTSET 1.0f +#define STROKE_NOTCH_R 2.2f +#define STROKE_NOTCH_L 1.7f +#define DROP_HEIGHT 4 + +#define STROKE_COLOR 0x90000000 +#define FILL_GRADIENT_TOP 0xD0F8DFA0 +#define FILL_GRADIENT_BOTTOM 0xD0FFEFEF +#define DROP_GRADIENT_TOP 0x50000000 +#define DROP_GRADIENT_BOTTOM 0x00000000 + +#define SLOP 20 + +SelectText::SelectText() +{ + reset(); + SkPaint paint; + + SkPath startFillPath; + startFillPath.moveTo(-CONTROL_WIDTH + STROKE_OUTSET, CONTROL_NOTCH); + startFillPath.lineTo(-CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET); + startFillPath.lineTo(-STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET); + startFillPath.lineTo(-STROKE_OUTSET, CONTROL_OFFSET + STROKE_NOTCH_R); + startFillPath.close(); + SkPath startStrokePath; + startStrokePath.moveTo(-CONTROL_WIDTH, CONTROL_NOTCH); + startStrokePath.lineTo(-CONTROL_WIDTH, CONTROL_HEIGHT); + startStrokePath.lineTo(0, CONTROL_HEIGHT); + startStrokePath.lineTo(0, CONTROL_OFFSET); + startStrokePath.close(); + SkPoint gradientLine[] = {{0, 0}, {0, CONTROL_HEIGHT}}; + SkColor gradientColors[] = {FILL_GRADIENT_TOP, FILL_GRADIENT_BOTTOM}; + SkShader* fillGradient = SkGradientShader::CreateLinear(gradientLine, + gradientColors, 0, 2, SkShader::kClamp_TileMode); + SkPoint dropLine[] = {{0, CONTROL_HEIGHT}, {0, CONTROL_HEIGHT + DROP_HEIGHT}}; + SkColor dropColors[] = {DROP_GRADIENT_TOP, DROP_GRADIENT_BOTTOM}; + SkShader* dropGradient = SkGradientShader::CreateLinear(dropLine, + dropColors, 0, 2, SkShader::kClamp_TileMode); + SkRect startDropRect = {-CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT, + STROKE_OUTSET, CONTROL_HEIGHT + DROP_HEIGHT}; + + SkCanvas* canvas = m_startControl.beginRecording(CONTROL_WIDTH, CONTROL_HEIGHT + DROP_HEIGHT); + paint.setAntiAlias(true); + paint.setStyle(SkPaint::kFill_Style); + paint.setShader(fillGradient); + canvas->drawPath(startFillPath, paint); + paint.setShader(0); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(STROKE_COLOR); + paint.setStrokeWidth(STROKE_WIDTH); + canvas->drawPath(startStrokePath, paint); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(0xff000000); + paint.setShader(dropGradient); + canvas->drawRect(startDropRect, paint); + m_startControl.endRecording(); + + SkPath endFillPath; + endFillPath.moveTo(STROKE_OUTSET, CONTROL_OFFSET + STROKE_NOTCH_R); + endFillPath.lineTo(STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET); + endFillPath.lineTo(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_HEIGHT - STROKE_OUTSET); + endFillPath.lineTo(CONTROL_WIDTH - STROKE_OUTSET, CONTROL_NOTCH); + endFillPath.close(); + SkPath endStrokePath; + endStrokePath.moveTo(0, CONTROL_OFFSET); + endStrokePath.lineTo(0, CONTROL_HEIGHT); + endStrokePath.lineTo(CONTROL_WIDTH, CONTROL_HEIGHT); + endStrokePath.lineTo(CONTROL_WIDTH, CONTROL_NOTCH); + endStrokePath.close(); + SkRect endDropRect = {-STROKE_OUTSET, CONTROL_HEIGHT, + CONTROL_WIDTH + STROKE_OUTSET, CONTROL_HEIGHT + DROP_HEIGHT}; + + canvas = m_endControl.beginRecording(CONTROL_WIDTH, CONTROL_HEIGHT); + paint.setColor(0xff000000); + paint.setStyle(SkPaint::kFill_Style); + paint.setShader(fillGradient); + canvas->drawPath(endFillPath, paint); + paint.setShader(0); + paint.setStyle(SkPaint::kStroke_Style); + paint.setColor(STROKE_COLOR); + paint.setStrokeWidth(STROKE_WIDTH); + canvas->drawPath(endStrokePath, paint); + paint.setStyle(SkPaint::kFill_Style); + paint.setColor(0xff000000); + paint.setShader(dropGradient); + canvas->drawRect(endDropRect, paint); + m_endControl.endRecording(); + fillGradient->safeUnref(); + dropGradient->safeUnref(); + m_picture = 0; +} + void SelectText::draw(SkCanvas* canvas, LayerAndroid* layer) { - if (layer->picture() != m_picture) + // Gmail makes layers appear dynamically the page scrolls. The picture + // recorded when the selection begins is confused by the pictures seen + // in subsequent layers. To work around this, only allow text selection + // in the main picture. + if (layer->uniqueId() != -1) + return; + // FIXME: layer may not own the original selected picture + m_picture = layer->picture(); + if (!m_picture) return; - if (m_drawRegion) + DBG_NAV_LOGD("m_extendSelection=%d m_drawPointer=%d", m_extendSelection, m_drawPointer); + if (m_extendSelection) drawSelectionRegion(canvas); if (m_drawPointer) drawSelectionPointer(canvas); @@ -406,7 +1310,7 @@ void SelectText::drawSelectionPointer(SkCanvas* canvas) paint.setStrokeWidth(SK_Scalar1 * 2); int sc = canvas->save(); canvas->scale(m_inverseScale, m_inverseScale); - canvas->translate(SkIntToScalar(m_selectX), SkIntToScalar(m_selectY)); + canvas->translate(m_selectX, m_selectY); canvas->drawPath(path, paint); if (!m_extendSelection) { paint.setStyle(SkPaint::kFill_Style); @@ -424,19 +1328,96 @@ void SelectText::drawSelectionRegion(SkCanvas* canvas) return; SkIRect ivisBounds; visBounds.round(&ivisBounds); - CopyPaste::buildSelection(*m_picture, ivisBounds, m_selStart, m_selEnd, - &m_selRegion); + ivisBounds.join(m_selStart); + ivisBounds.join(m_selEnd); + DBG_NAV_LOGD("m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); + m_flipped = buildSelection(*m_picture, ivisBounds, m_selStart, m_startBase, + m_selEnd, m_endBase, &m_selRegion); SkPath path; m_selRegion.getBoundaryPath(&path); + path.setFillType(SkPath::kEvenOdd_FillType); + SkPaint paint; paint.setAntiAlias(true); - paint.setColor(SkColorSetARGB(0x40, 255, 51, 204)); + paint.setColor(SkColorSetARGB(0x80, 0xFF, 0xA8, 0x00)); canvas->drawPath(path, paint); + // experiment to draw touchable controls that resize the selection + canvas->save(); + canvas->translate(m_selStart.fLeft, m_selStart.fBottom); + canvas->drawPicture(m_startControl); + canvas->restore(); + canvas->save(); + canvas->translate(m_selEnd.fRight, m_selEnd.fBottom); + canvas->drawPicture(m_endControl); + canvas->restore(); +} + +void SelectText::extendSelection(const SkPicture* picture, int x, int y) +{ + if (!picture) + return; + SkIRect clipRect = m_visibleRect; + int base; + if (m_startSelection) { + if (!clipRect.contains(x, y) + || !clipRect.contains(m_original.fX, m_original.fY)) { + clipRect.set(m_original.fX, m_original.fY, x, y); + clipRect.sort(); + clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height()); + } + DBG_NAV_LOGD("selStart clip=(%d,%d,%d,%d)", clipRect.fLeft, + clipRect.fTop, clipRect.fRight, clipRect.fBottom); + m_picture = picture; + FirstCheck center(m_original.fX, m_original.fY, clipRect); + m_selStart = m_selEnd = findClosest(center, *picture, clipRect, &base); + m_startBase = m_endBase = base; + m_startSelection = false; + m_extendSelection = true; + m_original.fX = m_original.fY = 0; + } else if (picture != m_picture) + return; + x -= m_original.fX; + y -= m_original.fY; + if (!clipRect.contains(x, y) || !clipRect.contains(m_selStart)) { + clipRect.set(m_selStart.fLeft, m_selStart.fTop, x, y); + clipRect.sort(); + clipRect.inset(-m_visibleRect.width(), -m_visibleRect.height()); + } + DBG_NAV_LOGD("extend clip=(%d,%d,%d,%d)", clipRect.fLeft, + clipRect.fTop, clipRect.fRight, clipRect.fBottom); + FirstCheck extension(x, y, clipRect); + SkIRect found = findClosest(extension, *picture, clipRect, &base); + DBG_NAV_LOGD("pic=%p x=%d y=%d m_startSelection=%s %s=(%d, %d, %d, %d)" + " m_extendSelection=%s", + picture, x, y, m_startSelection ? "true" : "false", + m_hitTopLeft ? "m_selStart" : "m_selEnd", + found.fLeft, found.fTop, found.fRight, found.fBottom, + m_extendSelection ? "true" : "false"); + if (m_hitTopLeft) { + m_startBase = base; + m_selStart = found; + } else { + m_endBase = base; + m_selEnd = found; + } + swapAsNeeded(); } const String SelectText::getSelection() { - String result = CopyPaste::text(*m_picture, m_visibleRect, m_selRegion); + if (!m_picture) + return String(); + SkIRect clipRect; + clipRect.set(0, 0, m_picture->width(), m_picture->height()); + String result = text(*m_picture, clipRect, m_selStart, m_startBase, + m_selEnd, m_endBase, m_flipped); + DBG_NAV_LOGD("clip=(%d,%d,%d,%d)" + " m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", + clipRect.fLeft, clipRect.fTop, clipRect.fRight, clipRect.fBottom, + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); DBG_NAV_LOGD("text=%s", result.latin1().data()); // uses CString return result; } @@ -447,35 +1428,178 @@ void SelectText::getSelectionArrow(SkPath* path) 0, 14, 3, 11, 5, 15, 9, 15, 7, 11, 11, 11 }; for (unsigned index = 0; index < sizeof(arrow)/sizeof(arrow[0]); index += 2) - path->lineTo(SkIntToScalar(arrow[index]), SkIntToScalar(arrow[index + 1])); + path->lineTo(arrow[index], arrow[index + 1]); path->close(); } void SelectText::getSelectionCaret(SkPath* path) { - SkScalar height = SkIntToScalar(m_selStart.fBottom - m_selStart.fTop); + SkScalar height = m_selStart.fBottom - m_selStart.fTop; SkScalar dist = height / 4; path->moveTo(0, -height / 2); path->rLineTo(0, height); path->rLineTo(-dist, dist); - path->rMoveTo(0, -SK_Scalar1/2); + path->rMoveTo(0, -0.5f); path->rLineTo(dist * 2, 0); - path->rMoveTo(0, SK_Scalar1/2); + path->rMoveTo(0, 0.5f); path->rLineTo(-dist, -dist); } -void SelectText::moveSelection(const SkPicture* picture, int x, int y, - bool extendSelection) +bool SelectText::hitCorner(int cx, int cy, int x, int y) const +{ + SkIRect test; + test.set(cx, cy, cx, cy); + test.inset(-SLOP, -SLOP); + return test.contains(x, y); +} + +bool SelectText::hitSelection(int x, int y) const { - if (!extendSelection) + int left = m_selStart.fLeft - CONTROL_WIDTH / 2; + int top = m_selStart.fBottom + CONTROL_HEIGHT / 2; + if (hitCorner(left, top, x, y)) + return true; + int right = m_selEnd.fRight + CONTROL_WIDTH / 2; + int bottom = m_selEnd.fBottom + CONTROL_HEIGHT / 2; + if (hitCorner(right, bottom, x, y)) + return true; + SkIRect test; + test.set(x - CONTROL_WIDTH, y - CONTROL_HEIGHT, x + CONTROL_WIDTH, + y + CONTROL_HEIGHT); + return m_selRegion.intersects(test); +} + +void SelectText::moveSelection(const SkPicture* picture, int x, int y) +{ + if (!picture) + return; + SkIRect clipRect = m_visibleRect; + clipRect.join(m_selStart); + clipRect.join(m_selEnd); + if (!m_extendSelection) m_picture = picture; - m_selEnd = CopyPaste::findClosest(*picture, m_visibleRect, x, y); - if (!extendSelection) - m_selStart = m_selEnd; + FirstCheck center(x, y, clipRect); + int base; + SkIRect found = findClosest(center, *picture, clipRect, &base); + if (m_hitTopLeft || !m_extendSelection) { + m_startBase = base; + m_selStart = found; + } + if (!m_hitTopLeft || !m_extendSelection) { + m_endBase = base; + m_selEnd = found; + } + swapAsNeeded(); DBG_NAV_LOGD("x=%d y=%d extendSelection=%s m_selStart=(%d, %d, %d, %d)" - " m_selEnd=(%d, %d, %d, %d)", x, y, extendSelection ? "true" : "false", + " m_selEnd=(%d, %d, %d, %d)", x, y, m_extendSelection ? "true" : "false", + m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, + m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); +} + +void SelectText::reset() +{ + DBG_NAV_LOG("m_extendSelection=false"); + m_selStart.setEmpty(); + m_selEnd.setEmpty(); + m_extendSelection = false; + m_startSelection = false; +} + +void SelectText::selectAll(const SkPicture* picture) +{ + m_selStart = findFirst(*picture, &m_startBase); + m_selEnd = findLast(*picture, &m_endBase); + m_extendSelection = true; +} + +int SelectText::selectionX() const +{ + return m_hitTopLeft ? m_selStart.fLeft : m_selEnd.fRight; +} + +int SelectText::selectionY() const +{ + const SkIRect& rect = m_hitTopLeft ? m_selStart : m_selEnd; + return (rect.fTop + rect.fBottom) >> 1; +} + +bool SelectText::startSelection(int x, int y) +{ + m_original.fX = x; + m_original.fY = y; + if (m_selStart.isEmpty()) { + DBG_NAV_LOGD("empty start x=%d y=%d", x, y); + m_startSelection = true; + return true; + } + int left = m_selStart.fLeft - CONTROL_WIDTH / 2; + int top = m_selStart.fBottom + CONTROL_HEIGHT / 2; + m_hitTopLeft = hitCorner(left, top, x, y); + int right = m_selEnd.fRight + CONTROL_WIDTH / 2; + int bottom = m_selEnd.fBottom + CONTROL_HEIGHT / 2; + bool hitBottomRight = hitCorner(right, bottom, x, y); + DBG_NAV_LOGD("left=%d top=%d right=%d bottom=%d x=%d y=%d", left, top, + right, bottom, x, y); + if (m_hitTopLeft && (!hitBottomRight || y - top < bottom - y)) { + DBG_NAV_LOG("hit top left"); + m_original.fX -= left; + m_original.fY -= (m_selStart.fTop + m_selStart.fBottom) >> 1; + } else if (hitBottomRight) { + DBG_NAV_LOG("hit bottom right"); + m_original.fX -= right; + m_original.fY -= (m_selEnd.fTop + m_selEnd.fBottom) >> 1; + } + return m_hitTopLeft || hitBottomRight; +} + +/* selects the word at (x, y) +* a word is normally delimited by spaces +* a string of digits (even with inside spaces) is a word (for phone numbers) +* FIXME: digit find isn't implemented yet +* returns true if a word was selected +*/ +bool SelectText::wordSelection(const SkPicture* picture) +{ + int x = m_selStart.fLeft; + int y = (m_selStart.fTop + m_selStart.fBottom) >> 1; + SkIRect clipRect = m_visibleRect; + clipRect.fLeft -= m_visibleRect.width() >> 1; + int base; + SkIRect left = findLeft(*picture, clipRect, x, y, &base); + if (!left.isEmpty()) { + m_startBase = base; + m_selStart = left; + } + x = m_selEnd.fRight; + y = (m_selEnd.fTop + m_selEnd.fBottom) >> 1; + clipRect = m_visibleRect; + clipRect.fRight += m_visibleRect.width() >> 1; + SkIRect right = findRight(*picture, clipRect, x, y, &base); + if (!right.isEmpty()) { + m_endBase = base; + m_selEnd = right; + } + DBG_NAV_LOGD("m_selStart=(%d, %d, %d, %d) m_selEnd=(%d, %d, %d, %d)", m_selStart.fLeft, m_selStart.fTop, m_selStart.fRight, m_selStart.fBottom, m_selEnd.fLeft, m_selEnd.fTop, m_selEnd.fRight, m_selEnd.fBottom); + if (!left.isEmpty() || !right.isEmpty()) { + m_extendSelection = true; + return true; + } + return false; +} + +void SelectText::swapAsNeeded() +{ + if (m_selStart.fTop >= (m_selEnd.fTop + m_selEnd.fBottom) >> 1 + || (m_selEnd.fTop < (m_selStart.fTop + m_selStart.fBottom) >> 1 + && m_selStart.fRight > m_selEnd.fLeft)) + { + SkTSwap(m_startBase, m_endBase); + SkTSwap(m_selStart, m_selEnd); + m_hitTopLeft ^= true; + DBG_NAV_LOGD("m_hitTopLeft=%s", m_hitTopLeft ? "true" : "false"); + } } } diff --git a/WebKit/android/nav/SelectText.h b/WebKit/android/nav/SelectText.h index 8174046..404e9e7 100644 --- a/WebKit/android/nav/SelectText.h +++ b/WebKit/android/nav/SelectText.h @@ -29,6 +29,7 @@ #include "DrawExtra.h" #include "IntRect.h" #include "PlatformString.h" +#include "SkPath.h" class SkPicture; struct SkIRect; @@ -38,47 +39,57 @@ namespace android { class CachedRoot; -class CopyPaste { -public: - static void buildSelection(const SkPicture& , const SkIRect& area, - const SkIRect& selStart, const SkIRect& selEnd, SkRegion* region); - static SkIRect findClosest(const SkPicture& , const SkIRect& area, - int x, int y); - static String text(const SkPicture& , const SkIRect& area, - const SkRegion& ); -}; - class SelectText : public DrawExtra { public: - SelectText() { - m_selStart.setEmpty(); - m_selEnd.setEmpty(); - } + SelectText(); virtual void draw(SkCanvas* , LayerAndroid* ); + void extendSelection(const SkPicture* , int x, int y); const String getSelection(); - void moveSelection(const SkPicture* , int x, int y, bool extendSelection); + bool hitSelection(int x, int y) const; + void moveSelection(const SkPicture* , int x, int y); + void reset(); + void selectAll(const SkPicture* ); + int selectionX() const; + int selectionY() const; void setDrawPointer(bool drawPointer) { m_drawPointer = drawPointer; } - void setDrawRegion(bool drawRegion) { m_drawRegion = drawRegion; } + void setExtendSelection(bool extend) { m_extendSelection = extend; } void setVisibleRect(const IntRect& rect) { m_visibleRect = rect; } + bool startSelection(int x, int y); + bool wordSelection(const SkPicture* picture); +public: + float m_inverseScale; // inverse scale, x, y used for drawing select path + int m_selectX; + int m_selectY; private: - friend class WebView; void drawSelectionPointer(SkCanvas* ); void drawSelectionRegion(SkCanvas* ); static void getSelectionArrow(SkPath* ); void getSelectionCaret(SkPath* ); + bool hitCorner(int cx, int cy, int x, int y) const; + void swapAsNeeded(); + SkIPoint m_original; // computed start of extend selection SkIRect m_selStart; SkIRect m_selEnd; - SkIRect m_visibleRect; - SkRegion m_selRegion; + int m_startBase; + int m_endBase; + SkIRect m_visibleRect; // constrains picture computations to visible area + SkRegion m_selRegion; // computed from sel start, end + SkPicture m_startControl; + SkPicture m_endControl; const SkPicture* m_picture; - float m_inverseScale; - int m_selectX; - int m_selectY; - bool m_drawRegion; bool m_drawPointer; - bool m_extendSelection; + bool m_extendSelection; // false when trackball is moving pointer + bool m_flipped; + bool m_hitTopLeft; + bool m_startSelection; }; } +namespace WebCore { + +void ReverseBidi(UChar* chars, int len); + +} + #endif diff --git a/WebKit/android/nav/WebView.cpp b/WebKit/android/nav/WebView.cpp index 6c58852..913c4ba 100644 --- a/WebKit/android/nav/WebView.cpp +++ b/WebKit/android/nav/WebView.cpp @@ -637,19 +637,19 @@ void getVisibleRect(WebCore::IntRect* rect) checkException(env); } -static CachedFrame::Direction KeyToDirection(KeyCode keyCode) +static CachedFrame::Direction KeyToDirection(int32_t keyCode) { switch (keyCode) { - case kKeyCodeDpadRight: + case AKEYCODE_DPAD_RIGHT: DBG_NAV_LOGD("keyCode=%s", "right"); return CachedFrame::RIGHT; - case kKeyCodeDpadLeft: + case AKEYCODE_DPAD_LEFT: DBG_NAV_LOGD("keyCode=%s", "left"); return CachedFrame::LEFT; - case kKeyCodeDpadDown: + case AKEYCODE_DPAD_DOWN: DBG_NAV_LOGD("keyCode=%s", "down"); return CachedFrame::DOWN; - case kKeyCodeDpadUp: + case AKEYCODE_DPAD_UP: DBG_NAV_LOGD("keyCode=%s", "up"); return CachedFrame::UP; default: @@ -686,7 +686,7 @@ bool moveCursor(int keyCode, int count, bool ignoreScroll) } m_viewImpl->m_moveGeneration++; - CachedFrame::Direction direction = KeyToDirection((KeyCode) keyCode); + CachedFrame::Direction direction = KeyToDirection(keyCode); const CachedFrame* cachedFrame, * oldFrame = 0; const CachedNode* cursor = root->currentCursor(&oldFrame); WebCore::IntPoint cursorLocation = root->cursorLocation(); @@ -946,7 +946,7 @@ String getSelection() return m_selectText.getSelection(); } -void moveSelection(int x, int y, bool extendSelection) +void moveSelection(int x, int y) { const CachedRoot* root = getFrameCache(DontAllowNewer); if (!root) @@ -957,26 +957,81 @@ void moveSelection(int x, int y, bool extendSelection) IntRect visibleRect; getVisibleRect(&visibleRect); m_selectText.setVisibleRect(visibleRect); - m_selectText.moveSelection(picture, x, y, extendSelection); + m_selectText.moveSelection(picture, x, y); } -void setSelectionPointer(bool set, float scale, int x, int y, - bool extendSelection) +void selectAll() +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return; + SkPicture* picture = root->pictureAt(0, 0); + m_selectText.selectAll(picture); +} + +int selectionX() +{ + return m_selectText.selectionX(); +} + +int selectionY() +{ + return m_selectText.selectionY(); +} + +void resetSelection() +{ + m_selectText.reset(); +} + +bool startSelection(int x, int y) +{ + return m_selectText.startSelection(x, y); +} + +bool wordSelection(int x, int y) +{ + startSelection(x, y); + if (!extendSelection(x, y)) + return false; + m_selectText.setDrawPointer(false); + SkPicture* picture = getFrameCache(DontAllowNewer)->pictureAt(x, y); + return m_selectText.wordSelection(picture); +} + +bool extendSelection(int x, int y) +{ + const CachedRoot* root = getFrameCache(DontAllowNewer); + if (!root) + return false; + SkPicture* picture = root->pictureAt(x, y); + IntRect visibleRect; + getVisibleRect(&visibleRect); + m_selectText.setVisibleRect(visibleRect); + m_selectText.extendSelection(picture, x, y); + return true; +} + +bool hitSelection(int x, int y) +{ + return m_selectText.hitSelection(x, y); +} + +void setExtendSelection() +{ + m_selectText.setExtendSelection(true); +} + +void setSelectionPointer(bool set, float scale, int x, int y) { m_selectText.setDrawPointer(set); if (!set) return; - m_selectText.m_extendSelection = extendSelection; m_selectText.m_inverseScale = scale; m_selectText.m_selectX = x; m_selectText.m_selectY = y; } -void setSelectionRegion(bool set) -{ - m_selectText.setDrawRegion(set); -} - void sendMoveFocus(WebCore::Frame* framePtr, WebCore::Node* nodePtr) { DBG_NAV_LOGD("framePtr=%p nodePtr=%p", framePtr, nodePtr); @@ -1035,6 +1090,11 @@ void setMatches(WTF::Vector<MatchInfo>* matches) viewInvalidate(); } +int currentMatchIndex() +{ + return m_findOnPage.currentMatchIndex(); +} + bool scrollBy(int dx, int dy) { LOG_ASSERT(m_javaGlue.m_obj, "A java object was not associated with this native WebView!"); @@ -1441,17 +1501,32 @@ static jobject nativeFocusCandidateName(JNIEnv *env, jobject obj) return env->NewString((jchar*)name.characters(), name.length()); } +static jobject createJavaRect(JNIEnv* env, int x, int y, int right, int bottom) +{ + jclass rectClass = env->FindClass("android/graphics/Rect"); + jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); + jobject rect = env->NewObject(rectClass, init, x, y, right, bottom); + return rect; +} + static jobject nativeFocusCandidateNodeBounds(JNIEnv *env, jobject obj) { const CachedFrame* frame; const CachedNode* node = getFocusCandidate(env, obj, &frame); WebCore::IntRect bounds = node ? node->bounds(frame) : WebCore::IntRect(0, 0, 0, 0); - jclass rectClass = env->FindClass("android/graphics/Rect"); - jmethodID init = env->GetMethodID(rectClass, "<init>", "(IIII)V"); - jobject rect = env->NewObject(rectClass, init, bounds.x(), - bounds.y(), bounds.right(), bounds.bottom()); - return rect; + return createJavaRect(env, bounds.x(), bounds.y(), bounds.right(), bounds.bottom()); +} + +static jobject nativeFocusCandidatePaddingRect(JNIEnv *env, jobject obj) +{ + const CachedInput* input = getInputCandidate(env, obj); + if (!input) + return 0; + // Note that the Java Rect is being used to pass four integers, rather than + // being used as an actual rectangle. + return createJavaRect(env, input->paddingLeft(), input->paddingTop(), + input->paddingRight(), input->paddingBottom()); } static jint nativeFocusCandidatePointer(JNIEnv *env, jobject obj) @@ -1735,6 +1810,13 @@ static void nativeFindNext(JNIEnv *env, jobject obj, bool forward) view->findNext(forward); } +static int nativeFindIndex(JNIEnv *env, jobject obj) +{ + WebView* view = GET_NATIVE_VIEW(env, obj); + LOG_ASSERT(view, "view not set in nativeFindIndex"); + return view->currentMatchIndex(); +} + static void nativeUpdateCachedTextfield(JNIEnv *env, jobject obj, jstring updatedText, jint generation) { WebView* view = GET_NATIVE_VIEW(env, obj); @@ -1805,11 +1887,39 @@ static int nativeMoveGeneration(JNIEnv *env, jobject obj) return view->moveGeneration(); } -static void nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y, bool ex) +static void nativeMoveSelection(JNIEnv *env, jobject obj, int x, int y) { - WebView* view = GET_NATIVE_VIEW(env, obj); - LOG_ASSERT(view, "view not set in %s", __FUNCTION__); - view->moveSelection(x, y, ex); + GET_NATIVE_VIEW(env, obj)->moveSelection(x, y); +} + +static void nativeResetSelection(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->resetSelection(); +} + +static void nativeSelectAll(JNIEnv* env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->selectAll(); +} + +static void nativeSetExtendSelection(JNIEnv *env, jobject obj) +{ + GET_NATIVE_VIEW(env, obj)->setExtendSelection(); +} + +static jboolean nativeStartSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->startSelection(x, y); +} + +static jboolean nativeWordSelection(JNIEnv *env, jobject obj, int x, int y) +{ + return GET_NATIVE_VIEW(env, obj)->wordSelection(x, y); +} + +static void nativeExtendSelection(JNIEnv *env, jobject obj, int x, int y) +{ + GET_NATIVE_VIEW(env, obj)->extendSelection(x, y); } static jobject nativeGetSelection(JNIEnv *env, jobject obj) @@ -1820,15 +1930,25 @@ static jobject nativeGetSelection(JNIEnv *env, jobject obj) return env->NewString((jchar*)selection.characters(), selection.length()); } -static void nativeSetSelectionPointer(JNIEnv *env, jobject obj, jboolean set, - jfloat scale, jint x, jint y, bool ex) +static jboolean nativeHitSelection(JNIEnv *env, jobject obj, int x, int y) { - GET_NATIVE_VIEW(env, obj)->setSelectionPointer(set, scale, x, y, ex); + return GET_NATIVE_VIEW(env, obj)->hitSelection(x, y); } -static void nativeSetSelectionRegion(JNIEnv *env, jobject obj, jboolean set) +static jint nativeSelectionX(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->selectionX(); +} + +static jint nativeSelectionY(JNIEnv *env, jobject obj) +{ + return GET_NATIVE_VIEW(env, obj)->selectionY(); +} + +static void nativeSetSelectionPointer(JNIEnv *env, jobject obj, jboolean set, + jfloat scale, jint x, jint y) { - GET_NATIVE_VIEW(env, obj)->setSelectionRegion(set); + GET_NATIVE_VIEW(env, obj)->setSelectionPointer(set, scale, x, y); } #ifdef ANDROID_DUMP_DISPLAY_TREE @@ -1935,10 +2055,14 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeDumpDisplayTree }, { "nativeEvaluateLayersAnimations", "()Z", (void*) nativeEvaluateLayersAnimations }, + { "nativeExtendSelection", "(II)V", + (void*) nativeExtendSelection }, { "nativeFindAll", "(Ljava/lang/String;Ljava/lang/String;)I", (void*) nativeFindAll }, { "nativeFindNext", "(Z)V", (void*) nativeFindNext }, + { "nativeFindIndex", "()I", + (void*) nativeFindIndex}, { "nativeFocusCandidateFramePointer", "()I", (void*) nativeFocusCandidateFramePointer }, { "nativeFocusCandidateHasNextTextfield", "()Z", @@ -1955,6 +2079,8 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeFocusCandidateName }, { "nativeFocusCandidateNodeBounds", "()Landroid/graphics/Rect;", (void*) nativeFocusCandidateNodeBounds }, + { "nativeFocusCandidatePaddingRect", "()Landroid/graphics/Rect;", + (void*) nativeFocusCandidatePaddingRect }, { "nativeFocusCandidatePointer", "()I", (void*) nativeFocusCandidatePointer }, { "nativeFocusCandidateText", "()Ljava/lang/String;", @@ -1979,6 +2105,8 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeHasFocusNode }, { "nativeHideCursor", "()V", (void*) nativeHideCursor }, + { "nativeHitSelection", "(II)Z", + (void*) nativeHitSelection }, { "nativeImageURI", "(II)Ljava/lang/String;", (void*) nativeImageURI }, { "nativeInstrumentReport", "()V", @@ -1991,14 +2119,24 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeMoveCursorToNextTextInput }, { "nativeMoveGeneration", "()I", (void*) nativeMoveGeneration }, - { "nativeMoveSelection", "(IIZ)V", + { "nativeMoveSelection", "(II)V", (void*) nativeMoveSelection }, { "nativePointInNavCache", "(III)Z", (void*) nativePointInNavCache }, { "nativeRecordButtons", "(ZZZ)V", (void*) nativeRecordButtons }, + { "nativeResetSelection", "()V", + (void*) nativeResetSelection }, + { "nativeSelectAll", "()V", + (void*) nativeSelectAll }, { "nativeSelectBestAt", "(Landroid/graphics/Rect;)V", (void*) nativeSelectBestAt }, + { "nativeSelectionX", "()I", + (void*) nativeSelectionX }, + { "nativeSelectionY", "()I", + (void*) nativeSelectionY }, + { "nativeSetExtendSelection", "()V", + (void*) nativeSetExtendSelection }, { "nativeSetFindIsEmpty", "()V", (void*) nativeSetFindIsEmpty }, { "nativeSetFindIsUp", "(Z)V", @@ -2009,16 +2147,18 @@ static JNINativeMethod gJavaWebViewMethods[] = { (void*) nativeSetHeightCanMeasure }, { "nativeSetRootLayer", "(I)V", (void*) nativeSetRootLayer }, - { "nativeSetSelectionPointer", "(ZFIIZ)V", + { "nativeSetSelectionPointer", "(ZFII)V", (void*) nativeSetSelectionPointer }, - { "nativeSetSelectionRegion", "(Z)V", - (void*) nativeSetSelectionRegion }, + { "nativeStartSelection", "(II)Z", + (void*) nativeStartSelection }, { "nativeSubtractLayers", "(Landroid/graphics/Rect;)Landroid/graphics/Rect;", (void*) nativeSubtractLayers }, { "nativeTextGeneration", "()I", (void*) nativeTextGeneration }, { "nativeUpdateCachedTextfield", "(Ljava/lang/String;I)V", (void*) nativeUpdateCachedTextfield }, + { "nativeWordSelection", "(II)Z", + (void*) nativeWordSelection }, { "nativeGetBlockLeftEdge", "(IIF)I", (void*) nativeGetBlockLeftEdge }, }; diff --git a/WebKit/android/plugins/ANPKeyCodes.h b/WebKit/android/plugins/ANPKeyCodes.h index 328eb56..edfe2b9 100644 --- a/WebKit/android/plugins/ANPKeyCodes.h +++ b/WebKit/android/plugins/ANPKeyCodes.h @@ -29,8 +29,7 @@ /* List the key codes that are set to a plugin in the ANPKeyEvent. These exactly match the values in android/view/KeyEvent.java and the - corresponding .h file include/ui/KeycodeLabels.h, which contains more than - I want to publish to plugin authors. + corresponding .h file android/keycodes.h. */ enum ANPKeyCodes { kUnknown_ANPKeyCode = 0, @@ -118,7 +117,36 @@ enum ANPKeyCodes { kPlus_ANPKeyCode = 81, kMenu_ANPKeyCode = 82, kNotification_ANPKeyCode = 83, - kSearch_ANPKeyCode = 84 + kSearch_ANPKeyCode = 84, + kMediaPlayPause_ANPKeyCode = 85, + kMediaStop_ANPKeyCode = 86, + kMediaNext_ANPKeyCode = 87, + kMediaPrevious_ANPKeyCode = 88, + kMediaRewind_ANPKeyCode = 89, + kMediaFastForward_ANPKeyCode = 90, + kMute_ANPKeyCode = 91, + kPageUp_ANPKeyCode = 92, + kPageDown_ANPKeyCode = 93, + kPictsymbols_ANPKeyCode = 94, + kSwitchCharset_ANPKeyCode = 95, + kButtonA_ANPKeyCode = 96, + kButtonB_ANPKeyCode = 97, + kButtonC_ANPKeyCode = 98, + kButtonX_ANPKeyCode = 99, + kButtonY_ANPKeyCode = 100, + kButtonZ_ANPKeyCode = 101, + kButtonL1_ANPKeyCode = 102, + kButtonR1_ANPKeyCode = 103, + kButtonL2_ANPKeyCode = 104, + kButtonR2_ANPKeyCode = 105, + kButtonThumbL_ANPKeyCode = 106, + kButtonThumbR_ANPKeyCode = 107, + kButtonStart_ANPKeyCode = 108, + kButtonSelect_ANPKeyCode = 109, + kButtonMode_ANPKeyCode = 110, + + // NOTE: If you add a new keycode here you must also add it to several other files. + // Refer to frameworks/base/core/java/android/view/KeyEvent.java for the full list. }; #endif diff --git a/WebKit/android/plugins/ANPSurfaceInterface.cpp b/WebKit/android/plugins/ANPSurfaceInterface.cpp index 8830191..4b99b31 100644 --- a/WebKit/android/plugins/ANPSurfaceInterface.cpp +++ b/WebKit/android/plugins/ANPSurfaceInterface.cpp @@ -64,7 +64,7 @@ static inline sp<Surface> getSurface(JNIEnv* env, jobject view) { jclass surfaceClass = env->FindClass("android/view/Surface"); gSurfaceJavaGlue.surfacePointer = env->GetFieldID(surfaceClass, - "mSurface", "I"); + ANDROID_VIEW_SURFACE_JNI_ID, "I"); env->DeleteLocalRef(surfaceClass); env->DeleteLocalRef(surfaceViewClass); |
