| /* |
| * Copyright 2009, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "PaintPlugin.h" |
| |
| #include <fcntl.h> |
| #include <math.h> |
| #include <string.h> |
| |
| extern NPNetscapeFuncs* browser; |
| extern ANPLogInterfaceV0 gLogI; |
| extern ANPCanvasInterfaceV0 gCanvasI; |
| extern ANPPaintInterfaceV0 gPaintI; |
| extern ANPPathInterfaceV0 gPathI; |
| extern ANPSurfaceInterfaceV0 gSurfaceI; |
| extern ANPSystemInterfaceV0 gSystemI; |
| extern ANPTypefaceInterfaceV0 gTypefaceI; |
| extern ANPWindowInterfaceV0 gWindowI; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| PaintPlugin::PaintPlugin(NPP inst) : SurfaceSubPlugin(inst) { |
| |
| m_isTouchActive = false; |
| m_isTouchCurrentInput = true; |
| m_activePaintColor = s_redColor; |
| |
| memset(&m_drawingSurface, 0, sizeof(m_drawingSurface)); |
| memset(&m_inputToggle, 0, sizeof(m_inputToggle)); |
| memset(&m_colorToggle, 0, sizeof(m_colorToggle)); |
| memset(&m_fullScreenToggle, 0, sizeof(m_fullScreenToggle)); |
| memset(&m_clearSurface, 0, sizeof(m_clearSurface)); |
| |
| // initialize the drawing surface |
| m_surface = NULL; |
| |
| // initialize the path |
| m_touchPath = gPathI.newPath(); |
| if(!m_touchPath) |
| gLogI.log(kError_ANPLogType, "----%p Unable to create the touch path", inst); |
| |
| // initialize the paint colors |
| m_paintSurface = gPaintI.newPaint(); |
| gPaintI.setFlags(m_paintSurface, gPaintI.getFlags(m_paintSurface) | kAntiAlias_ANPPaintFlag); |
| gPaintI.setColor(m_paintSurface, 0xFFC0C0C0); |
| gPaintI.setTextSize(m_paintSurface, 18); |
| |
| m_paintButton = gPaintI.newPaint(); |
| gPaintI.setFlags(m_paintButton, gPaintI.getFlags(m_paintButton) | kAntiAlias_ANPPaintFlag); |
| gPaintI.setColor(m_paintButton, 0xFFA8A8A8); |
| |
| // initialize the typeface (set the colors) |
| ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle); |
| gPaintI.setTypeface(m_paintSurface, tf); |
| gTypefaceI.unref(tf); |
| |
| //register for touch events |
| ANPEventFlags flags = kTouch_ANPEventFlag; |
| NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags); |
| if (err != NPERR_NO_ERROR) { |
| gLogI.log(kError_ANPLogType, "Error selecting input events."); |
| } |
| } |
| |
| PaintPlugin::~PaintPlugin() { |
| gPathI.deletePath(m_touchPath); |
| gPaintI.deletePaint(m_paintSurface); |
| gPaintI.deletePaint(m_paintButton); |
| |
| setContext(NULL); |
| destroySurface(); |
| } |
| |
| ANPCanvas* PaintPlugin::getCanvas(ANPRectI* dirtyRect) { |
| |
| ANPBitmap bitmap; |
| JNIEnv* env = NULL; |
| if (!m_surface || gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK || |
| !gSurfaceI.lock(env, m_surface, &bitmap, dirtyRect)) { |
| return NULL; |
| } |
| |
| ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap); |
| |
| // clip the canvas to the dirty rect b/c the surface is only required to |
| // copy a minimum of the dirty rect and may copy more. The clipped canvas |
| // however will never write to pixels outside of the clipped area. |
| if (dirtyRect) { |
| ANPRectF clipR; |
| clipR.left = dirtyRect->left; |
| clipR.top = dirtyRect->top; |
| clipR.right = dirtyRect->right; |
| clipR.bottom = dirtyRect->bottom; |
| gCanvasI.clipRect(canvas, &clipR); |
| } |
| |
| return canvas; |
| } |
| |
| ANPCanvas* PaintPlugin::getCanvas(ANPRectF* dirtyRect) { |
| |
| ANPRectI newRect; |
| newRect.left = (int) dirtyRect->left; |
| newRect.top = (int) dirtyRect->top; |
| newRect.right = (int) dirtyRect->right; |
| newRect.bottom = (int) dirtyRect->bottom; |
| |
| return getCanvas(&newRect); |
| } |
| |
| void PaintPlugin::releaseCanvas(ANPCanvas* canvas) { |
| JNIEnv* env = NULL; |
| if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) { |
| gSurfaceI.unlock(env, m_surface); |
| } |
| gCanvasI.deleteCanvas(canvas); |
| } |
| |
| void PaintPlugin::drawCleanPlugin(ANPCanvas* canvas) { |
| NPP instance = this->inst(); |
| PluginObject *obj = (PluginObject*) instance->pdata; |
| |
| // if no canvas get a locked canvas |
| if (!canvas) |
| canvas = getCanvas(); |
| |
| if (!canvas) |
| return; |
| |
| const float buttonWidth = 60; |
| const float buttonHeight = 30; |
| const int W = obj->window->width; |
| const int H = obj->window->height; |
| |
| // color the plugin canvas |
| gCanvasI.drawColor(canvas, 0xFFCDCDCD); |
| |
| // get font metrics |
| ANPFontMetrics fontMetrics; |
| gPaintI.getFontMetrics(m_paintSurface, &fontMetrics); |
| |
| // draw the input toggle button |
| m_inputToggle.left = 5; |
| m_inputToggle.top = H - buttonHeight - 5; |
| m_inputToggle.right = m_inputToggle.left + buttonWidth; |
| m_inputToggle.bottom = m_inputToggle.top + buttonHeight; |
| gCanvasI.drawRect(canvas, &m_inputToggle, m_paintButton); |
| const char* inputText = m_isTouchCurrentInput ? "Touch" : "Mouse"; |
| gCanvasI.drawText(canvas, inputText, strlen(inputText), m_inputToggle.left + 5, |
| m_inputToggle.top - fontMetrics.fTop, m_paintSurface); |
| |
| // draw the color selector button |
| m_colorToggle.left = (W/3) - (buttonWidth/2); |
| m_colorToggle.top = H - buttonHeight - 5; |
| m_colorToggle.right = m_colorToggle.left + buttonWidth; |
| m_colorToggle.bottom = m_colorToggle.top + buttonHeight; |
| gCanvasI.drawRect(canvas, &m_colorToggle, m_paintButton); |
| const char* colorText = getColorText(); |
| gCanvasI.drawText(canvas, colorText, strlen(colorText), m_colorToggle.left + 5, |
| m_colorToggle.top - fontMetrics.fTop, m_paintSurface); |
| |
| // draw the full-screen toggle button |
| m_fullScreenToggle.left = ((W*2)/3) - (buttonWidth/2); |
| m_fullScreenToggle.top = H - buttonHeight - 5; |
| m_fullScreenToggle.right = m_fullScreenToggle.left + buttonWidth; |
| m_fullScreenToggle.bottom = m_fullScreenToggle.top + buttonHeight; |
| gCanvasI.drawRect(canvas, &m_fullScreenToggle, m_paintButton); |
| const char* fullScreenText = "Full"; |
| gCanvasI.drawText(canvas, fullScreenText, strlen(fullScreenText), |
| m_fullScreenToggle.left + 5, |
| m_fullScreenToggle.top - fontMetrics.fTop, m_paintSurface); |
| |
| // draw the clear canvas button |
| m_clearSurface.left = W - buttonWidth - 5; |
| m_clearSurface.top = H - buttonHeight - 5; |
| m_clearSurface.right = m_clearSurface.left + buttonWidth; |
| m_clearSurface.bottom = m_clearSurface.top + buttonHeight; |
| gCanvasI.drawRect(canvas, &m_clearSurface, m_paintButton); |
| const char* clearText = "Clear"; |
| gCanvasI.drawText(canvas, clearText, strlen(clearText), m_clearSurface.left + 5, |
| m_clearSurface.top - fontMetrics.fTop, m_paintSurface); |
| |
| // draw the drawing surface box (5 px from the edge) |
| m_drawingSurface.left = 5; |
| m_drawingSurface.top = 5; |
| m_drawingSurface.right = W - 5; |
| m_drawingSurface.bottom = m_colorToggle.top - 5; |
| gCanvasI.drawRect(canvas, &m_drawingSurface, m_paintSurface); |
| |
| // release the canvas |
| releaseCanvas(canvas); |
| } |
| |
| const char* PaintPlugin::getColorText() { |
| |
| if (m_activePaintColor == s_blueColor) |
| return "Blue"; |
| else if (m_activePaintColor == s_greenColor) |
| return "Green"; |
| else |
| return "Red"; |
| } |
| |
| jobject PaintPlugin::getSurface() { |
| if (m_surface) { |
| return m_surface; |
| } |
| |
| // load the appropriate java class and instantiate it |
| JNIEnv* env = NULL; |
| if (gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { |
| gLogI.log(kError_ANPLogType, " ---- getSurface: failed to get env"); |
| return NULL; |
| } |
| |
| const char* className = "com.android.sampleplugin.PaintSurface"; |
| jclass paintClass = gSystemI.loadJavaClass(inst(), className); |
| |
| if(!paintClass) { |
| gLogI.log(kError_ANPLogType, " ---- getSurface: failed to load class"); |
| return NULL; |
| } |
| |
| PluginObject *obj = (PluginObject*) inst()->pdata; |
| const int pW = obj->window->width; |
| const int pH = obj->window->height; |
| |
| jmethodID constructor = env->GetMethodID(paintClass, "<init>", "(Landroid/content/Context;III)V"); |
| jobject paintSurface = env->NewObject(paintClass, constructor, m_context, (int)inst(), pW, pH); |
| |
| if(!paintSurface) { |
| gLogI.log(kError_ANPLogType, " ---- getSurface: failed to construct object"); |
| return NULL; |
| } |
| |
| m_surface = env->NewGlobalRef(paintSurface); |
| return m_surface; |
| } |
| |
| void PaintPlugin::destroySurface() { |
| JNIEnv* env = NULL; |
| if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) { |
| |
| // detach the native code from the object |
| jclass javaClass = env->GetObjectClass(m_surface); |
| jmethodID invalMethod = env->GetMethodID(javaClass, "invalidateNPP", "()V"); |
| env->CallVoidMethod(m_surface, invalMethod); |
| |
| env->DeleteGlobalRef(m_surface); |
| m_surface = NULL; |
| } |
| } |
| |
| int16_t PaintPlugin::handleEvent(const ANPEvent* evt) { |
| switch (evt->eventType) { |
| case kTouch_ANPEventType: { |
| float x = (float) evt->data.touch.x; |
| float y = (float) evt->data.touch.y; |
| if (kDown_ANPTouchAction == evt->data.touch.action && m_isTouchCurrentInput) { |
| |
| ANPRectF* rect = validTouch(evt->data.touch.x, evt->data.touch.y); |
| if(rect == &m_drawingSurface) { |
| m_isTouchActive = true; |
| gPathI.moveTo(m_touchPath, x, y); |
| paintTouch(); |
| return 1; |
| } |
| |
| } else if (kMove_ANPTouchAction == evt->data.touch.action && m_isTouchActive) { |
| gPathI.lineTo(m_touchPath, x, y); |
| paintTouch(); |
| return 1; |
| } else if (kUp_ANPTouchAction == evt->data.touch.action && m_isTouchActive) { |
| gPathI.lineTo(m_touchPath, x, y); |
| paintTouch(); |
| m_isTouchActive = false; |
| gPathI.reset(m_touchPath); |
| return 1; |
| } else if (kCancel_ANPTouchAction == evt->data.touch.action) { |
| m_isTouchActive = false; |
| gPathI.reset(m_touchPath); |
| return 1; |
| } else if (kDoubleTap_ANPTouchAction == evt->data.touch.action) { |
| gWindowI.requestCenterFitZoom(inst()); |
| return 1; |
| |
| } |
| break; |
| } |
| case kMouse_ANPEventType: { |
| |
| if (m_isTouchActive) |
| gLogI.log(kError_ANPLogType, "----%p Received unintended mouse event", inst()); |
| |
| if (kDown_ANPMouseAction == evt->data.mouse.action) { |
| ANPRectF* rect = validTouch(evt->data.mouse.x, evt->data.mouse.y); |
| if (rect == &m_drawingSurface) |
| paintMouse(evt->data.mouse.x, evt->data.mouse.y); |
| else if (rect == &m_inputToggle) |
| toggleInputMethod(); |
| else if (rect == &m_colorToggle) |
| togglePaintColor(); |
| else if (rect == &m_fullScreenToggle) |
| gWindowI.requestFullScreen(inst()); |
| else if (rect == &m_clearSurface) |
| drawCleanPlugin(); |
| } |
| return 1; |
| } |
| case kCustom_ANPEventType: { |
| |
| switch (evt->data.other[0]) { |
| case kSurfaceCreated_CustomEvent: |
| gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceCreated"); |
| /* The second draw call is added to cover up a problem in this |
| plugin and is not a recommended usage pattern. This plugin |
| does not correctly make partial updates to the double |
| buffered surface and this second call hides that problem. |
| */ |
| drawCleanPlugin(); |
| drawCleanPlugin(); |
| break; |
| case kSurfaceChanged_CustomEvent: { |
| gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceChanged"); |
| |
| int width = evt->data.other[1]; |
| int height = evt->data.other[2]; |
| |
| PluginObject *obj = (PluginObject*) inst()->pdata; |
| const int pW = obj->window->width; |
| const int pH = obj->window->height; |
| // compare to the plugin's surface dimensions |
| if (pW != width || pH != height) |
| gLogI.log(kError_ANPLogType, |
| "----%p Invalid Surface Dimensions (%d,%d):(%d,%d)", |
| inst(), pW, pH, width, height); |
| break; |
| } |
| case kSurfaceDestroyed_CustomEvent: |
| gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceDestroyed"); |
| break; |
| } |
| break; // end KCustom_ANPEventType |
| } |
| default: |
| break; |
| } |
| return 0; // unknown or unhandled event |
| } |
| |
| ANPRectF* PaintPlugin::validTouch(int x, int y) { |
| |
| //convert to float |
| float fx = (int) x; |
| float fy = (int) y; |
| |
| if (fx > m_drawingSurface.left && fx < m_drawingSurface.right && fy > m_drawingSurface.top && fy < m_drawingSurface.bottom) |
| return &m_drawingSurface; |
| else if (fx > m_inputToggle.left && fx < m_inputToggle.right && fy > m_inputToggle.top && fy < m_inputToggle.bottom) |
| return &m_inputToggle; |
| else if (fx > m_colorToggle.left && fx < m_colorToggle.right && fy > m_colorToggle.top && fy < m_colorToggle.bottom) |
| return &m_colorToggle; |
| else if (fx > m_fullScreenToggle.left && fx < m_fullScreenToggle.right && fy > m_fullScreenToggle.top && fy < m_fullScreenToggle.bottom) |
| return &m_fullScreenToggle; |
| else if (fx > m_clearSurface.left && fx < m_clearSurface.right && fy > m_clearSurface.top && fy < m_clearSurface.bottom) |
| return &m_clearSurface; |
| else |
| return NULL; |
| } |
| |
| void PaintPlugin::toggleInputMethod() { |
| m_isTouchCurrentInput = !m_isTouchCurrentInput; |
| |
| // lock only the input toggle and redraw the canvas |
| ANPCanvas* lockedCanvas = getCanvas(&m_inputToggle); |
| drawCleanPlugin(lockedCanvas); |
| } |
| |
| void PaintPlugin::togglePaintColor() { |
| if (m_activePaintColor == s_blueColor) |
| m_activePaintColor = s_redColor; |
| else if (m_activePaintColor == s_greenColor) |
| m_activePaintColor = s_blueColor; |
| else |
| m_activePaintColor = s_greenColor; |
| |
| // lock only the color toggle and redraw the canvas |
| ANPCanvas* lockedCanvas = getCanvas(&m_colorToggle); |
| drawCleanPlugin(lockedCanvas); |
| } |
| |
| void PaintPlugin::paintMouse(int x, int y) { |
| //TODO do not paint outside the drawing surface |
| |
| //create the paint color |
| ANPPaint* fillPaint = gPaintI.newPaint(); |
| gPaintI.setFlags(fillPaint, gPaintI.getFlags(fillPaint) | kAntiAlias_ANPPaintFlag); |
| gPaintI.setStyle(fillPaint, kFill_ANPPaintStyle); |
| gPaintI.setColor(fillPaint, m_activePaintColor); |
| |
| // handle the simple "mouse" paint (draw a point) |
| ANPRectF point; |
| point.left = (float) x-3; |
| point.top = (float) y-3; |
| point.right = (float) x+3; |
| point.bottom = (float) y+3; |
| |
| // get a canvas that is only locked around the point and draw it |
| ANPCanvas* canvas = getCanvas(&point); |
| gCanvasI.drawOval(canvas, &point, fillPaint); |
| |
| // clean up |
| releaseCanvas(canvas); |
| gPaintI.deletePaint(fillPaint); |
| } |
| |
| void PaintPlugin::paintTouch() { |
| //TODO do not paint outside the drawing surface |
| |
| //create the paint color |
| ANPPaint* strokePaint = gPaintI.newPaint(); |
| gPaintI.setFlags(strokePaint, gPaintI.getFlags(strokePaint) | kAntiAlias_ANPPaintFlag); |
| gPaintI.setColor(strokePaint, m_activePaintColor); |
| gPaintI.setStyle(strokePaint, kStroke_ANPPaintStyle); |
| gPaintI.setStrokeWidth(strokePaint, 6.0); |
| gPaintI.setStrokeCap(strokePaint, kRound_ANPPaintCap); |
| gPaintI.setStrokeJoin(strokePaint, kRound_ANPPaintJoin); |
| |
| // handle the complex "touch" paint (draw a line) |
| ANPRectF bounds; |
| gPathI.getBounds(m_touchPath, &bounds); |
| |
| // get a canvas that is only locked around the point and draw the path |
| ANPCanvas* canvas = getCanvas(&bounds); |
| gCanvasI.drawPath(canvas, m_touchPath, strokePaint); |
| |
| // clean up |
| releaseCanvas(canvas); |
| gPaintI.deletePaint(strokePaint); |
| } |