1 /* 2 * Copyright 2009, The Android Open Source Project 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * * Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * * Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "PaintPlugin.h" 27 28 #include <fcntl.h> 29 #include <math.h> 30 #include <string.h> 31 32 extern NPNetscapeFuncs* browser; 33 extern ANPLogInterfaceV0 gLogI; 34 extern ANPCanvasInterfaceV0 gCanvasI; 35 extern ANPPaintInterfaceV0 gPaintI; 36 extern ANPPathInterfaceV0 gPathI; 37 extern ANPSurfaceInterfaceV0 gSurfaceI; 38 extern ANPSystemInterfaceV0 gSystemI; 39 extern ANPTypefaceInterfaceV0 gTypefaceI; 40 extern ANPWindowInterfaceV0 gWindowI; 41 42 /////////////////////////////////////////////////////////////////////////////// 43 44 PaintPlugin::PaintPlugin(NPP inst) : SurfaceSubPlugin(inst) { 45 46 m_isTouchActive = false; 47 m_isTouchCurrentInput = true; 48 m_activePaintColor = s_redColor; 49 50 memset(&m_drawingSurface, 0, sizeof(m_drawingSurface)); 51 memset(&m_inputToggle, 0, sizeof(m_inputToggle)); 52 memset(&m_colorToggle, 0, sizeof(m_colorToggle)); 53 memset(&m_fullScreenToggle, 0, sizeof(m_fullScreenToggle)); 54 memset(&m_clearSurface, 0, sizeof(m_clearSurface)); 55 56 // initialize the drawing surface 57 m_surface = NULL; 58 59 // initialize the path 60 m_touchPath = gPathI.newPath(); 61 if(!m_touchPath) 62 gLogI.log(kError_ANPLogType, "----%p Unable to create the touch path", inst); 63 64 // initialize the paint colors 65 m_paintSurface = gPaintI.newPaint(); 66 gPaintI.setFlags(m_paintSurface, gPaintI.getFlags(m_paintSurface) | kAntiAlias_ANPPaintFlag); 67 gPaintI.setColor(m_paintSurface, 0xFFC0C0C0); 68 gPaintI.setTextSize(m_paintSurface, 18); 69 70 m_paintButton = gPaintI.newPaint(); 71 gPaintI.setFlags(m_paintButton, gPaintI.getFlags(m_paintButton) | kAntiAlias_ANPPaintFlag); 72 gPaintI.setColor(m_paintButton, 0xFFA8A8A8); 73 74 // initialize the typeface (set the colors) 75 ANPTypeface* tf = gTypefaceI.createFromName("serif", kItalic_ANPTypefaceStyle); 76 gPaintI.setTypeface(m_paintSurface, tf); 77 gTypefaceI.unref(tf); 78 79 //register for touch events 80 ANPEventFlags flags = kTouch_ANPEventFlag; 81 NPError err = browser->setvalue(inst, kAcceptEvents_ANPSetValue, &flags); 82 if (err != NPERR_NO_ERROR) { 83 gLogI.log(kError_ANPLogType, "Error selecting input events."); 84 } 85 } 86 87 PaintPlugin::~PaintPlugin() { 88 gPathI.deletePath(m_touchPath); 89 gPaintI.deletePaint(m_paintSurface); 90 gPaintI.deletePaint(m_paintButton); 91 92 setContext(NULL); 93 destroySurface(); 94 } 95 96 ANPCanvas* PaintPlugin::getCanvas(ANPRectI* dirtyRect) { 97 98 ANPBitmap bitmap; 99 JNIEnv* env = NULL; 100 if (!m_surface || gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK || 101 !gSurfaceI.lock(env, m_surface, &bitmap, dirtyRect)) { 102 return NULL; 103 } 104 105 ANPCanvas* canvas = gCanvasI.newCanvas(&bitmap); 106 107 // clip the canvas to the dirty rect b/c the surface is only required to 108 // copy a minimum of the dirty rect and may copy more. The clipped canvas 109 // however will never write to pixels outside of the clipped area. 110 if (dirtyRect) { 111 ANPRectF clipR; 112 clipR.left = dirtyRect->left; 113 clipR.top = dirtyRect->top; 114 clipR.right = dirtyRect->right; 115 clipR.bottom = dirtyRect->bottom; 116 gCanvasI.clipRect(canvas, &clipR); 117 } 118 119 return canvas; 120 } 121 122 ANPCanvas* PaintPlugin::getCanvas(ANPRectF* dirtyRect) { 123 124 ANPRectI newRect; 125 newRect.left = (int) dirtyRect->left; 126 newRect.top = (int) dirtyRect->top; 127 newRect.right = (int) dirtyRect->right; 128 newRect.bottom = (int) dirtyRect->bottom; 129 130 return getCanvas(&newRect); 131 } 132 133 void PaintPlugin::releaseCanvas(ANPCanvas* canvas) { 134 JNIEnv* env = NULL; 135 if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) { 136 gSurfaceI.unlock(env, m_surface); 137 } 138 gCanvasI.deleteCanvas(canvas); 139 } 140 141 void PaintPlugin::drawCleanPlugin(ANPCanvas* canvas) { 142 NPP instance = this->inst(); 143 PluginObject *obj = (PluginObject*) instance->pdata; 144 145 // if no canvas get a locked canvas 146 if (!canvas) 147 canvas = getCanvas(); 148 149 if (!canvas) 150 return; 151 152 const float buttonWidth = 60; 153 const float buttonHeight = 30; 154 const int W = obj->window->width; 155 const int H = obj->window->height; 156 157 // color the plugin canvas 158 gCanvasI.drawColor(canvas, 0xFFCDCDCD); 159 160 // get font metrics 161 ANPFontMetrics fontMetrics; 162 gPaintI.getFontMetrics(m_paintSurface, &fontMetrics); 163 164 // draw the input toggle button 165 m_inputToggle.left = 5; 166 m_inputToggle.top = H - buttonHeight - 5; 167 m_inputToggle.right = m_inputToggle.left + buttonWidth; 168 m_inputToggle.bottom = m_inputToggle.top + buttonHeight; 169 gCanvasI.drawRect(canvas, &m_inputToggle, m_paintButton); 170 const char* inputText = m_isTouchCurrentInput ? "Touch" : "Mouse"; 171 gCanvasI.drawText(canvas, inputText, strlen(inputText), m_inputToggle.left + 5, 172 m_inputToggle.top - fontMetrics.fTop, m_paintSurface); 173 174 // draw the color selector button 175 m_colorToggle.left = (W/3) - (buttonWidth/2); 176 m_colorToggle.top = H - buttonHeight - 5; 177 m_colorToggle.right = m_colorToggle.left + buttonWidth; 178 m_colorToggle.bottom = m_colorToggle.top + buttonHeight; 179 gCanvasI.drawRect(canvas, &m_colorToggle, m_paintButton); 180 const char* colorText = getColorText(); 181 gCanvasI.drawText(canvas, colorText, strlen(colorText), m_colorToggle.left + 5, 182 m_colorToggle.top - fontMetrics.fTop, m_paintSurface); 183 184 // draw the full-screen toggle button 185 m_fullScreenToggle.left = ((W*2)/3) - (buttonWidth/2); 186 m_fullScreenToggle.top = H - buttonHeight - 5; 187 m_fullScreenToggle.right = m_fullScreenToggle.left + buttonWidth; 188 m_fullScreenToggle.bottom = m_fullScreenToggle.top + buttonHeight; 189 gCanvasI.drawRect(canvas, &m_fullScreenToggle, m_paintButton); 190 const char* fullScreenText = "Full"; 191 gCanvasI.drawText(canvas, fullScreenText, strlen(fullScreenText), 192 m_fullScreenToggle.left + 5, 193 m_fullScreenToggle.top - fontMetrics.fTop, m_paintSurface); 194 195 // draw the clear canvas button 196 m_clearSurface.left = W - buttonWidth - 5; 197 m_clearSurface.top = H - buttonHeight - 5; 198 m_clearSurface.right = m_clearSurface.left + buttonWidth; 199 m_clearSurface.bottom = m_clearSurface.top + buttonHeight; 200 gCanvasI.drawRect(canvas, &m_clearSurface, m_paintButton); 201 const char* clearText = "Clear"; 202 gCanvasI.drawText(canvas, clearText, strlen(clearText), m_clearSurface.left + 5, 203 m_clearSurface.top - fontMetrics.fTop, m_paintSurface); 204 205 // draw the drawing surface box (5 px from the edge) 206 m_drawingSurface.left = 5; 207 m_drawingSurface.top = 5; 208 m_drawingSurface.right = W - 5; 209 m_drawingSurface.bottom = m_colorToggle.top - 5; 210 gCanvasI.drawRect(canvas, &m_drawingSurface, m_paintSurface); 211 212 // release the canvas 213 releaseCanvas(canvas); 214 } 215 216 const char* PaintPlugin::getColorText() { 217 218 if (m_activePaintColor == s_blueColor) 219 return "Blue"; 220 else if (m_activePaintColor == s_greenColor) 221 return "Green"; 222 else 223 return "Red"; 224 } 225 226 jobject PaintPlugin::getSurface() { 227 if (m_surface) { 228 return m_surface; 229 } 230 231 // load the appropriate java class and instantiate it 232 JNIEnv* env = NULL; 233 if (gVM->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) { 234 gLogI.log(kError_ANPLogType, " ---- getSurface: failed to get env"); 235 return NULL; 236 } 237 238 const char* className = "com.android.sampleplugin.PaintSurface"; 239 jclass paintClass = gSystemI.loadJavaClass(inst(), className); 240 241 if(!paintClass) { 242 gLogI.log(kError_ANPLogType, " ---- getSurface: failed to load class"); 243 return NULL; 244 } 245 246 PluginObject *obj = (PluginObject*) inst()->pdata; 247 const int pW = obj->window->width; 248 const int pH = obj->window->height; 249 250 jmethodID constructor = env->GetMethodID(paintClass, "<init>", "(Landroid/content/Context;III)V"); 251 jobject paintSurface = env->NewObject(paintClass, constructor, m_context, (int)inst(), pW, pH); 252 253 if(!paintSurface) { 254 gLogI.log(kError_ANPLogType, " ---- getSurface: failed to construct object"); 255 return NULL; 256 } 257 258 m_surface = env->NewGlobalRef(paintSurface); 259 return m_surface; 260 } 261 262 void PaintPlugin::destroySurface() { 263 JNIEnv* env = NULL; 264 if (m_surface && gVM->GetEnv((void**) &env, JNI_VERSION_1_4) == JNI_OK) { 265 266 // detach the native code from the object 267 jclass javaClass = env->GetObjectClass(m_surface); 268 jmethodID invalMethod = env->GetMethodID(javaClass, "invalidateNPP", "()V"); 269 env->CallVoidMethod(m_surface, invalMethod); 270 271 env->DeleteGlobalRef(m_surface); 272 m_surface = NULL; 273 } 274 } 275 276 int16 PaintPlugin::handleEvent(const ANPEvent* evt) { 277 switch (evt->eventType) { 278 case kTouch_ANPEventType: { 279 float x = (float) evt->data.touch.x; 280 float y = (float) evt->data.touch.y; 281 if (kDown_ANPTouchAction == evt->data.touch.action && m_isTouchCurrentInput) { 282 283 ANPRectF* rect = validTouch(evt->data.touch.x, evt->data.touch.y); 284 if(rect == &m_drawingSurface) { 285 m_isTouchActive = true; 286 gPathI.moveTo(m_touchPath, x, y); 287 paintTouch(); 288 return 1; 289 } 290 291 } else if (kMove_ANPTouchAction == evt->data.touch.action && m_isTouchActive) { 292 gPathI.lineTo(m_touchPath, x, y); 293 paintTouch(); 294 return 1; 295 } else if (kUp_ANPTouchAction == evt->data.touch.action && m_isTouchActive) { 296 gPathI.lineTo(m_touchPath, x, y); 297 paintTouch(); 298 m_isTouchActive = false; 299 gPathI.reset(m_touchPath); 300 return 1; 301 } else if (kCancel_ANPTouchAction == evt->data.touch.action) { 302 m_isTouchActive = false; 303 gPathI.reset(m_touchPath); 304 return 1; 305 } else if (kDoubleTap_ANPTouchAction == evt->data.touch.action) { 306 gWindowI.requestCenterFitZoom(inst()); 307 return 1; 308 309 } 310 break; 311 } 312 case kMouse_ANPEventType: { 313 314 if (m_isTouchActive) 315 gLogI.log(kError_ANPLogType, "----%p Received unintended mouse event", inst()); 316 317 if (kDown_ANPMouseAction == evt->data.mouse.action) { 318 ANPRectF* rect = validTouch(evt->data.mouse.x, evt->data.mouse.y); 319 if (rect == &m_drawingSurface) 320 paintMouse(evt->data.mouse.x, evt->data.mouse.y); 321 else if (rect == &m_inputToggle) 322 toggleInputMethod(); 323 else if (rect == &m_colorToggle) 324 togglePaintColor(); 325 else if (rect == &m_fullScreenToggle) 326 gWindowI.requestFullScreen(inst()); 327 else if (rect == &m_clearSurface) 328 drawCleanPlugin(); 329 } 330 return 1; 331 } 332 case kCustom_ANPEventType: { 333 334 switch (evt->data.other[0]) { 335 case kSurfaceCreated_CustomEvent: 336 gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceCreated"); 337 /* The second draw call is added to cover up a problem in this 338 plugin and is not a recommended usage pattern. This plugin 339 does not correctly make partial updates to the double 340 buffered surface and this second call hides that problem. 341 */ 342 drawCleanPlugin(); 343 drawCleanPlugin(); 344 break; 345 case kSurfaceChanged_CustomEvent: { 346 gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceChanged"); 347 348 int width = evt->data.other[1]; 349 int height = evt->data.other[2]; 350 351 PluginObject *obj = (PluginObject*) inst()->pdata; 352 const int pW = obj->window->width; 353 const int pH = obj->window->height; 354 // compare to the plugin's surface dimensions 355 if (pW != width || pH != height) 356 gLogI.log(kError_ANPLogType, 357 "----%p Invalid Surface Dimensions (%d,%d):(%d,%d)", 358 inst(), pW, pH, width, height); 359 break; 360 } 361 case kSurfaceDestroyed_CustomEvent: 362 gLogI.log(kDebug_ANPLogType, " ---- customEvent: surfaceDestroyed"); 363 break; 364 } 365 break; // end KCustom_ANPEventType 366 } 367 default: 368 break; 369 } 370 return 0; // unknown or unhandled event 371 } 372 373 ANPRectF* PaintPlugin::validTouch(int x, int y) { 374 375 //convert to float 376 float fx = (int) x; 377 float fy = (int) y; 378 379 if (fx > m_drawingSurface.left && fx < m_drawingSurface.right && fy > m_drawingSurface.top && fy < m_drawingSurface.bottom) 380 return &m_drawingSurface; 381 else if (fx > m_inputToggle.left && fx < m_inputToggle.right && fy > m_inputToggle.top && fy < m_inputToggle.bottom) 382 return &m_inputToggle; 383 else if (fx > m_colorToggle.left && fx < m_colorToggle.right && fy > m_colorToggle.top && fy < m_colorToggle.bottom) 384 return &m_colorToggle; 385 else if (fx > m_fullScreenToggle.left && fx < m_fullScreenToggle.right && fy > m_fullScreenToggle.top && fy < m_fullScreenToggle.bottom) 386 return &m_fullScreenToggle; 387 else if (fx > m_clearSurface.left && fx < m_clearSurface.right && fy > m_clearSurface.top && fy < m_clearSurface.bottom) 388 return &m_clearSurface; 389 else 390 return NULL; 391 } 392 393 void PaintPlugin::toggleInputMethod() { 394 m_isTouchCurrentInput = !m_isTouchCurrentInput; 395 396 // lock only the input toggle and redraw the canvas 397 ANPCanvas* lockedCanvas = getCanvas(&m_inputToggle); 398 drawCleanPlugin(lockedCanvas); 399 } 400 401 void PaintPlugin::togglePaintColor() { 402 if (m_activePaintColor == s_blueColor) 403 m_activePaintColor = s_redColor; 404 else if (m_activePaintColor == s_greenColor) 405 m_activePaintColor = s_blueColor; 406 else 407 m_activePaintColor = s_greenColor; 408 409 // lock only the color toggle and redraw the canvas 410 ANPCanvas* lockedCanvas = getCanvas(&m_colorToggle); 411 drawCleanPlugin(lockedCanvas); 412 } 413 414 void PaintPlugin::paintMouse(int x, int y) { 415 //TODO do not paint outside the drawing surface 416 417 //create the paint color 418 ANPPaint* fillPaint = gPaintI.newPaint(); 419 gPaintI.setFlags(fillPaint, gPaintI.getFlags(fillPaint) | kAntiAlias_ANPPaintFlag); 420 gPaintI.setStyle(fillPaint, kFill_ANPPaintStyle); 421 gPaintI.setColor(fillPaint, m_activePaintColor); 422 423 // handle the simple "mouse" paint (draw a point) 424 ANPRectF point; 425 point.left = (float) x-3; 426 point.top = (float) y-3; 427 point.right = (float) x+3; 428 point.bottom = (float) y+3; 429 430 // get a canvas that is only locked around the point and draw it 431 ANPCanvas* canvas = getCanvas(&point); 432 gCanvasI.drawOval(canvas, &point, fillPaint); 433 434 // clean up 435 releaseCanvas(canvas); 436 gPaintI.deletePaint(fillPaint); 437 } 438 439 void PaintPlugin::paintTouch() { 440 //TODO do not paint outside the drawing surface 441 442 //create the paint color 443 ANPPaint* strokePaint = gPaintI.newPaint(); 444 gPaintI.setFlags(strokePaint, gPaintI.getFlags(strokePaint) | kAntiAlias_ANPPaintFlag); 445 gPaintI.setColor(strokePaint, m_activePaintColor); 446 gPaintI.setStyle(strokePaint, kStroke_ANPPaintStyle); 447 gPaintI.setStrokeWidth(strokePaint, 6.0); 448 gPaintI.setStrokeCap(strokePaint, kRound_ANPPaintCap); 449 gPaintI.setStrokeJoin(strokePaint, kRound_ANPPaintJoin); 450 451 // handle the complex "touch" paint (draw a line) 452 ANPRectF bounds; 453 gPathI.getBounds(m_touchPath, &bounds); 454 455 // get a canvas that is only locked around the point and draw the path 456 ANPCanvas* canvas = getCanvas(&bounds); 457 gCanvasI.drawPath(canvas, m_touchPath, strokePaint); 458 459 // clean up 460 releaseCanvas(canvas); 461 gPaintI.deletePaint(strokePaint); 462 } 463