/* -*- Mode: c++; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Android code. * * The Initial Developer of the Original Code is Mozilla Foundation. * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Vladimir Vukicevic * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include #include #include "nsAppShell.h" #include "nsIdleService.h" #include "nsWindow.h" #include "nsIDeviceContext.h" #include "nsIRenderingContext.h" #include "nsIDOMSimpleGestureEvent.h" #include "nsWidgetAtoms.h" #include "nsWidgetsCID.h" #include "nsGfxCIID.h" #include "gfxImageSurface.h" #include "gfxContext.h" #include "Layers.h" #include "BasicLayers.h" #include "LayerManagerOGL.h" #include "GLContext.h" #include "GLContextProvider.h" #include "nsTArray.h" #include "AndroidBridge.h" using namespace mozilla; NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget) // The dimensions of the current android view static gfxIntSize gAndroidBounds; static PRBool gLeftShift; static PRBool gRightShift; static PRBool gLeftAlt; static PRBool gRightAlt; static PRBool gSym; // All the toplevel windows that have been created; these are in // stacking order, so the window at gAndroidBounds[0] is the topmost // one. static nsTArray gTopLevelWindows; static nsWindow* gFocusedWindow = nsnull; static nsRefPtr sGLContext; static bool sFailedToCreateGLContext = false; // Multitouch swipe thresholds (in screen pixels) static const double SWIPE_MAX_PINCH_DELTA = 100; static const double SWIPE_MIN_DISTANCE = 150; static nsWindow* TopWindow() { if (!gTopLevelWindows.IsEmpty()) return gTopLevelWindows[0]; return nsnull; } void nsWindow::LogWindow(nsWindow *win, int index, int indent) { char spaces[] = " "; spaces[indent < 20 ? indent : 20] = 0; ALOG("%s [% 2d] 0x%08x [parent 0x%08x] [% 3d,% 3d % 3dx% 3d] vis %d type %d", spaces, index, (intptr_t)win, (intptr_t)win->mParent, win->mBounds.x, win->mBounds.y, win->mBounds.width, win->mBounds.height, win->mIsVisible, win->mWindowType); } void nsWindow::DumpWindows() { DumpWindows(gTopLevelWindows); } void nsWindow::DumpWindows(const nsTArray& wins, int indent) { for (PRUint32 i = 0; i < wins.Length(); ++i) { nsWindow *w = wins[i]; LogWindow(w, i, indent); DumpWindows(w->mChildren, indent+1); } } nsWindow::nsWindow() : mIsVisible(PR_FALSE), mParent(nsnull) { } nsWindow::~nsWindow() { gTopLevelWindows.RemoveElement(this); if (gFocusedWindow == this) gFocusedWindow = nsnull; ALOG("nsWindow %p destructor", (void*)this); } PRBool nsWindow::IsTopLevel() { return mWindowType == eWindowType_toplevel || mWindowType == eWindowType_dialog || mWindowType == eWindowType_invisible; } NS_IMETHODIMP nsWindow::Create(nsIWidget *aParent, nsNativeWidget aNativeParent, const nsIntRect &aRect, EVENT_CALLBACK aHandleEventFunction, nsIDeviceContext *aContext, nsIAppShell *aAppShell, nsIToolkit *aToolkit, nsWidgetInitData *aInitData) { ALOG("nsWindow[%p]::Create %p [%d %d %d %d]", (void*)this, (void*)aParent, aRect.x, aRect.y, aRect.width, aRect.height); nsWindow *parent = (nsWindow*) aParent; if (!AndroidBridge::Bridge()) { aNativeParent = nsnull; } if (aNativeParent) { if (parent) { ALOG("Ignoring native parent on Android window [%p], since parent was specified (%p %p)", (void*)this, (void*)aNativeParent, (void*)aParent); } else { parent = (nsWindow*) aNativeParent; } } mBounds = aRect; // for toplevel windows, bounds are fixed to full screen size if (!parent) { mBounds.x = 0; mBounds.y = 0; mBounds.width = gAndroidBounds.width; mBounds.height = gAndroidBounds.height; } BaseCreate(nsnull, mBounds, aHandleEventFunction, aContext, aAppShell, aToolkit, aInitData); NS_ASSERTION(IsTopLevel() || parent, "non top level windowdoesn't have a parent!"); if (IsTopLevel()) { gTopLevelWindows.AppendElement(this); } if (parent) { parent->mChildren.AppendElement(this); mParent = parent; } return NS_OK; } NS_IMETHODIMP nsWindow::Destroy(void) { for (PRUint32 i = 0; i < mChildren.Length(); ++i) { // why do we still have children? ALOG("### Warning: Destroying window %p and reparenting child %p to null!", (void*)this, (void*)mChildren[i]); mChildren[i]->SetParent(nsnull); } if (IsTopLevel()) gTopLevelWindows.RemoveElement(this); if (mParent) mParent->mChildren.RemoveElement(this); return NS_OK; } NS_IMETHODIMP nsWindow::ConfigureChildren(const nsTArray& config) { for (PRUint32 i = 0; i < config.Length(); ++i) { nsWindow *childWin = (nsWindow*) config[i].mChild; childWin->Resize(config[i].mBounds.x, config[i].mBounds.y, config[i].mBounds.width, config[i].mBounds.height, PR_FALSE); } return NS_OK; } NS_IMETHODIMP nsWindow::SetParent(nsIWidget *aNewParent) { if ((nsIWidget*)mParent == aNewParent) return NS_OK; // If we had a parent before, remove ourselves from its list of // children. If we didn't have a parent, then remove ourselves // from the list of toplevel windows if we're about to get a // parent. if (mParent) mParent->mChildren.RemoveElement(this); mParent = (nsWindow*)aNewParent; if (mParent) mParent->mChildren.AppendElement(this); // if we are now in the toplevel window's hierarchy, schedule a redraw if (FindTopLevel() == TopWindow()) nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(TopWindow(), -1, -1, -1, -1)); return NS_OK; } NS_IMETHODIMP nsWindow::ReparentNativeWidget(nsIWidget *aNewParent) { NS_PRECONDITION(aNewParent, ""); return NS_OK; } nsIWidget* nsWindow::GetParent() { return mParent; } NS_IMETHODIMP nsWindow::Show(PRBool aState) { ALOG("nsWindow[%p]::Show %d", (void*)this, aState); if (mWindowType == eWindowType_invisible) { ALOG("trying to show invisible window! ignoring.."); return NS_ERROR_FAILURE; } if ((aState && !mIsVisible) || (!aState && mIsVisible)) { mIsVisible = aState; if (IsTopLevel()) { // XXX should we bring this to the front when it's shown, // if it's a toplevel widget? // XXX we should synthesize a NS_MOUSE_EXIT (for old top // window)/NS_MOUSE_ENTER (for new top window) since we need // to pretend that the top window always has focus. Not sure // if Show() is the right place to do this, though. if (mIsVisible) { // It just became visible, so send a resize update if necessary // and bring it to the front. Resize(0, 0, gAndroidBounds.width, gAndroidBounds.height, PR_FALSE); BringToFront(); } } else if (FindTopLevel() == TopWindow()) { nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(TopWindow(), -1, -1, -1, -1)); } } #ifdef ANDROID_DEBUG_WIDGET DumpWindows(); #endif return NS_OK; } NS_IMETHODIMP nsWindow::SetModal(PRBool aState) { ALOG("nsWindow[%p]::SetModal %d ignored", (void*)this, aState); return NS_OK; } NS_IMETHODIMP nsWindow::IsVisible(PRBool& aState) { aState = mIsVisible; return NS_OK; } NS_IMETHODIMP nsWindow::ConstrainPosition(PRBool aAllowSlop, PRInt32 *aX, PRInt32 *aY) { ALOG("nsWindow[%p]::ConstrainPosition %d [%d %d]", (void*)this, aAllowSlop, *aX, *aY); // constrain toplevel windows; children we don't care about if (IsTopLevel()) { *aX = 0; *aY = 0; } return NS_OK; } NS_IMETHODIMP nsWindow::Move(PRInt32 aX, PRInt32 aY) { if (IsTopLevel()) return NS_OK; return Resize(aX, aY, mBounds.width, mBounds.height, PR_TRUE); } NS_IMETHODIMP nsWindow::Resize(PRInt32 aWidth, PRInt32 aHeight, PRBool aRepaint) { return Resize(mBounds.x, mBounds.y, aWidth, aHeight, aRepaint); } NS_IMETHODIMP nsWindow::Resize(PRInt32 aX, PRInt32 aY, PRInt32 aWidth, PRInt32 aHeight, PRBool aRepaint) { ALOG("nsWindow[%p]::Resize [%d %d %d %d] (repaint %d)", (void*)this, aX, aY, aWidth, aHeight, aRepaint); PRBool needSizeDispatch = aWidth != mBounds.width || aHeight != mBounds.height; if (IsTopLevel()) { ALOG("... ignoring Resize sizes on toplevel window"); aX = 0; aY = 0; aWidth = gAndroidBounds.width; aHeight = gAndroidBounds.height; } mBounds.x = aX; mBounds.y = aY; mBounds.width = aWidth; mBounds.height = aHeight; if (needSizeDispatch) OnSizeChanged(gfxIntSize(aWidth, aHeight)); // Should we skip honoring aRepaint here? if (aRepaint && FindTopLevel() == TopWindow()) nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(TopWindow(), -1, -1, -1, -1)); return NS_OK; } NS_IMETHODIMP nsWindow::SetZIndex(PRInt32 aZIndex) { ALOG("nsWindow[%p]::SetZIndex %d ignored", (void*)this, aZIndex); return NS_OK; } NS_IMETHODIMP nsWindow::PlaceBehind(nsTopLevelWidgetZPlacement aPlacement, nsIWidget *aWidget, PRBool aActivate) { return NS_OK; } NS_IMETHODIMP nsWindow::SetSizeMode(PRInt32 aMode) { switch (aMode) { case nsSizeMode_Minimized: AndroidBridge::Bridge()->MoveTaskToBack(); break; } return NS_OK; } NS_IMETHODIMP nsWindow::Enable(PRBool aState) { ALOG("nsWindow[%p]::Enable %d ignored", (void*)this, aState); return NS_OK; } NS_IMETHODIMP nsWindow::IsEnabled(PRBool *aState) { *aState = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsWindow::Invalidate(const nsIntRect &aRect, PRBool aIsSynchronous) { ALOG("nsWindow::Invalidate %p [%d %d %d %d]", (void*) this, aRect.x, aRect.y, aRect.width, aRect.height); nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(TopWindow(), -1, -1, -1, -1)); return NS_OK; } NS_IMETHODIMP nsWindow::Update() { return NS_OK; } nsWindow* nsWindow::FindTopLevel() { nsWindow *toplevel = this; while (toplevel) { if (toplevel->IsTopLevel()) return toplevel; toplevel = toplevel->mParent; } ALOG("nsWindow::FindTopLevel(): couldn't find a toplevel or dialog window in this [%p] widget's hierarchy!", (void*)this); return this; } NS_IMETHODIMP nsWindow::SetFocus(PRBool aRaise) { if (!aRaise) ALOG("nsWindow::SetFocus: can't set focus without raising, ignoring aRaise = false!"); gFocusedWindow = this; if (!AndroidBridge::Bridge()) return NS_OK; FindTopLevel()->BringToFront(); return NS_OK; } void nsWindow::BringToFront() { if (FindTopLevel() == TopWindow()) return; if (!IsTopLevel()) { FindTopLevel()->BringToFront(); return; } nsWindow *oldTop = nsnull; if (!gTopLevelWindows.IsEmpty()) oldTop = gTopLevelWindows[0]; gTopLevelWindows.RemoveElement(this); gTopLevelWindows.InsertElementAt(0, this); if (oldTop) { nsGUIEvent event(PR_TRUE, NS_DEACTIVATE, gTopLevelWindows[0]); DispatchEvent(&event); } nsGUIEvent event(PR_TRUE, NS_ACTIVATE, this); DispatchEvent(&event); nsAppShell::gAppShell->PostEvent(new AndroidGeckoEvent(TopWindow(), -1, -1, -1, -1)); } NS_IMETHODIMP nsWindow::GetScreenBounds(nsIntRect &aRect) { nsIntPoint p = WidgetToScreenOffset(); aRect.x = p.x; aRect.y = p.y; aRect.width = mBounds.width; aRect.height = mBounds.height; return NS_OK; } nsIntPoint nsWindow::WidgetToScreenOffset() { nsIntPoint p(0, 0); nsWindow *w = this; while (w && !w->IsTopLevel()) { p.x += w->mBounds.x; p.y += w->mBounds.y; w = w->mParent; } return p; } NS_IMETHODIMP nsWindow::DispatchEvent(nsGUIEvent *aEvent, nsEventStatus &aStatus) { aStatus = DispatchEvent(aEvent); return NS_OK; } nsEventStatus nsWindow::DispatchEvent(nsGUIEvent *aEvent) { if (mEventCallback) { nsEventStatus status = (*mEventCallback)(aEvent); switch (aEvent->message) { case NS_COMPOSITION_START: mIMEComposing = PR_TRUE; break; case NS_COMPOSITION_END: mIMEComposing = PR_FALSE; break; case NS_TEXT_TEXT: mIMEComposingText = static_cast(aEvent)->theText; break; } return status; } return nsEventStatus_eIgnore; } NS_IMETHODIMP nsWindow::SetWindowClass(const nsAString& xulWinType) { return NS_OK; } mozilla::layers::LayerManager* nsWindow::GetLayerManager() { if (mLayerManager) { return mLayerManager; } printf_stderr("nsWindow::GetLayerManager\n"); nsWindow *topWindow = TopWindow(); if (!topWindow) { printf_stderr(" -- no topwindow\n"); mLayerManager = CreateBasicLayerManager(); return mLayerManager; } mUseAcceleratedRendering = GetShouldAccelerate(); if (!mUseAcceleratedRendering || sFailedToCreateGLContext) { printf_stderr(" -- creating basic, not accelerated\n"); mLayerManager = CreateBasicLayerManager(); return mLayerManager; } if (!sGLContext) { // the window we give doesn't matter here sGLContext = mozilla::gl::GLContextProvider::CreateForWindow(this); } if (sGLContext) { nsRefPtr layerManager = new mozilla::layers::LayerManagerOGL(this); if (layerManager && layerManager->Initialize(sGLContext)) mLayerManager = layerManager; } if (!sGLContext || !mLayerManager) { sGLContext = nsnull; sFailedToCreateGLContext = PR_TRUE; mLayerManager = CreateBasicLayerManager(); } return mLayerManager; } gfxASurface* nsWindow::GetThebesSurface() { /* This is really a dummy surface; this is only used when doing reflow, because * we need a RenderingContext to measure text against. */ // XXX this really wants to return already_AddRefed, but this only really gets used // on direct assignment to a gfxASurface return new gfxImageSurface(gfxIntSize(5,5), gfxImageSurface::ImageFormatRGB24); } void nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae) { if (!AndroidBridge::Bridge()) return; switch (ae->Type()) { case AndroidGeckoEvent::SIZE_CHANGED: { int nw = ae->P0().x; int nh = ae->P0().y; if (nw == gAndroidBounds.width && nh == gAndroidBounds.height) { return; } gAndroidBounds.width = nw; gAndroidBounds.height = nh; // tell all the windows about the new size for (size_t i = 0; i < gTopLevelWindows.Length(); ++i) { if (gTopLevelWindows[i]->mIsVisible) gTopLevelWindows[i]->Resize(gAndroidBounds.width, gAndroidBounds.height, PR_TRUE); } break; } case AndroidGeckoEvent::MOTION_EVENT: { TopWindow()->UserActivity(); if (!gTopLevelWindows.IsEmpty()) { nsIntPoint pt(ae->P0()); pt.x = NS_MIN(NS_MAX(pt.x, 0), gAndroidBounds.width - 1); pt.y = NS_MIN(NS_MAX(pt.y, 0), gAndroidBounds.height - 1); nsWindow *target = TopWindow()->FindWindowForPoint(pt); #if 0 ALOG("MOTION_EVENT %f,%f -> %p (visible: %d children: %d)", ae->P0().x, ae->P0().y, (void*)target, target ? target->mIsVisible : 0, target ? target->mChildren.Length() : 0); DumpWindows(); #endif if (target) { if (ae->Count() > 1) target->OnMultitouchEvent(ae); else target->OnMotionEvent(ae); } } break; } case AndroidGeckoEvent::KEY_EVENT: TopWindow()->UserActivity(); if (gFocusedWindow) gFocusedWindow->OnKeyEvent(ae); break; case AndroidGeckoEvent::DRAW: if (TopWindow()) TopWindow()->OnDraw(ae); break; case AndroidGeckoEvent::IME_EVENT: TopWindow()->UserActivity(); if (gFocusedWindow) { gFocusedWindow->OnIMEEvent(ae); } else { NS_WARNING("Sending unexpected IME event to top window"); TopWindow()->OnIMEEvent(ae); } break; default: break; } } void nsWindow::OnAndroidEvent(AndroidGeckoEvent *ae) { if (!AndroidBridge::Bridge()) return; switch (ae->Type()) { case AndroidGeckoEvent::DRAW: OnDraw(ae); break; default: ALOG("Window got targetted android event type %d, but didn't handle!", ae->Type()); break; } } PRBool nsWindow::DrawTo(gfxASurface *targetSurface) { if (!mIsVisible) return PR_FALSE; nsEventStatus status; nsIntRect boundsRect(0, 0, mBounds.width, mBounds.height); // Figure out if any of our children cover this widget completely PRInt32 coveringChildIndex = -1; for (PRUint32 i = 0; i < mChildren.Length(); ++i) { if (mChildren[i]->mBounds.IsEmpty()) continue; if (mChildren[i]->mBounds.Contains(boundsRect)) { coveringChildIndex = PRInt32(i); } } // If we have no covering child, then we need to render this. if (coveringChildIndex == -1) { ALOG("nsWindow[%p]::DrawTo no covering child, drawing this", (void*) this); nsPaintEvent event(PR_TRUE, NS_PAINT, this); event.region = boundsRect; switch (GetLayerManager()->GetBackendType()) { case LayerManager::LAYERS_BASIC: { nsRefPtr ctx = new gfxContext(targetSurface); { AutoLayerManagerSetup setupLayerManager(this, ctx, BasicLayerManager::BUFFER_NONE); status = DispatchEvent(&event); } // XXX uhh.. we can't just ignore this because we no longer have // what we needed before, but let's keep drawing the children anyway? #if 0 if (status == nsEventStatus_eIgnore) return PR_FALSE; #endif // XXX if we got an ignore for the parent, do we still want to draw the children? // We don't really have a good way not to... break; } case LayerManager::LAYERS_OPENGL: { static_cast(GetLayerManager())-> SetClippingRegion(nsIntRegion(boundsRect)); status = DispatchEvent(&event); break; } default: NS_ERROR("Invalid layer manager"); } // We had no covering child, so make sure we draw all the children, // starting from index 0. coveringChildIndex = 0; } gfxPoint offset; if (targetSurface) offset = targetSurface->GetDeviceOffset(); for (PRUint32 i = coveringChildIndex; i < mChildren.Length(); ++i) { if (mChildren[i]->mBounds.IsEmpty() || !mChildren[i]->mBounds.Intersects(boundsRect)) { continue; } if (targetSurface) targetSurface->SetDeviceOffset(offset + gfxPoint(mChildren[i]->mBounds.x, mChildren[i]->mBounds.y)); PRBool ok = mChildren[i]->DrawTo(targetSurface); if (!ok) { ALOG("nsWindow[%p]::DrawTo child %d[%p] returned FALSE!", (void*) this, i, (void*)mChildren[i]); } } if (targetSurface) targetSurface->SetDeviceOffset(offset); return PR_TRUE; } void nsWindow::OnDraw(AndroidGeckoEvent *ae) { AndroidBridge::AutoLocalJNIFrame jniFrame; ALOG(">> OnDraw"); AndroidGeckoSurfaceView& sview(AndroidBridge::Bridge()->SurfaceView()); NS_ASSERTION(!sview.isNull(), "SurfaceView is null!"); if (!IsTopLevel()) { ALOG("##### redraw for window %p, which is not a toplevel window -- sending to toplevel!", (void*) this); DumpWindows(); return; } if (!mIsVisible) { ALOG("##### redraw for window %p, which is not visible -- ignoring!", (void*) this); DumpWindows(); return; } if (GetLayerManager()->GetBackendType() == LayerManager::LAYERS_BASIC) { jobject bytebuf = sview.GetSoftwareDrawBuffer(); if (!bytebuf) { ALOG("no buffer to draw into - skipping draw"); return; } void *buf = AndroidBridge::JNI()->GetDirectBufferAddress(bytebuf); int cap = AndroidBridge::JNI()->GetDirectBufferCapacity(bytebuf); if (!buf || cap < (mBounds.width * mBounds.height * 2)) { ALOG("### Software drawing, but too small a buffer %d expected %d (or no buffer %p)!", cap, mBounds.width * mBounds.height * 2, buf); return; } nsRefPtr targetSurface = new gfxImageSurface((unsigned char *)buf, gfxIntSize(mBounds.width, mBounds.height), mBounds.width * 2, gfxASurface::ImageFormatRGB16_565); DrawTo(targetSurface); sview.Draw2D(bytebuf); } else { int drawType = sview.BeginDrawing(); if (drawType == AndroidGeckoSurfaceView::DRAW_ERROR) { ALOG("##### BeginDrawing failed!"); return; } NS_ASSERTION(sGLContext, "Drawing with GLES without a GL context?"); sGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT); DrawTo(nsnull); if (sGLContext) sGLContext->SwapBuffers(); sview.EndDrawing(); } } void nsWindow::OnSizeChanged(const gfxIntSize& aSize) { int w = aSize.width; int h = aSize.height; ALOG("nsWindow: %p OnSizeChanged [%d %d]", (void*)this, w, h); nsSizeEvent event(PR_TRUE, NS_SIZE, this); InitEvent(event); nsIntRect wsz(0, 0, w, h); event.windowSize = &wsz; event.mWinWidth = w; event.mWinHeight = h; mBounds.width = w; mBounds.height = h; DispatchEvent(&event); } void nsWindow::InitEvent(nsGUIEvent& event, nsIntPoint* aPoint) { if (aPoint) { event.refPoint.x = aPoint->x; event.refPoint.y = aPoint->y; } else { event.refPoint.x = 0; event.refPoint.y = 0; } event.time = PR_Now() / 1000; } void nsWindow::SetInitialAndroidBounds(const gfxIntSize& sz) { if (!gTopLevelWindows.IsEmpty()) { NS_WARNING("SetInitialAndroidBounds called way too late, we already have toplevel windows!"); } gAndroidBounds = sz; } gfxIntSize nsWindow::GetAndroidBounds() { return gAndroidBounds; } void * nsWindow::GetNativeData(PRUint32 aDataType) { switch (aDataType) { // used by GLContextProviderEGL, NULL is EGL_DEFAULT_DISPLAY case NS_NATIVE_DISPLAY: return NULL; case NS_NATIVE_WIDGET: return (void *) this; } return nsnull; } void nsWindow::OnMotionEvent(AndroidGeckoEvent *ae) { PRUint32 msg; switch (ae->Action() & AndroidMotionEvent::ACTION_MASK) { case AndroidMotionEvent::ACTION_DOWN: msg = NS_MOUSE_BUTTON_DOWN; break; case AndroidMotionEvent::ACTION_MOVE: msg = NS_MOUSE_MOVE; break; case AndroidMotionEvent::ACTION_UP: case AndroidMotionEvent::ACTION_CANCEL: msg = NS_MOUSE_BUTTON_UP; break; default: return; } nsIntPoint pt(ae->P0()); nsIntPoint offset = WidgetToScreenOffset(); //ALOG("#### motion pt: %d %d offset: %d %d", pt.x, pt.y, offset.x, offset.y); pt.x -= offset.x; pt.y -= offset.y; // XXX possibly bound the range of pt here. some code may get confused. send_again: nsMouseEvent event(PR_TRUE, msg, this, nsMouseEvent::eReal, nsMouseEvent::eNormal); InitEvent(event, &pt); event.time = ae->Time(); event.isShift = gLeftShift || gRightShift; event.isControl = gSym; event.isMeta = PR_FALSE; event.isAlt = gLeftAlt || gRightAlt; // XXX can we synthesize different buttons? event.button = nsMouseEvent::eLeftButton; if (msg != NS_MOUSE_MOVE) event.clickCount = 1; // XXX add the double-click handling logic here DispatchEvent(&event); if (msg == NS_MOUSE_BUTTON_DOWN) { msg = NS_MOUSE_MOVE; goto send_again; } } static double getDistance(const nsIntPoint &p1, const nsIntPoint &p2) { double deltaX = p2.x - p1.x; double deltaY = p2.y - p1.y; return sqrt(deltaX*deltaX + deltaY*deltaY); } void nsWindow::OnMultitouchEvent(AndroidGeckoEvent *ae) { PRUint32 msg = 0; nsIntPoint midPoint; midPoint.x = ((ae->P0().x + ae->P1().x) / 2); midPoint.y = ((ae->P0().y + ae->P1().y) / 2); nsIntPoint refPoint = midPoint - WidgetToScreenOffset(); double pinchDist = getDistance(ae->P0(), ae->P1()); double pinchDelta = 0; switch (ae->Action() & AndroidMotionEvent::ACTION_MASK) { case AndroidMotionEvent::ACTION_POINTER_DOWN: msg = NS_SIMPLE_GESTURE_MAGNIFY_START; mStartPoint = new nsIntPoint(midPoint); mStartDist = mLastDist = pinchDist; mGestureFinished = false; break; case AndroidMotionEvent::ACTION_MOVE: msg = NS_SIMPLE_GESTURE_MAGNIFY_UPDATE; pinchDelta = pinchDist - mLastDist; mLastDist = pinchDist; break; case AndroidMotionEvent::ACTION_POINTER_UP: msg = NS_SIMPLE_GESTURE_MAGNIFY; pinchDelta = pinchDist - mStartDist; mStartPoint = nsnull; break; default: return; } if (!mGestureFinished) { DispatchGestureEvent(msg, 0, pinchDelta, refPoint, ae->Time()); // If the cumulative pinch delta goes past the threshold, treat this // as a pinch only, and not a swipe. if (fabs(pinchDist - mStartDist) > SWIPE_MAX_PINCH_DELTA) mStartPoint = nsnull; // If we have traveled more than SWIPE_MIN_DISTANCE from the start // point, stop the pinch gesture and fire a swipe event. if (mStartPoint) { double swipeDistance = getDistance(midPoint, *mStartPoint); if (swipeDistance > SWIPE_MIN_DISTANCE) { PRUint32 direction = 0; nsIntPoint motion = midPoint - *mStartPoint; if (motion.x < -swipeDistance/2) direction |= nsIDOMSimpleGestureEvent::DIRECTION_LEFT; if (motion.x > swipeDistance/2) direction |= nsIDOMSimpleGestureEvent::DIRECTION_RIGHT; if (motion.y < -swipeDistance/2) direction |= nsIDOMSimpleGestureEvent::DIRECTION_UP; if (motion.y > swipeDistance/2) direction |= nsIDOMSimpleGestureEvent::DIRECTION_DOWN; // Finish the pinch gesture, then fire the swipe event: msg = NS_SIMPLE_GESTURE_MAGNIFY; DispatchGestureEvent(msg, 0, pinchDist - mStartDist, refPoint, ae->Time()); msg = NS_SIMPLE_GESTURE_SWIPE; DispatchGestureEvent(msg, direction, 0, refPoint, ae->Time()); // Don't generate any more gesture events for this touch. mGestureFinished = true; } } } } void nsWindow::DispatchGestureEvent(PRUint32 msg, PRUint32 direction, double delta, const nsIntPoint &refPoint, PRUint64 time) { nsSimpleGestureEvent event(PR_TRUE, msg, this, direction, delta); event.isShift = gLeftShift || gRightShift; event.isControl = gSym; event.isMeta = PR_FALSE; event.isAlt = gLeftAlt || gRightAlt; event.time = time; event.refPoint = refPoint; DispatchEvent(&event); } void nsWindow::InitKeyEvent(nsKeyEvent& event, AndroidGeckoEvent& key) { switch (key.KeyCode()) { case AndroidKeyEvent::KEYCODE_UNKNOWN: case AndroidKeyEvent::KEYCODE_HOME: break; case AndroidKeyEvent::KEYCODE_BACK: event.keyCode = NS_VK_ESCAPE; break; case AndroidKeyEvent::KEYCODE_CALL: case AndroidKeyEvent::KEYCODE_ENDCALL: break; case AndroidKeyEvent::KEYCODE_0: case AndroidKeyEvent::KEYCODE_1: case AndroidKeyEvent::KEYCODE_2: case AndroidKeyEvent::KEYCODE_3: case AndroidKeyEvent::KEYCODE_4: case AndroidKeyEvent::KEYCODE_5: case AndroidKeyEvent::KEYCODE_6: case AndroidKeyEvent::KEYCODE_7: case AndroidKeyEvent::KEYCODE_8: case AndroidKeyEvent::KEYCODE_9: event.keyCode = key.KeyCode() - AndroidKeyEvent::KEYCODE_0 + NS_VK_0; break; case AndroidKeyEvent::KEYCODE_STAR: event.keyCode = NS_VK_MULTIPLY; break; case AndroidKeyEvent::KEYCODE_POUND: break; case AndroidKeyEvent::KEYCODE_DPAD_UP: event.keyCode = NS_VK_UP; break; case AndroidKeyEvent::KEYCODE_DPAD_DOWN: event.keyCode = NS_VK_DOWN; break; case AndroidKeyEvent::KEYCODE_SOFT_LEFT: case AndroidKeyEvent::KEYCODE_DPAD_LEFT: event.keyCode = NS_VK_LEFT; break; case AndroidKeyEvent::KEYCODE_SOFT_RIGHT: case AndroidKeyEvent::KEYCODE_DPAD_RIGHT: event.keyCode = NS_VK_RIGHT; break; case AndroidKeyEvent::KEYCODE_VOLUME_UP: case AndroidKeyEvent::KEYCODE_VOLUME_DOWN: case AndroidKeyEvent::KEYCODE_POWER: case AndroidKeyEvent::KEYCODE_CAMERA: case AndroidKeyEvent::KEYCODE_CLEAR: break; case AndroidKeyEvent::KEYCODE_A: case AndroidKeyEvent::KEYCODE_B: case AndroidKeyEvent::KEYCODE_C: case AndroidKeyEvent::KEYCODE_D: case AndroidKeyEvent::KEYCODE_E: case AndroidKeyEvent::KEYCODE_F: case AndroidKeyEvent::KEYCODE_G: case AndroidKeyEvent::KEYCODE_H: case AndroidKeyEvent::KEYCODE_I: case AndroidKeyEvent::KEYCODE_J: case AndroidKeyEvent::KEYCODE_K: case AndroidKeyEvent::KEYCODE_L: case AndroidKeyEvent::KEYCODE_M: case AndroidKeyEvent::KEYCODE_N: case AndroidKeyEvent::KEYCODE_O: case AndroidKeyEvent::KEYCODE_P: case AndroidKeyEvent::KEYCODE_Q: case AndroidKeyEvent::KEYCODE_R: case AndroidKeyEvent::KEYCODE_S: case AndroidKeyEvent::KEYCODE_T: case AndroidKeyEvent::KEYCODE_U: case AndroidKeyEvent::KEYCODE_V: case AndroidKeyEvent::KEYCODE_W: case AndroidKeyEvent::KEYCODE_X: case AndroidKeyEvent::KEYCODE_Y: case AndroidKeyEvent::KEYCODE_Z: event.keyCode = key.KeyCode() - AndroidKeyEvent::KEYCODE_A + NS_VK_A; break; case AndroidKeyEvent::KEYCODE_COMMA: event.keyCode = NS_VK_COMMA; break; case AndroidKeyEvent::KEYCODE_PERIOD: event.keyCode = NS_VK_PERIOD; break; case AndroidKeyEvent::KEYCODE_ALT_LEFT: case AndroidKeyEvent::KEYCODE_ALT_RIGHT: case AndroidKeyEvent::KEYCODE_SHIFT_LEFT: case AndroidKeyEvent::KEYCODE_SHIFT_RIGHT: break; case AndroidKeyEvent::KEYCODE_TAB: event.keyCode = NS_VK_TAB; break; case AndroidKeyEvent::KEYCODE_SPACE: event.keyCode = NS_VK_SPACE; break; case AndroidKeyEvent::KEYCODE_SYM: case AndroidKeyEvent::KEYCODE_EXPLORER: case AndroidKeyEvent::KEYCODE_ENVELOPE: break; case AndroidKeyEvent::KEYCODE_DPAD_CENTER: case AndroidKeyEvent::KEYCODE_ENTER: event.keyCode = NS_VK_RETURN; break; case AndroidKeyEvent::KEYCODE_DEL: event.keyCode = NS_VK_BACK; break; case AndroidKeyEvent::KEYCODE_GRAVE: break; case AndroidKeyEvent::KEYCODE_MINUS: event.keyCode = NS_VK_SUBTRACT; break; case AndroidKeyEvent::KEYCODE_EQUALS: event.keyCode = NS_VK_EQUALS; break; case AndroidKeyEvent::KEYCODE_LEFT_BRACKET: event.keyCode = NS_VK_OPEN_BRACKET; break; case AndroidKeyEvent::KEYCODE_RIGHT_BRACKET: event.keyCode = NS_VK_CLOSE_BRACKET; break; case AndroidKeyEvent::KEYCODE_BACKSLASH: event.keyCode = NS_VK_BACK_SLASH; break; case AndroidKeyEvent::KEYCODE_SEMICOLON: event.keyCode = NS_VK_SEMICOLON; break; case AndroidKeyEvent::KEYCODE_APOSTROPHE: event.keyCode = NS_VK_QUOTE; break; case AndroidKeyEvent::KEYCODE_SLASH: event.keyCode = NS_VK_SLASH; break; case AndroidKeyEvent::KEYCODE_AT: case AndroidKeyEvent::KEYCODE_NUM: case AndroidKeyEvent::KEYCODE_HEADSETHOOK: case AndroidKeyEvent::KEYCODE_FOCUS: break; case AndroidKeyEvent::KEYCODE_PLUS: event.keyCode = NS_VK_ADD; break; case AndroidKeyEvent::KEYCODE_MENU: case AndroidKeyEvent::KEYCODE_NOTIFICATION: case AndroidKeyEvent::KEYCODE_SEARCH: case AndroidKeyEvent::KEYCODE_MEDIA_PLAY_PAUSE: case AndroidKeyEvent::KEYCODE_MEDIA_STOP: case AndroidKeyEvent::KEYCODE_MEDIA_NEXT: case AndroidKeyEvent::KEYCODE_MEDIA_PREVIOUS: case AndroidKeyEvent::KEYCODE_MEDIA_REWIND: case AndroidKeyEvent::KEYCODE_MEDIA_FAST_FORWARD: case AndroidKeyEvent::KEYCODE_MUTE: break; default: ALOG("Unknown key code!"); break; } event.charCode = key.UnicodeChar(); event.isShift = gLeftShift || gRightShift; event.isControl = PR_FALSE; event.isAlt = PR_FALSE; event.isMeta = PR_FALSE; event.time = key.Time(); } void nsWindow::HandleSpecialKey(AndroidGeckoEvent *ae) { nsCOMPtr command; PRBool isDown = ae->Action() == AndroidKeyEvent::ACTION_DOWN; PRBool doCommand = PR_FALSE; PRUint32 keyCode = ae->KeyCode(); if (isDown) { switch (keyCode) { case AndroidKeyEvent::KEYCODE_VOLUME_UP: command = nsWidgetAtoms::VolumeUp; doCommand = PR_TRUE; break; case AndroidKeyEvent::KEYCODE_VOLUME_DOWN: command = nsWidgetAtoms::VolumeDown; doCommand = PR_TRUE; break; } } else { switch (keyCode) { case AndroidKeyEvent::KEYCODE_BACK: { nsKeyEvent pressEvent(PR_TRUE, NS_KEY_PRESS, this); InitKeyEvent(pressEvent, *ae); DispatchEvent(&pressEvent); return; } case AndroidKeyEvent::KEYCODE_MENU: command = nsWidgetAtoms::Menu; doCommand = PR_TRUE; break; case AndroidKeyEvent::KEYCODE_SEARCH: command = nsWidgetAtoms::Search; doCommand = PR_TRUE; break; default: ALOG("Unknown special key code!"); return; } } if (doCommand) { nsCommandEvent event(PR_TRUE, nsWidgetAtoms::onAppCommand, command, this); InitEvent(event); DispatchEvent(&event); } } void nsWindow::OnKeyEvent(AndroidGeckoEvent *ae) { PRUint32 msg; switch (ae->Action()) { case AndroidKeyEvent::ACTION_DOWN: msg = NS_KEY_DOWN; break; case AndroidKeyEvent::ACTION_UP: msg = NS_KEY_UP; break; case AndroidKeyEvent::ACTION_MULTIPLE: { nsTextEvent event(PR_TRUE, NS_TEXT_TEXT, this); event.theText.Assign(ae->Characters()); DispatchEvent(&event); } return; default: ALOG("Unknown key action event!"); return; } PRBool isDown = ae->Action() == AndroidKeyEvent::ACTION_DOWN; switch (ae->KeyCode()) { case AndroidKeyEvent::KEYCODE_SHIFT_LEFT: gLeftShift = isDown; break; case AndroidKeyEvent::KEYCODE_SHIFT_RIGHT: gRightShift = isDown; break; case AndroidKeyEvent::KEYCODE_ALT_LEFT: gLeftAlt = isDown; break; case AndroidKeyEvent::KEYCODE_ALT_RIGHT: gRightAlt = isDown; break; case AndroidKeyEvent::KEYCODE_SYM: gSym = isDown; break; case AndroidKeyEvent::KEYCODE_BACK: case AndroidKeyEvent::KEYCODE_MENU: case AndroidKeyEvent::KEYCODE_SEARCH: case AndroidKeyEvent::KEYCODE_VOLUME_UP: case AndroidKeyEvent::KEYCODE_VOLUME_DOWN: HandleSpecialKey(ae); return; } nsKeyEvent event(PR_TRUE, msg, this); InitKeyEvent(event, *ae); if (event.charCode) event.keyCode = 0; DispatchEvent(&event); if (isDown) { nsKeyEvent pressEvent(PR_TRUE, NS_KEY_PRESS, this); InitKeyEvent(pressEvent, *ae); #ifdef ANDROID_DEBUG_WIDGET ALOG("Dispatching key event with keyCode %d charCode %d shift %d alt %d sym/ctrl %d metamask %d", event.keyCode, event.charCode, event.isShift, event.isAlt, event.isControl, ae->MetaState()); #endif DispatchEvent(&pressEvent); } } #ifdef ANDROID_DEBUG_IME #define ALOGIME(args...) ALOG(args) #else #define ALOGIME(args...) #endif void nsWindow::OnIMEAddRange(AndroidGeckoEvent *ae) { //ALOGIME("IME: IME_ADD_RANGE"); nsTextRange range; range.mStartOffset = ae->Offset(); range.mEndOffset = range.mStartOffset + ae->Count(); range.mRangeType = ae->RangeType(); range.mRangeStyle.mDefinedStyles = ae->RangeStyles(); range.mRangeStyle.mLineStyle = nsTextRangeStyle::LINESTYLE_SOLID; range.mRangeStyle.mForegroundColor = NS_RGBA( ((ae->RangeForeColor() >> 16) & 0xff), ((ae->RangeForeColor() >> 8) & 0xff), (ae->RangeForeColor() & 0xff), ((ae->RangeForeColor() >> 24) & 0xff)); range.mRangeStyle.mBackgroundColor = NS_RGBA( ((ae->RangeBackColor() >> 16) & 0xff), ((ae->RangeBackColor() >> 8) & 0xff), (ae->RangeBackColor() & 0xff), ((ae->RangeBackColor() >> 24) & 0xff)); mIMERanges.AppendElement(range); return; } void nsWindow::OnIMEEvent(AndroidGeckoEvent *ae) { switch (ae->Action()) { case AndroidGeckoEvent::IME_COMPOSITION_END: { ALOGIME("IME: IME_COMPOSITION_END"); nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, this); InitEvent(event, nsnull); DispatchEvent(&event); } return; case AndroidGeckoEvent::IME_COMPOSITION_BEGIN: { ALOGIME("IME: IME_COMPOSITION_BEGIN"); nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_START, this); InitEvent(event, nsnull); DispatchEvent(&event); } return; case AndroidGeckoEvent::IME_ADD_RANGE: { OnIMEAddRange(ae); } return; case AndroidGeckoEvent::IME_SET_TEXT: { OnIMEAddRange(ae); nsTextEvent event(PR_TRUE, NS_TEXT_TEXT, this); InitEvent(event, nsnull); event.theText.Assign(ae->Characters()); event.rangeArray = mIMERanges.Elements(); event.rangeCount = mIMERanges.Length(); ALOGIME("IME: IME_SET_TEXT: l=%u, r=%u", event.theText.Length(), mIMERanges.Length()); DispatchEvent(&event); mIMERanges.Clear(); } return; case AndroidGeckoEvent::IME_GET_TEXT: { ALOGIME("IME: IME_GET_TEXT: o=%u, l=%u", ae->Offset(), ae->Count()); nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, this); InitEvent(event, nsnull); event.InitForQueryTextContent(ae->Offset(), ae->Count()); DispatchEvent(&event); if (!event.mSucceeded) { ALOGIME("IME: -> failed"); AndroidBridge::Bridge()->ReturnIMEQueryResult( nsnull, 0, 0, 0); return; } else if (!event.mWasAsync) { AndroidBridge::Bridge()->ReturnIMEQueryResult( event.mReply.mString.get(), event.mReply.mString.Length(), 0, 0); } //ALOGIME("IME: -> l=%u", event.mReply.mString.Length()); } return; case AndroidGeckoEvent::IME_DELETE_TEXT: { ALOGIME("IME: IME_DELETE_TEXT"); nsKeyEvent event(PR_TRUE, NS_KEY_PRESS, this); InitEvent(event, nsnull); event.keyCode = NS_VK_BACK; DispatchEvent(&event); } return; case AndroidGeckoEvent::IME_SET_SELECTION: { ALOGIME("IME: IME_SET_SELECTION: o=%u, l=%d", ae->Offset(), ae->Count()); nsSelectionEvent selEvent(PR_TRUE, NS_SELECTION_SET, this); InitEvent(selEvent, nsnull); selEvent.mOffset = PRUint32(ae->Count() >= 0 ? ae->Offset() : ae->Offset() + ae->Count()); selEvent.mLength = PRUint32(PR_ABS(ae->Count())); selEvent.mReversed = ae->Count() >= 0 ? PR_FALSE : PR_TRUE; DispatchEvent(&selEvent); } return; case AndroidGeckoEvent::IME_GET_SELECTION: { ALOGIME("IME: IME_GET_SELECTION"); nsQueryContentEvent event(PR_TRUE, NS_QUERY_SELECTED_TEXT, this); InitEvent(event, nsnull); DispatchEvent(&event); if (!event.mSucceeded) { ALOGIME("IME: -> failed"); AndroidBridge::Bridge()->ReturnIMEQueryResult( nsnull, 0, 0, 0); return; } else if (!event.mWasAsync) { AndroidBridge::Bridge()->ReturnIMEQueryResult( event.mReply.mString.get(), event.mReply.mString.Length(), event.GetSelectionStart(), event.GetSelectionEnd() - event.GetSelectionStart()); } //ALOGIME("IME: -> o=%u, l=%u", event.mReply.mOffset, event.mReply.mString.Length()); } return; } } nsWindow * nsWindow::FindWindowForPoint(const nsIntPoint& pt) { if (!mBounds.Contains(pt)) return nsnull; // children mBounds are relative to their parent nsIntPoint childPoint(pt.x - mBounds.x, pt.y - mBounds.y); for (PRUint32 i = 0; i < mChildren.Length(); ++i) { if (mChildren[i]->mBounds.Contains(childPoint)) return mChildren[i]->FindWindowForPoint(childPoint); } return this; } void nsWindow::UserActivity() { if (!mIdleService) { mIdleService = do_GetService("@mozilla.org/widget/idleservice;1"); } if (mIdleService) { mIdleService->ResetIdleTimeOut(); } } NS_IMETHODIMP nsWindow::ResetInputState() { //ALOGIME("IME: ResetInputState: s=%d", aState); // Cancel composition on Gecko side if (mIMEComposing) { nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, this); InitEvent(textEvent, nsnull); textEvent.theText = mIMEComposingText; DispatchEvent(&textEvent); mIMEComposingText.Truncate(0); nsCompositionEvent event(PR_TRUE, NS_COMPOSITION_END, this); InitEvent(event, nsnull); DispatchEvent(&event); } AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_RESETINPUTSTATE, 0); return NS_OK; } NS_IMETHODIMP nsWindow::SetIMEEnabled(PRUint32 aState) { ALOGIME("IME: SetIMEEnabled: s=%d", aState); mIMEEnabled = aState; AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_SETENABLED, int(aState)); return NS_OK; } NS_IMETHODIMP nsWindow::GetIMEEnabled(PRUint32* aState) { *aState = mIMEEnabled; return NS_OK; } NS_IMETHODIMP nsWindow::CancelIMEComposition() { ALOGIME("IME: CancelIMEComposition"); // Cancel composition on Gecko side if (mIMEComposing) { nsTextEvent textEvent(PR_TRUE, NS_TEXT_TEXT, this); InitEvent(textEvent, nsnull); DispatchEvent(&textEvent); mIMEComposingText.Truncate(0); nsCompositionEvent compEvent(PR_TRUE, NS_COMPOSITION_END, this); InitEvent(compEvent, nsnull); DispatchEvent(&compEvent); } AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_CANCELCOMPOSITION, 0); return NS_OK; } NS_IMETHODIMP nsWindow::OnIMEFocusChange(PRBool aFocus) { ALOGIME("IME: OnIMEFocusChange: f=%d", aFocus); AndroidBridge::NotifyIME(AndroidBridge::NOTIFY_IME_FOCUSCHANGE, int(aFocus)); return NS_OK; } NS_IMETHODIMP nsWindow::OnIMETextChange(PRUint32 aStart, PRUint32 aOldEnd, PRUint32 aNewEnd) { ALOGIME("IME: OnIMETextChange: s=%d, oe=%d, ne=%d", aStart, aOldEnd, aNewEnd); // A quirk in Android makes it necessary to pass the whole text // from index 0 to index aNewEnd. The more efficient way would // have been passing the substring from index aStart to index aNewEnd if (aNewEnd > 0) { nsQueryContentEvent event(PR_TRUE, NS_QUERY_TEXT_CONTENT, this); InitEvent(event, nsnull); event.InitForQueryTextContent(0, aNewEnd); DispatchEvent(&event); if (!event.mSucceeded) return NS_OK; AndroidBridge::NotifyIMEChange(event.mReply.mString.get(), event.mReply.mString.Length(), aStart, aOldEnd, aNewEnd); } else { AndroidBridge::NotifyIMEChange(nsnull, 0, aStart, aOldEnd, aNewEnd); } return NS_OK; } NS_IMETHODIMP nsWindow::OnIMESelectionChange(void) { ALOGIME("IME: OnIMESelectionChange"); nsQueryContentEvent event(PR_TRUE, NS_QUERY_SELECTED_TEXT, this); InitEvent(event, nsnull); DispatchEvent(&event); if (!event.mSucceeded) return NS_OK; AndroidBridge::NotifyIMEChange(nsnull, 0, int(event.mReply.mOffset), int(event.mReply.mOffset + event.mReply.mString.Length()), -1); return NS_OK; } nsIMEUpdatePreference nsWindow::GetIMEUpdatePreference() { return nsIMEUpdatePreference(PR_TRUE, PR_TRUE); }