/** * Copyright (c) 2012-2013 Nokia Corporation. All rights reserved. * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. * Oracle and Java are trademarks or registered trademarks of Oracle and/or its * affiliates. Other product and company names mentioned herein may be trademarks * or trade names of their respective owners. * See LICENSE.TXT for license information. */ package com.nokia.example.paint.views; import com.nokia.example.paint.Main; import com.nokia.example.paint.helpers.DrawableLine; import com.nokia.example.paint.touch.MultiTouchListener; import com.nokia.example.paint.touch.MultiTouch; import com.nokia.example.paint.views.components.ColorChangeListener; import com.nokia.example.paint.views.components.ColorPicker; import com.nokia.example.paint.views.components.ColorTool; import com.nokia.example.paint.views.components.FileSaveDialog; import com.nokia.example.paint.views.components.Toolbar; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import javax.microedition.lcdui.game.GameCanvas; /* * Implements the draw area. */ public class DrawArea extends GameCanvas implements ColorChangeListener, MultiTouchListener { public static final int SMALL_BRUSH = 4; public static final int MEDIUM_BRUSH = 6; public static final int LARGE_BRUSH = 8; public static final int XL_BRUSH = 10; private final int TOUCH_POINTS; private Graphics g; private Timer mTimer; private int[] previousX; private int[] previousY; private int brushWidth = SMALL_BRUSH; private Image drawing; private Graphics dg; private Toolbar toolbar; private ColorPicker colorpicker; private FileSaveDialog savedialog; private boolean drawingAllowed = true; private boolean drawingChanged = false; private boolean renderDrawing = false; private Vector[] buffers; public DrawArea() { super(false); setFullScreenMode(true); int width = getWidth(); int height = getHeight(); drawing = Image.createImage(width, height - Toolbar.HEIGHT); dg = drawing.getGraphics(); toolbar = new Toolbar(width); ColorTool colortool = new ColorTool(); colortool.colorChanged(0x000000); // initial color toolbar.addTool(colortool, 1); colorpicker = new ColorPicker(width); colorpicker.addListener(this); colorpicker.addListener(colortool); savedialog = new FileSaveDialog(this); MultiTouch.setTouchListener(this); TOUCH_POINTS = MultiTouch.getMaxPointers(); buffers = new Vector[TOUCH_POINTS]; previousX = new int[TOUCH_POINTS]; previousY = new int[TOUCH_POINTS]; for (int i = 0; i < TOUCH_POINTS; i++) { buffers[i] = new Vector(); previousX[i] = -1; previousY[i] = -1; } Command backCommand = new Command("Back", Command.BACK, 0); addCommand(backCommand); setCommandListener(new CommandListener() { public void commandAction(Command c, Displayable d) { if (c.getCommandType() == Command.BACK) { Main main = Main.getInstance(); if (main.getDrawArea().isDrawingChanged()) { main.showExitPrompt(); } else { main.exit(); } } } }); } /** * Called when showing canvas. * @see javax.microedition.lcdui.Canvas#showNotify() */ protected void showNotify() { mTimer = new Timer(); g = getGraphics(); g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP); // Periodically update draw area graphics. TimerTask ui = new TimerTask() { public void run() { render(); flushGraphics(); } }; mTimer.schedule(ui, 0, 60); } /** * Called when hiding the canvas. * @see javax.microedition.lcdui.Canvas#hideNotify() */ protected void hideNotify() { mTimer.cancel(); } protected void render() { g.setClip(0, 0, getWidth(), getHeight()); toolbar.render(g); if (renderDrawing) { g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP); renderDrawing = false; } else { if (drawingAllowed) { boolean changed = false; for (int i = 0; i < TOUCH_POINTS; i++) { /* Push all the touch events to a buffer, which is * read every 30 ms. If touch events were read immediately, * events would be lost while drawing the line. */ Vector buffer = buffers[i]; if (!buffer.isEmpty()) { renderLines(buffer); changed = true; } } if (changed) { g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP); } } else { g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.LEFT | Graphics.TOP); } colorpicker.render(g); savedialog.render(g); } } /** * Line drawing is implemented by drawing overlapping filled circles along the drag path. * (The diameter of the circles is determined by the selected brush size.) * This is achieved by dividing the line between the drag events into small segments * with a length of 1/3 of the brush width. The 1/3 length presents a good trade-off for not * generating too much unnecessary drawing load while still producing a decent line. * @param buffer holding the latest touch events. */ private void renderLines(Vector buffer) { int l = buffer.size(); DrawableLine[] currentLines = new DrawableLine[buffer.size()]; for (int i = 0; i < l; i++) { currentLines[i] = (DrawableLine) buffer.elementAt(i); } int s = currentLines.length; float divider = brushWidth / 3; for (int j = 0; j < s; j++) { DrawableLine line = currentLines[j]; buffer.removeElement(line); int x1 = line.startX; int y1 = line.startY; int x2 = line.endX; int y2 = line.endY; // Draw a line between two drag events. if (x1 > 0 && y1 > 0 && x2 > 0 && y2 > 0) { float d = calcDistance(x1, y1, x2, y2); if (d > divider) { int steps = (int) (d / divider + 0.5); float dx = (x2 - x1) / (float) steps; float dy = (y2 - y1) / (float) steps; for (int i = 0; i < steps; i++) { int x = (int) (x1 + dx * i + 0.5); int y = (int) (y1 + dy * i + 0.5); dg.fillArc(x, y - Toolbar.HEIGHT, brushWidth, brushWidth, 0, 360); } } dg.fillArc((int) x2, (int) y2 - Toolbar.HEIGHT, brushWidth, brushWidth, 0, 360); } } } private float calcDistance(float x1, float y1, float x2, float y2) { return (float) (Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))); } /** * Handle pointer press events. * @see javax.microedition.lcdui.Canvas#pointerPressed(int, int) * @param x coordinate of press * @param y coordinate of press */ public void pointerPressed(int x, int y) { if (TOUCH_POINTS == 1) { // Prevent redundant calls pointerPressed(x, y, 0); } } /** * Handle pointer drag events. * @see javax.microedition.lcdui.Canvas#pointerDragged(int, int) * @param x coordinate * @param y coordinate */ public void pointerDragged(int x, int y) { if (TOUCH_POINTS == 1) { pointerDragged(x, y, 0); } } /** * Handle pointer release events. * @see javax.microedition.lcdui.Canvas#pointerReleased(int, int) * @param x coordinate * @param y coordinate */ public void pointerReleased(int x, int y) { if (TOUCH_POINTS == 1) { pointerReleased(x, y, 0); // Signifies there are unsaved changes; the exit dialog // will be called when user attempts to exit. drawingChanged = true; } } /** * Handle multitouch events, which are delivered through MultiTouch wrapper. * @see com.nokia.example.paint.touch.MultiTouchListener#pointerPressed(int, int, int) * @param x coordinate of press * @param y coordinate of press * @param id id of pointer */ public void pointerPressed(int x, int y, int id) { if (drawingAllowed) { if (y > Toolbar.HEIGHT) { // Add events to buffer buffers[id].addElement(new DrawableLine(x, y, x, y)); previousX[id] = x; previousY[id] = y; } else { toolbar.pointerPressed(x, y); } } else if (savedialog.isVisible()) { savedialog.pointerPressed(x, y); } } /** * Handle multitouch events, which are delivered through MultiTouch wrapper. * @see com.nokia.example.paint.touch.MultiTouchListener#pointerDragged(int, int, int) * @param x coordinate of drag * @param y coordinate of drag * @param id id of pointer */ public void pointerDragged(int x, int y, int id) { if (!drawingAllowed && colorpicker.isVisible()) { colorpicker.dragged(x, y); } else if (drawingAllowed && y > Toolbar.HEIGHT) { // Add events to buffer. buffers[id].addElement(new DrawableLine(previousX[id], previousY[id], x, y)); previousX[id] = x; previousY[id] = y; } } /** * Handle multitouch events, which are delivered through MultiTouch wrapper. * @see com.nokia.example.paint.touch.MultiTouchListener#pointerDragged(int, int, int) * @param x coordinate of release * @param y coordinate of release * @param id id of pointer */ public void pointerReleased(int x, int y, int id) { if (!drawingAllowed) { if (colorpicker.isVisible()) { colorpicker.pressed(x, y); } else if (savedialog.isVisible()) { savedialog.pointerUnpressed(x, y); } } else { previousX[id] = -1; previousY[id] = -1; toolbar.pointerUnpressed(x, y); drawingChanged = true; } } public Image getDrawing() { return drawing; } public void setBrush(final int brushsize) { brushWidth = brushsize; } public void showColorPicker() { drawingAllowed = false; colorpicker.show(); } public void hideColorPicker() { colorpicker.hide(); g.drawImage(drawing, 0, Toolbar.HEIGHT, Graphics.TOP | Graphics.LEFT); drawingAllowed = true; } public void colorChanged(int brushColor) { dg.setColor(brushColor); } public void clearDrawing() { int oldcolor = dg.getColor(); dg.setColor(0xffffff); dg.fillRect(0, 0, drawing.getWidth(), drawing.getHeight()); dg.setColor(oldcolor); renderDrawing = true; drawingChanged = false; } public void showSaveDialog(boolean quitAfterSave) { drawingAllowed = false; savedialog.show(quitAfterSave); } public void allowDrawing(boolean value) { if (value) { renderDrawing = true; } drawingAllowed = value; } public boolean isDrawingChanged() { return drawingChanged; } public void setDrawingChanged(boolean changed) { drawingChanged = changed; } }