You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1278 lines
43 KiB
1278 lines
43 KiB
package com.jotuntech.sketcher.client;
|
|
|
|
import java.awt.AlphaComposite;
|
|
import java.awt.BasicStroke;
|
|
import java.awt.Color;
|
|
import java.awt.Cursor;
|
|
import java.awt.Dimension;
|
|
import java.awt.Graphics;
|
|
import java.awt.Graphics2D;
|
|
import java.awt.Image;
|
|
import java.awt.Point;
|
|
import java.awt.Rectangle;
|
|
import java.awt.RenderingHints;
|
|
import java.awt.Shape;
|
|
import java.awt.Toolkit;
|
|
import java.awt.event.ActionEvent;
|
|
import java.awt.event.ActionListener;
|
|
import java.awt.event.MouseEvent;
|
|
import java.awt.event.MouseListener;
|
|
import java.awt.event.MouseMotionListener;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.awt.geom.Ellipse2D;
|
|
import java.awt.geom.Line2D;
|
|
import java.awt.geom.Point2D;
|
|
import java.awt.geom.Rectangle2D;
|
|
import java.awt.image.BufferedImage;
|
|
import java.awt.image.ImageObserver;
|
|
import java.net.URI;
|
|
import java.util.HashSet;
|
|
import java.util.IdentityHashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Set;
|
|
|
|
import javax.swing.AbstractAction;
|
|
import javax.swing.BoundedRangeModel;
|
|
import javax.swing.JComponent;
|
|
import javax.swing.JOptionPane;
|
|
import javax.swing.KeyStroke;
|
|
import javax.swing.Timer;
|
|
|
|
import cello.jtablet.TabletManager;
|
|
import cello.jtablet.event.TabletEvent;
|
|
import cello.jtablet.event.TabletListener;
|
|
import cello.jtablet.installer.JTabletExtension;
|
|
import cello.tablet.JTablet;
|
|
|
|
import com.jotuntech.sketcher.client.command.CursorCommand;
|
|
import com.jotuntech.sketcher.client.command.LineCommand;
|
|
import com.jotuntech.sketcher.client.command.MergeCommand;
|
|
import com.jotuntech.sketcher.client.command.MoveCommand;
|
|
import com.jotuntech.sketcher.client.command.SetColorCommand;
|
|
import com.jotuntech.sketcher.client.command.UndoCommand;
|
|
import com.jotuntech.sketcher.common.BitmapLayer;
|
|
import com.jotuntech.sketcher.common.Brush;
|
|
import com.jotuntech.sketcher.common.Canvas;
|
|
import com.jotuntech.sketcher.common.Input;
|
|
import com.jotuntech.sketcher.common.Layer;
|
|
import com.jotuntech.sketcher.common.Log;
|
|
import com.jotuntech.sketcher.common.User;
|
|
|
|
public class Editor extends JComponent implements MouseListener, MouseMotionListener, TabletListener {
|
|
private Client client;
|
|
private BufferedImage image;
|
|
private float zoom = 1;
|
|
private boolean smoothZoom = true;
|
|
private Dimension size;
|
|
private JTablet jTablet1;
|
|
private boolean jTablet2;
|
|
private Smoother smoother;
|
|
private Cursor currentNativeCursor, crosshair, blank, eyedropper, hand;
|
|
private Timer tagTimer, selectTimer, yieldTimer;
|
|
private Color canvasBackground = Color.WHITE;
|
|
private long lastCursorCommand;
|
|
private int dashPhase = 0;
|
|
private boolean tagsEnabled = true;
|
|
public enum State {
|
|
DISABLED,
|
|
DRAW_HOVER, DRAW_PRESS,
|
|
LINE_HOVER, LINE_PRESS,
|
|
RECT_HOVER, RECT_PRESS,
|
|
OVAL_HOVER, OVAL_PRESS,
|
|
BEZIER_HOVER, BEZIER_PRESS, BEZIER_HOVER2, BEZIER_PRESS_P2, BEZIER_PRESS_P3,
|
|
EYEDROP_PRESS,
|
|
SCROLL_HOVER, SCROLL_PRESS,
|
|
SELECT_HOVER, SELECT_PRESS, SELECT_INSIDE, SELECT_MOVE,
|
|
MOVE_HOVER, MOVE_INSIDE, MOVE_PRESS,
|
|
AD_HOVER, AD_PRESS;
|
|
};
|
|
private State _state, _oldState = State.DISABLED;
|
|
private int buttonDown = TabletEvent.NOBUTTON;
|
|
public enum CursorType {
|
|
NATIVE, SOFTWARE
|
|
};
|
|
private CursorType cursorType;
|
|
private boolean cursorVisible = true;
|
|
private boolean softwareCursorEnabled = false;
|
|
private BufferedImage ad;
|
|
private Point adPosition;
|
|
|
|
private int pressure = 0xFF;
|
|
|
|
private float scrollSpeedX, scrollSpeedY, originX, originY, slowLOSX, slowLOSY;
|
|
private Point los;
|
|
private Timer scrollTimer;
|
|
|
|
private float currentX, currentY;
|
|
private float x1, y1, x2, y2, x3, y3, x4, y4;
|
|
|
|
private int iOriginX, iOriginY;
|
|
|
|
/** Current select */
|
|
private Rectangle select;
|
|
|
|
public Editor(final Client client) {
|
|
super();
|
|
|
|
Toolkit tk = Toolkit.getDefaultToolkit();
|
|
crosshair = tk.createCustomCursor(tk.getImage(getClass().getResource("images/crosshair.gif")), new Point(16, 16), "crosshair");
|
|
blank = tk.createCustomCursor(new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB), new Point(0, 0), "blank");
|
|
eyedropper = tk.createCustomCursor(tk.getImage(getClass().getResource("images/eyedropper.gif")), new Point(16, 16), "eyedropper");
|
|
hand = tk.createCustomCursor(tk.getImage(getClass().getResource("images/hand.gif")), new Point(16, 16), "hand");
|
|
|
|
setState(State.DISABLED);
|
|
|
|
jTablet2 = false;
|
|
|
|
if(JTabletExtension.checkCompatibility(this, "1.2.0") && JTabletExtension.getInstallStatus("1.2.0").equals(JTabletExtension.InstallStatus.INSTALLED)) {
|
|
jTablet2 = true;
|
|
TabletManager.getDefaultManager().addTabletListener(this, this);
|
|
Log.info("JTablet 2.x installed.");
|
|
} else {
|
|
try {
|
|
jTablet1 = new JTablet();
|
|
Log.info("JTablet 0.9.x installed.");
|
|
} catch (Throwable t) {
|
|
Log.info("JTablet not installed.");
|
|
}
|
|
}
|
|
|
|
smoother = new Smoother();
|
|
|
|
if(!jTablet2) {
|
|
addMouseListener(this);
|
|
addMouseMotionListener(this);
|
|
}
|
|
|
|
this.client = client;
|
|
|
|
ActionListener tagListener = new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
if(client.getConnection() == null || client.getConnection().getUser() == null) {
|
|
return;
|
|
}
|
|
|
|
if(lastCursorCommand + 500 < System.currentTimeMillis()) {
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(smoother.getIndex(2))));
|
|
lastCursorCommand = System.currentTimeMillis();
|
|
}
|
|
|
|
if(!tagsEnabled) {
|
|
return;
|
|
}
|
|
|
|
for(User u : client.getUserArray()) {
|
|
if(u != client.getConnection().getUser()) {
|
|
Input c = u.getCursor();
|
|
|
|
int cursorX = (int) (c.x * zoom);
|
|
int cursorY = (int) (c.y * zoom);
|
|
|
|
Rectangle tag = u.getTag();
|
|
|
|
if(tag.x != cursorX || tag.y != cursorY) {
|
|
String name = u.getName();
|
|
Graphics2D g2 = (Graphics2D) getGraphics();
|
|
if(g2 == null) {
|
|
return;
|
|
}
|
|
Rectangle2D stringBounds = g2.getFontMetrics().getStringBounds(name, g2);
|
|
g2.dispose();
|
|
|
|
Rectangle newTag = new Rectangle(cursorX, cursorY, (int) stringBounds.getWidth() + 4, (int) stringBounds.getHeight() + 4);
|
|
u.setTag(newTag);
|
|
repaint(tag);
|
|
repaint(newTag);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
tagTimer = new Timer(1000, tagListener);
|
|
|
|
yieldTimer = new Timer(100, new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
client.getCanvas().setDrawing(false);
|
|
}
|
|
});
|
|
|
|
ActionListener selectListener = new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
Connection connection = client.getConnection();
|
|
if(connection == null) {
|
|
return;
|
|
}
|
|
User user = connection.getUser();
|
|
if(user == null) {
|
|
return;
|
|
}
|
|
if(select == null) {
|
|
return;
|
|
}
|
|
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
|
|
++dashPhase;
|
|
}
|
|
};
|
|
|
|
selectTimer = new Timer(250, selectListener);
|
|
selectTimer.setCoalesce(true);
|
|
selectTimer.start();
|
|
|
|
lastCursorCommand = System.currentTimeMillis();
|
|
|
|
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke("SPACE"), "drag begin");
|
|
getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke("released SPACE"), "drag end");
|
|
getActionMap().put("drag begin", new AbstractAction() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
if(buttonDown != TabletEvent.NOBUTTON || getState() == State.SCROLL_PRESS || getState() == State.SCROLL_HOVER) {
|
|
return;
|
|
}
|
|
if(storeState()) {
|
|
setState(State.SCROLL_HOVER);
|
|
}
|
|
}
|
|
});
|
|
getActionMap().put("drag end", new AbstractAction() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
if(getState() == State.SCROLL_PRESS || getState() == State.SCROLL_HOVER) {
|
|
restoreState();
|
|
}
|
|
}
|
|
});
|
|
|
|
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ctrl Z"), "undo");
|
|
getActionMap().put("undo", new AbstractAction() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
if(buttonDown == TabletEvent.NOBUTTON) {
|
|
client.getCommandQueue().offer(new CommandEntry(0, new UndoCommand()));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
protected void adjust() {
|
|
if(image == null) {
|
|
return;
|
|
}
|
|
|
|
size = new Dimension((int)(image.getWidth() * zoom), (int)(image.getHeight() * zoom));
|
|
setPreferredSize(size);
|
|
setMaximumSize(size);
|
|
|
|
invalidate();
|
|
getParent().validate();
|
|
repaint();
|
|
}
|
|
|
|
public float getZoom() {
|
|
return zoom;
|
|
}
|
|
|
|
public void setZoom(float zoom) {
|
|
this.zoom = zoom;
|
|
adjust();
|
|
}
|
|
|
|
public void setState(State state) {
|
|
if(state == this._state) {
|
|
return;
|
|
}
|
|
if(state != State.MOVE_HOVER && state != State.MOVE_INSIDE && state != State.MOVE_PRESS && (this._state == State.MOVE_HOVER || this._state == State.MOVE_INSIDE)) {
|
|
// TODO: Fix this dirty hack somehow. We must do this here, or the Move tool leaves our phantom layer dirty.
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
select = null;
|
|
repaint();
|
|
System.err.println("Move state hack!");
|
|
}
|
|
switch(state) {
|
|
case DISABLED:
|
|
setNativeCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case DRAW_HOVER:
|
|
case DRAW_PRESS:
|
|
setCursorType(CursorType.SOFTWARE);
|
|
break;
|
|
case LINE_HOVER:
|
|
case RECT_HOVER:
|
|
case OVAL_HOVER:
|
|
case BEZIER_HOVER:
|
|
case BEZIER_HOVER2:
|
|
setNativeCursor(crosshair);
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case EYEDROP_PRESS:
|
|
setNativeCursor(eyedropper);
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case AD_HOVER:
|
|
setNativeCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); // Don't set to 'hand', this is actually a finger cursor
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case SCROLL_HOVER:
|
|
setNativeCursor(hand);
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case SELECT_HOVER:
|
|
setNativeCursor(crosshair);
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case SELECT_INSIDE:
|
|
setNativeCursor(hand);
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case MOVE_HOVER:
|
|
setNativeCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
case MOVE_INSIDE:
|
|
setNativeCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
|
|
setCursorType(CursorType.NATIVE);
|
|
break;
|
|
}
|
|
this._state = state;
|
|
System.err.println("Set state: " + state);
|
|
|
|
}
|
|
|
|
public State getState() {
|
|
return _state;
|
|
}
|
|
|
|
private boolean storeState() {
|
|
if(_oldState != State.DISABLED) {
|
|
return false;
|
|
}
|
|
_oldState = getState();
|
|
System.err.println("Stored state, stack: " + _oldState);
|
|
return true;
|
|
}
|
|
|
|
private void restoreState() {
|
|
if(_oldState == State.DISABLED) {
|
|
throw new RuntimeException("Stack is empty.");
|
|
}
|
|
setState(_oldState);
|
|
_oldState = State.DISABLED;
|
|
System.err.println("Restored state, stack: " + _oldState);
|
|
}
|
|
|
|
private void setNativeCursor(Cursor cursor) {
|
|
Cursor actualCursor = cursorVisible ? cursor : blank;
|
|
if(cursorType == CursorType.NATIVE && actualCursor != getCursor()) {
|
|
setCursor(actualCursor);
|
|
System.err.println("Changed native cursor (setNativeCursor): " + getCursor());
|
|
}
|
|
currentNativeCursor = cursor;
|
|
}
|
|
|
|
private void setCursorVisible(boolean cursorVisible) {
|
|
if(cursorVisible == this.cursorVisible) {
|
|
return;
|
|
}
|
|
this.cursorVisible = cursorVisible;
|
|
if(cursorType == CursorType.SOFTWARE || !cursorVisible) {
|
|
if(getCursor() != blank) {
|
|
setCursor(blank);
|
|
System.err.println("Changed native cursor: " + getCursor());
|
|
}
|
|
repaint();
|
|
} else if(currentNativeCursor != getCursor()) {
|
|
setCursor(currentNativeCursor);
|
|
}
|
|
}
|
|
|
|
public void setCursorType(CursorType cursorType) {
|
|
switch(cursorType) {
|
|
case NATIVE:
|
|
this.setCursor(cursorVisible ? currentNativeCursor : blank);
|
|
this.cursorType = CursorType.NATIVE;
|
|
System.err.println("Changed native cursor: " + getCursor());
|
|
repaint();
|
|
break;
|
|
case SOFTWARE:
|
|
if(softwareCursorEnabled) {
|
|
setCursor(blank);
|
|
this.cursorType = CursorType.SOFTWARE;
|
|
System.err.println("Changed native cursor: " + getCursor());
|
|
} else {
|
|
currentNativeCursor = crosshair;
|
|
this.setCursor(cursorVisible ? currentNativeCursor : blank);
|
|
this.cursorType = CursorType.NATIVE;
|
|
System.err.println("Changed native cursor: " + getCursor());
|
|
}
|
|
repaint();
|
|
break;
|
|
}
|
|
}
|
|
|
|
private float bezier(float t, float p0, float p1, float p2, float p3) {
|
|
return (float) (Math.pow(1 - t, 3) * p0 + 3 * Math.pow(1 - t, 2) * t * p1 + 3 * (1 - t) * Math.pow(t, 2) * p2 + Math.pow(t, 3) * p3);
|
|
}
|
|
|
|
public void paint(Graphics g) {
|
|
if(image == null) {
|
|
return;
|
|
}
|
|
|
|
// Ensure proper clip area since JScrollView is an asshole
|
|
Rectangle clip = g.getClipBounds();
|
|
Rectangle validRect = new Rectangle(new Point(0, 0), size);
|
|
if(clip == null) {
|
|
clip = validRect;
|
|
} else {
|
|
clip = clip.intersection(validRect);
|
|
}
|
|
g.setClip(clip);
|
|
|
|
Graphics2D g2 = (Graphics2D)g;
|
|
|
|
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
|
|
|
|
if(smoothZoom && zoom != Math.floor(zoom)) {
|
|
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
|
}
|
|
|
|
int zoomWidth = (int) (image.getWidth() * zoom);
|
|
int zoomHeight = (int) (image.getHeight() * zoom);
|
|
|
|
synchronized(image) {
|
|
g2.setColor(canvasBackground);
|
|
g2.fillRect(clip.x, clip.y, Math.min(zoomWidth, clip.width), Math.max(zoomHeight, clip.height));
|
|
|
|
int sourceX = Math.max(0, (int) Math.floor(clip.x / zoom));
|
|
float destinationX = sourceX * zoom;
|
|
|
|
int sourceY = Math.max(0, (int) Math.floor(clip.y / zoom));
|
|
float destinationY = sourceY * zoom;
|
|
|
|
int sourceWidth = Math.min(image.getWidth() - sourceX, (int) Math.ceil(clip.width / zoom) + 1);
|
|
int sourceHeight = Math.min(image.getHeight() - sourceY, (int) Math.ceil(clip.height / zoom) + 1);
|
|
|
|
if(sourceWidth > 0 && sourceHeight > 0) {
|
|
BufferedImage subImage = image.getSubimage(sourceX, sourceY, sourceWidth, sourceHeight);
|
|
AffineTransform xform = new AffineTransform();
|
|
xform.scale(zoom, zoom);
|
|
AffineTransform oldXform = g2.getTransform();
|
|
g2.translate(destinationX, destinationY);
|
|
g2.drawImage(subImage, xform, this);
|
|
g2.setTransform(oldXform);
|
|
}
|
|
}
|
|
|
|
for(User u : client.getUserArray()) {
|
|
if(u != client.getConnection().getUser()) {
|
|
Rectangle tag = u.getTag();
|
|
String name = u.getName();
|
|
Rectangle2D stringBounds = g2.getFontMetrics().getStringBounds(name, g2);
|
|
g2.setColor(Color.YELLOW);
|
|
g2.fillRect(tag.x, tag.y, tag.width, tag.height);
|
|
g2.setColor(Color.BLACK);
|
|
g2.drawRect(tag.x, tag.y, tag.width - 1, tag.height - 1);
|
|
g2.drawString(name, tag.x + 2, tag.y + (int) stringBounds.getHeight());
|
|
}
|
|
}
|
|
|
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
|
|
|
|
User user = client.getConnection().getUser();
|
|
|
|
if(cursorVisible && cursorType == CursorType.SOFTWARE) {
|
|
Brush brush = user.getBrush();
|
|
float hardAdjust = 0.5f + ((brush.getHardness() - 1f) / 12f);
|
|
Input input = smoother.getIndex(2);
|
|
float finalRadius = brush.isPressureToRadius() && input.pressure > 0 ? (brush.getRadius() * input.pressure * hardAdjust * zoom) / 255f : brush.getRadius() * hardAdjust * zoom;
|
|
float finalX = input.x * zoom;
|
|
float finalY = input.y * zoom;
|
|
Shape s = new Ellipse2D.Float(finalX - finalRadius, finalY - finalRadius, finalRadius * 2, finalRadius * 2);
|
|
g2.setColor(Color.BLACK);
|
|
g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] { 2, 2 }, 0));
|
|
g2.draw(s);
|
|
g2.setColor(Color.WHITE);
|
|
g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] { 2, 2 }, 2));
|
|
g2.draw(s);
|
|
}
|
|
|
|
g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10));
|
|
g2.setColor(Color.BLACK);
|
|
|
|
State myState = getState();
|
|
|
|
if(myState == State.LINE_PRESS || myState == State.RECT_PRESS || myState == State.OVAL_PRESS) {
|
|
switch(myState) {
|
|
case LINE_PRESS:
|
|
g2.draw(new Line2D.Float(new Point2D.Float(originX * zoom, originY * zoom), new Point2D.Float(currentX * zoom, currentY * zoom)));
|
|
break;
|
|
case RECT_PRESS:
|
|
g2.draw(new Rectangle2D.Float(originX * zoom, originY * zoom, currentX * zoom - originX * zoom, currentY * zoom - originY * zoom));
|
|
break;
|
|
case OVAL_PRESS:
|
|
float radiusX = Math.abs(currentX * zoom - originX * zoom);
|
|
float radiusY = Math.abs(currentY * zoom - originY * zoom);
|
|
float minX = originX * zoom - radiusX;
|
|
float minY = originY * zoom - radiusY;
|
|
g2.draw(new Ellipse2D.Float(minX, minY, radiusX * 2, radiusY * 2));
|
|
break;
|
|
}
|
|
} else if(myState == State.BEZIER_PRESS || myState == State.BEZIER_HOVER2 || myState == State.BEZIER_PRESS_P2 || myState == State.BEZIER_PRESS_P3) {
|
|
float t = 0, lx = x1 * zoom, ly = y1 * zoom;
|
|
for(int i = 0; i <= 64; i++, t += 1f / 64f) {
|
|
float x = bezier(t, x1 * zoom, x2 * zoom, x3 * zoom, x4 * zoom);
|
|
float y = bezier(t, y1 * zoom, y2 * zoom, y3 * zoom, y4 * zoom);
|
|
g2.draw(new Line2D.Float(lx, ly, x, y));
|
|
lx = x;
|
|
ly = y;
|
|
}
|
|
g2.setColor(Color.GRAY);
|
|
g2.draw(new Line2D.Float(x1 * zoom, y1 * zoom, x2 * zoom, y2 * zoom));
|
|
g2.draw(new Line2D.Float(x4 * zoom, y4 * zoom, x3 * zoom, y3 * zoom));
|
|
g2.draw(new Rectangle2D.Float(x2 * zoom - 3, y2 * zoom - 3, 6, 6));
|
|
g2.draw(new Rectangle2D.Float(x3 * zoom - 3, y3 * zoom - 3, 6, 6));
|
|
}
|
|
|
|
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
|
|
g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT);
|
|
|
|
if(ad != null) {
|
|
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.8f));
|
|
g2.drawImage(ad, adPosition.x, adPosition.y, null);
|
|
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f));
|
|
}
|
|
|
|
if(select != null) {
|
|
g2.setColor(Color.BLACK);
|
|
g2.setXORMode(Color.WHITE);
|
|
g2.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10, new float[] { 4, 4 }, dashPhase));
|
|
g2.drawRect(Math.round(select.x * zoom), Math.round(select.y * zoom), Math.round(select.width * zoom), Math.round(select.height * zoom));
|
|
}
|
|
}
|
|
|
|
public boolean imageUpdate(Image img, int infoflags, int x, int y, int width, int height) {
|
|
if(img == null) {
|
|
if(infoflags == ImageObserver.ALLBITS) {
|
|
x = 0;
|
|
y = 0;
|
|
width = client.getCanvas().getWidth();
|
|
height = client.getCanvas().getHeight();
|
|
}
|
|
|
|
synchronized(image) {
|
|
Graphics2D g2 = (Graphics2D)image.getGraphics();
|
|
g2.setClip(x, y, width, height);
|
|
|
|
AlphaComposite ac = AlphaComposite.getInstance(AlphaComposite.DST_OUT, 1);
|
|
g2.setComposite(ac);
|
|
|
|
g2.fillRect(x, y, width, height);
|
|
|
|
/** Create a map of layers, showing which phantom layers they have */
|
|
IdentityHashMap<Layer, Set<Layer>> selectedLayers = new IdentityHashMap<Layer, Set<Layer>>();
|
|
for(User u : client.getUserMap().values()) {
|
|
Layer l = u.getLayer();
|
|
|
|
if(l == null) {
|
|
continue;
|
|
}
|
|
|
|
Set<Layer> us = selectedLayers.get(l);
|
|
|
|
if(us == null) {
|
|
us = new HashSet<Layer>();
|
|
selectedLayers.put(l, us);
|
|
}
|
|
|
|
us.add(u.getPhantomLayer());
|
|
}
|
|
|
|
for(Layer l : client.getCanvas().getLayerMap().values()) {
|
|
Set<Layer> ls = selectedLayers.get(l);
|
|
if(l instanceof BitmapLayer && ls != null) {
|
|
((BitmapLayer) l).draw(g2, selectedLayers.get(l));
|
|
} else {
|
|
l.draw(g2);
|
|
}
|
|
}
|
|
|
|
g2.dispose();
|
|
}
|
|
|
|
repaint((int) Math.floor(x * zoom - zoom), (int) Math.floor(y * zoom - zoom), (int) Math.ceil(width * zoom + zoom), (int) Math.ceil(height * zoom + zoom));
|
|
|
|
return true;
|
|
} else {
|
|
return super.imageUpdate(img, infoflags, x, y, width, height);
|
|
}
|
|
}
|
|
|
|
public void mouseDragged(MouseEvent e) {
|
|
cursorDragged(new TabletEvent(e, null, null));
|
|
}
|
|
|
|
public void mouseMoved(MouseEvent e) {
|
|
cursorMoved(new TabletEvent(e, null, null));
|
|
}
|
|
|
|
public void mouseClicked(MouseEvent e) {
|
|
|
|
}
|
|
|
|
public void mousePressed(MouseEvent e) {
|
|
cursorPressed(new TabletEvent(e, null, null));
|
|
}
|
|
|
|
public void mouseReleased(MouseEvent e) {
|
|
cursorReleased(new TabletEvent(e, null, null));
|
|
}
|
|
|
|
public void mouseEntered(MouseEvent e) {
|
|
cursorEntered(new TabletEvent(e, null, null));
|
|
}
|
|
|
|
public void mouseExited(MouseEvent e) {
|
|
cursorExited(new TabletEvent(e, null, null));
|
|
}
|
|
|
|
void eyedrop(MouseEvent e) {
|
|
if(client.getConnection() == null || client.getConnection().getUser() == null || client.getConnection().getUser().getLayer() == null) {
|
|
return;
|
|
}
|
|
|
|
Input input = new Input(e.getX() / zoom, e.getY() / zoom, 0);
|
|
int color = client.getConnection().getUser().getLayer().getColor(input);
|
|
client.getCommandQueue().add(new CommandEntry(0, new SetColorCommand(color)));
|
|
client.getUserInterface().setColorSliders(color);
|
|
}
|
|
|
|
public void setTagTimer(Timer tagTimer) {
|
|
this.tagTimer = tagTimer;
|
|
}
|
|
|
|
public Timer getTagTimer() {
|
|
return tagTimer;
|
|
}
|
|
|
|
public void setCanvas(Canvas canvas) {
|
|
image = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
|
image.setAccelerationPriority(1);
|
|
|
|
canvas.addImageObserver(this);
|
|
adjust();
|
|
tagTimer.start();
|
|
yieldTimer.start();
|
|
}
|
|
|
|
public void setCanvasBackground(Color canvasBackground) {
|
|
this.canvasBackground = canvasBackground;
|
|
}
|
|
|
|
public Color getCanvasBackground() {
|
|
return canvasBackground;
|
|
}
|
|
|
|
public boolean hasJTablet() {
|
|
return (jTablet1 != null) || jTablet2;
|
|
}
|
|
|
|
void paintMouseCursor() {
|
|
if(cursorVisible && cursorType == CursorType.SOFTWARE) {
|
|
Brush brush = client.getConnection().getUser().getBrush();
|
|
float hardAdjust = 0.5f + ((brush.getHardness() - 1f) / 12f);
|
|
Input oldInput = smoother.getIndex(1);
|
|
float oldRadius = brush.isPressureToRadius() && oldInput.pressure > 0 ? (brush.getRadius() * oldInput.pressure * hardAdjust * zoom) / 255f : brush.getRadius() * hardAdjust * zoom;
|
|
float oldX = oldInput.x * zoom;
|
|
float oldY = oldInput.y * zoom;
|
|
repaint((int) Math.floor(oldX - oldRadius) - 2, (int) Math.floor(oldY - oldRadius) - 2, (int) Math.ceil(oldRadius * 2) + 4, (int) Math.ceil(oldRadius * 2) + 4);
|
|
Input input = smoother.getIndex(2);
|
|
float radius = brush.isPressureToRadius() && input.pressure > 0 ? (brush.getRadius() * input.pressure * hardAdjust * zoom) / 255f : brush.getRadius() * hardAdjust * zoom;
|
|
float x = input.x * zoom;
|
|
float y = input.y * zoom;
|
|
paintImmediately((int) Math.floor(x - oldRadius) - 2, (int) Math.floor(y - oldRadius) - 2, (int) Math.ceil(radius * 2) + 4, (int) Math.ceil(radius * 2) + 4);
|
|
}
|
|
}
|
|
|
|
private void yieldForPaint() {
|
|
if(client.getCanvas() != null) {
|
|
client.getCanvas().setDrawing(true);
|
|
yieldTimer.restart();
|
|
}
|
|
}
|
|
|
|
public void cursorEntered(TabletEvent e) {
|
|
setCursorVisible(true);
|
|
}
|
|
|
|
public void cursorExited(TabletEvent e) {
|
|
setCursorVisible(false);
|
|
}
|
|
|
|
public void cursorGestured(TabletEvent e) {
|
|
|
|
}
|
|
|
|
public void cursorMoved(TabletEvent e) {
|
|
if(client.getConnection() == null || client.getConnection().getUser() == null) {
|
|
return;
|
|
}
|
|
|
|
Input input = new Input(e.getFloatX() / zoom, e.getFloatY() / zoom, 0);
|
|
smoother.add(input);
|
|
paintMouseCursor();
|
|
|
|
if(lastCursorCommand + 500 < System.currentTimeMillis()) {
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(smoother.getIndex(2))));
|
|
lastCursorCommand = System.currentTimeMillis();
|
|
}
|
|
|
|
State myState = getState();
|
|
|
|
boolean inSelect = select != null && select.contains(e.getX() / zoom, e.getY() / zoom);
|
|
boolean inAd = ad != null && e.getX() >= adPosition.x && e.getY() >= adPosition.y && e.getX() < adPosition.x + ad.getWidth() && e.getY() < adPosition.y + ad.getHeight();
|
|
|
|
switch(myState) {
|
|
case SCROLL_HOVER:
|
|
case SCROLL_PRESS:
|
|
break;
|
|
case SELECT_HOVER:
|
|
if(inSelect) {
|
|
setState(State.SELECT_INSIDE);
|
|
}
|
|
break;
|
|
case SELECT_INSIDE:
|
|
if(!inSelect) {
|
|
setState(State.SELECT_HOVER);
|
|
}
|
|
break;
|
|
case MOVE_HOVER:
|
|
if(inSelect) {
|
|
setState(State.MOVE_INSIDE);
|
|
}
|
|
break;
|
|
case MOVE_INSIDE:
|
|
if(!inSelect) {
|
|
setState(State.MOVE_HOVER);
|
|
}
|
|
break;
|
|
case AD_HOVER:
|
|
if(!inAd) {
|
|
restoreState();
|
|
}
|
|
break;
|
|
default:
|
|
if(inAd && storeState()) {
|
|
setState(State.AD_HOVER);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void cursorPressed(TabletEvent e) {
|
|
if(buttonDown != TabletEvent.NOBUTTON) {
|
|
return;
|
|
}
|
|
buttonDown = e.getButton();
|
|
yieldForPaint();
|
|
requestFocusInWindow();
|
|
User user = client.getConnection().getUser();
|
|
State myState = getState();
|
|
int button = e.getButton();
|
|
switch(button) {
|
|
case MouseEvent.BUTTON1:
|
|
switch(myState) {
|
|
case DISABLED:
|
|
return;
|
|
case DRAW_HOVER:
|
|
if(user != null && user.isViewer()) {
|
|
client.getUserInterface().println("Drawing is not permitted in viewer mode. Please sign in to participate.");
|
|
break;
|
|
}
|
|
|
|
setState(State.DRAW_PRESS);
|
|
|
|
if(jTablet2) {
|
|
pressure = Math.round(e.getPressure() * 255f);
|
|
} else if(jTablet1 != null) {
|
|
try {
|
|
if(jTablet1.poll() && jTablet1.hasCursor()) {
|
|
pressure = (jTablet1.getPressure() * 255) / jTablet1.getPressureExtent();
|
|
}
|
|
} catch(Throwable jte) { }
|
|
}
|
|
Input input = new Input(e.getFloatX() / zoom, e.getFloatY() / zoom, pressure);
|
|
smoother.add(input);
|
|
smoother.setPressure(pressure);
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(smoother.getIndex(2))));
|
|
break;
|
|
case LINE_HOVER:
|
|
case RECT_HOVER:
|
|
case OVAL_HOVER:
|
|
originX = e.getFloatX() / zoom;
|
|
originY = e.getFloatY() / zoom;
|
|
currentX = e.getFloatX() / zoom;
|
|
currentY = e.getFloatY() / zoom;
|
|
switch(myState) {
|
|
case LINE_HOVER:
|
|
setState(State.LINE_PRESS);
|
|
break;
|
|
case RECT_HOVER:
|
|
setState(State.RECT_PRESS);
|
|
break;
|
|
case OVAL_HOVER:
|
|
setState(State.OVAL_PRESS);
|
|
break;
|
|
}
|
|
break;
|
|
case BEZIER_HOVER:
|
|
x4 = x3 = x2 = x1 = e.getFloatX() / zoom;
|
|
y4 = y3 = y2 = y1 = e.getFloatY() / zoom;
|
|
setState(State.BEZIER_PRESS);
|
|
break;
|
|
case BEZIER_HOVER2:
|
|
float x = e.getFloatX() / zoom;
|
|
float y = e.getFloatY() / zoom;
|
|
float pad = 3 / zoom;
|
|
if(x >= x2 - pad && x <= x2 + pad && y >= y2 - pad && y <= y2 + pad) {
|
|
setState(State.BEZIER_PRESS_P2);
|
|
} else if(x >= x3 - pad && x <= x3 + pad && y >= y3 - pad && y <= y3 + pad) {
|
|
setState(State.BEZIER_PRESS_P3);
|
|
}
|
|
break;
|
|
case SELECT_INSIDE:
|
|
setState(State.SELECT_MOVE);
|
|
originX = e.getX() / zoom;
|
|
originY = e.getY() / zoom;
|
|
break;
|
|
case SELECT_HOVER:
|
|
setState(State.SELECT_PRESS);
|
|
if(select != null) {
|
|
Rectangle r = new Rectangle(select);
|
|
r.grow(1, 1);
|
|
repaint(Math.round(r.x * zoom), Math.round(r.y * zoom), Math.round(r.width * zoom), Math.round(r.height * zoom));
|
|
}
|
|
select = new Rectangle(e.getX(), e.getY(), 0, 0);
|
|
iOriginX = Math.round(e.getX() / zoom);
|
|
iOriginY = Math.round(e.getY() / zoom);
|
|
break;
|
|
case MOVE_INSIDE:
|
|
if(user == null || user.isViewer()) {
|
|
client.getUserInterface().println("Moving is not permitted in viewer mode. Please sign in to participate.");
|
|
break;
|
|
}
|
|
if(user.getLayer() == null) {
|
|
break;
|
|
}
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MoveCommand(select.x, select.y, select.x, select.y, select.width, select.height)));
|
|
setState(State.MOVE_PRESS);
|
|
originX = e.getX() / zoom;
|
|
originY = e.getY() / zoom;
|
|
break;
|
|
case SCROLL_HOVER:
|
|
los = e.getLocationOnScreen();
|
|
originX = los.x;
|
|
originY = los.y;
|
|
slowLOSX = los.x;
|
|
slowLOSY = los.y;
|
|
scrollSpeedX = 0;
|
|
scrollSpeedY = 0;
|
|
setState(State.SCROLL_PRESS);
|
|
BoundedRangeModel hm = client.getUserInterface().getEditorPane().getHorizontalScrollBar().getModel();
|
|
BoundedRangeModel vm = client.getUserInterface().getEditorPane().getVerticalScrollBar().getModel();
|
|
hm.setValueIsAdjusting(true);
|
|
vm.setValueIsAdjusting(true);
|
|
|
|
if(scrollTimer != null) {
|
|
scrollTimer.stop();
|
|
scrollTimer = null;
|
|
}
|
|
|
|
scrollTimer = new Timer(20, new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
BoundedRangeModel hm = client.getUserInterface().getEditorPane().getHorizontalScrollBar().getModel();
|
|
BoundedRangeModel vm = client.getUserInterface().getEditorPane().getVerticalScrollBar().getModel();
|
|
if(getState() == State.SCROLL_PRESS) {
|
|
slowLOSX += (los.x - slowLOSX) * 0.125f;
|
|
slowLOSY += (los.y - slowLOSY) * 0.125f;
|
|
scrollSpeedX = -(slowLOSX - originX) - Math.signum(slowLOSX - originX) * (slowLOSX - originX) * (slowLOSX - originX) / 12f;
|
|
scrollSpeedY = -(slowLOSY - originY) - Math.signum(slowLOSY - originY) * (slowLOSY - originY) * (slowLOSY - originY) / 12f;
|
|
originX = slowLOSX;
|
|
originY = slowLOSY;
|
|
|
|
hm.setValue(hm.getValue() + Math.round(scrollSpeedX));
|
|
vm.setValue(vm.getValue() + Math.round(scrollSpeedY));
|
|
} else {
|
|
hm.setValue(hm.getValue() + Math.round(scrollSpeedX));
|
|
vm.setValue(vm.getValue() + Math.round(scrollSpeedY));
|
|
scrollSpeedX *= 0.95f;
|
|
scrollSpeedY *= 0.95f;
|
|
if(Math.abs(scrollSpeedX) < 0.5f && Math.abs(scrollSpeedY) < 0.5f) {
|
|
client.getUserInterface().getEditorPane().getHorizontalScrollBar().getModel().setValueIsAdjusting(false);
|
|
client.getUserInterface().getEditorPane().getVerticalScrollBar().getModel().setValueIsAdjusting(false);
|
|
scrollTimer.stop();
|
|
scrollTimer = null;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
scrollTimer.setCoalesce(false);
|
|
scrollTimer.start();
|
|
break;
|
|
case AD_HOVER:
|
|
setState(State.AD_PRESS);
|
|
break;
|
|
}
|
|
break;
|
|
case MouseEvent.BUTTON3:
|
|
if(storeState()) {
|
|
setState(State.EYEDROP_PRESS);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void cursorDragged(TabletEvent e) {
|
|
if(client.getConnection() == null || client.getConnection().getUser() == null || client.getConnection().getUser().getLayer() == null) {
|
|
return;
|
|
}
|
|
|
|
if(jTablet2) {
|
|
pressure = Math.round(e.getPressure() * 255f);
|
|
} else if(jTablet1 != null) {
|
|
try {
|
|
if(jTablet1.poll() && jTablet1.hasCursor()) {
|
|
pressure = (jTablet1.getPressure() * 255) / jTablet1.getPressureExtent();
|
|
}
|
|
} catch(Throwable jte) { }
|
|
}
|
|
|
|
Input input = new Input(e.getFloatX() / zoom, e.getFloatY() / zoom, pressure);
|
|
smoother.add(input);
|
|
paintMouseCursor();
|
|
|
|
State myState = getState();
|
|
|
|
switch(myState) {
|
|
case DRAW_PRESS:
|
|
yieldForPaint();
|
|
if(pressure > 0) {
|
|
List<Input> inputList = smoother.get();
|
|
for(Iterator<Input> i = inputList.iterator(); i.hasNext();) {
|
|
Input in = i.next();
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(in)));
|
|
}
|
|
}
|
|
break;
|
|
case LINE_PRESS:
|
|
case RECT_PRESS:
|
|
case OVAL_PRESS:
|
|
yieldForPaint();
|
|
|
|
// Calculate where shape is right now
|
|
float radiusX = Math.abs(currentX * zoom - originX * zoom);
|
|
float radiusY = Math.abs(currentY * zoom - originY * zoom);
|
|
int minX = (int) Math.floor(originX * zoom - radiusX) - 1;
|
|
int minY = (int) Math.floor(originY * zoom - radiusY) - 1;
|
|
int width = (int) Math.ceil(radiusX * 2) + 2;
|
|
int height = (int) Math.ceil(radiusY * 2) + 2;
|
|
|
|
// Repaint this area, clearing it out
|
|
repaint(minX, minY, width, height);
|
|
|
|
currentX = e.getFloatX() / zoom;
|
|
currentY = e.getFloatY() / zoom;
|
|
|
|
// Calculate new position of shape
|
|
radiusX = Math.abs(currentX * zoom - originX * zoom);
|
|
radiusY = Math.abs(currentY * zoom - originY * zoom);
|
|
minX = (int) Math.floor(originX * zoom - radiusX) - 1;
|
|
minY = (int) Math.floor(originY * zoom - radiusY) - 1;
|
|
width = (int) Math.ceil(radiusX * 2) + 2;
|
|
height = (int) Math.ceil(radiusY * 2) + 2;
|
|
|
|
repaint(minX, minY, width, height);
|
|
|
|
break;
|
|
case BEZIER_PRESS:
|
|
yieldForPaint();
|
|
|
|
// Initialize curve to straight line
|
|
x4 = e.getFloatX() / zoom;
|
|
y4 = e.getFloatY() / zoom;
|
|
x2 = (x1 * 2 + x4) / 3;
|
|
y2 = (y1 * 2 + y4) / 3;
|
|
x3 = (x1 + x4 * 2) / 3;
|
|
y3 = (y1 + y4 * 2) / 3;
|
|
|
|
// Calculate extreme corners
|
|
minX = (int) Math.floor(Math.min(x1, x4) * zoom) - 4;
|
|
minY = (int) Math.floor(Math.min(y1, y4) * zoom) - 4;
|
|
int maxX = (int) Math.ceil(Math.max(x1, x4) * zoom) + 4;
|
|
int maxY = (int) Math.ceil(Math.max(y1, y4) * zoom) + 4;
|
|
|
|
repaint(minX, minY, maxX - minX, maxY - minY);
|
|
break;
|
|
case BEZIER_PRESS_P2:
|
|
yieldForPaint();
|
|
|
|
minX = (int) Math.floor(Math.min(x1, Math.min(x2, Math.min(x3, x4))) * zoom) - 4;
|
|
minY = (int) Math.floor(Math.min(y1, Math.min(y2, Math.min(y3, y4))) * zoom) - 4;
|
|
maxX = (int) Math.ceil(Math.max(x1, Math.max(x2, Math.max(x3, x4))) * zoom) + 4;
|
|
maxY = (int) Math.ceil(Math.max(y1, Math.max(y2, Math.max(y3, y4))) * zoom) + 4;
|
|
repaint(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
x2 = e.getFloatX() / zoom;
|
|
y2 = e.getFloatY() / zoom;
|
|
|
|
minX = (int) Math.floor(Math.min(x1, Math.min(x2, Math.min(x3, x4))) * zoom) - 4;
|
|
minY = (int) Math.floor(Math.min(y1, Math.min(y2, Math.min(y3, y4))) * zoom) - 4;
|
|
maxX = (int) Math.ceil(Math.max(x1, Math.max(x2, Math.max(x3, x4))) * zoom) + 4;
|
|
maxY = (int) Math.ceil(Math.max(y1, Math.max(y2, Math.max(y3, y4))) * zoom) + 4;
|
|
repaint(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
break;
|
|
case BEZIER_PRESS_P3:
|
|
yieldForPaint();
|
|
|
|
minX = (int) Math.floor(Math.min(x1, Math.min(x2, Math.min(x3, x4))) * zoom) - 4;
|
|
minY = (int) Math.floor(Math.min(y1, Math.min(y2, Math.min(y3, y4))) * zoom) - 4;
|
|
maxX = (int) Math.ceil(Math.max(x1, Math.max(x2, Math.max(x3, x4))) * zoom) + 4;
|
|
maxY = (int) Math.ceil(Math.max(y1, Math.max(y2, Math.max(y3, y4))) * zoom) + 4;
|
|
repaint(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
x3 = e.getFloatX() / zoom;
|
|
y3 = e.getFloatY() / zoom;
|
|
|
|
minX = (int) Math.floor(Math.min(x1, Math.min(x2, Math.min(x3, x4))) * zoom) - 4;
|
|
minY = (int) Math.floor(Math.min(y1, Math.min(y2, Math.min(y3, y4))) * zoom) - 4;
|
|
maxX = (int) Math.ceil(Math.max(x1, Math.max(x2, Math.max(x3, x4))) * zoom) + 4;
|
|
maxY = (int) Math.ceil(Math.max(y1, Math.max(y2, Math.max(y3, y4))) * zoom) + 4;
|
|
repaint(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
break;
|
|
case SELECT_MOVE:
|
|
yieldForPaint();
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
select.translate((int) (e.getX() / zoom - originX), (int) (e.getY() / zoom - originY));
|
|
originX = e.getX() / zoom;
|
|
originY = e.getY() / zoom;
|
|
++dashPhase;
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
break;
|
|
case SELECT_PRESS:
|
|
yieldForPaint();
|
|
minX = Math.min(iOriginX, Math.round(e.getX() / zoom));
|
|
minY = Math.min(iOriginY, Math.round(e.getY() / zoom));
|
|
maxX = Math.max(iOriginX, Math.round(e.getX() / zoom));
|
|
maxY = Math.max(iOriginY, Math.round(e.getY() / zoom));
|
|
width = maxX - minX;
|
|
height = maxY - minY;
|
|
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
|
|
select.setBounds(minX, minY, maxX - minX, maxY - minY);
|
|
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
|
|
break;
|
|
case MOVE_PRESS:
|
|
break;
|
|
case SCROLL_PRESS:
|
|
los = e.getLocationOnScreen();
|
|
break;
|
|
case EYEDROP_PRESS:
|
|
yieldForPaint();
|
|
eyedrop(e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void cursorReleased(TabletEvent e) {
|
|
int button = e.getButton();
|
|
if(buttonDown == button) {
|
|
buttonDown = TabletEvent.NOBUTTON;
|
|
} else {
|
|
return;
|
|
}
|
|
State myState = getState();
|
|
|
|
switch(button) {
|
|
case TabletEvent.BUTTON1:
|
|
switch(myState) {
|
|
case DRAW_PRESS:
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
setState(State.DRAW_HOVER);
|
|
break;
|
|
case LINE_PRESS:
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(originX, originY, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(e.getFloatX() / zoom, e.getFloatY() / zoom, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
setState(State.LINE_HOVER);
|
|
break;
|
|
case RECT_PRESS:
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(originX, originY, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(e.getFloatX() / zoom, originY, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(e.getFloatX() / zoom, originY, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(e.getFloatX() / zoom, e.getFloatY() / zoom, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(e.getFloatX() / zoom, e.getFloatY() / zoom, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(originX, e.getFloatY() / zoom, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(originX, e.getFloatY() / zoom, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(originX, originY, 0xFF))));
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
setState(State.RECT_HOVER);
|
|
break;
|
|
case BEZIER_PRESS:
|
|
setState(State.BEZIER_HOVER2);
|
|
break;
|
|
case BEZIER_HOVER2:
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(x1, y1, 64))));
|
|
float t = 1f / 64f;
|
|
for(int i = 1; i <= 64; i++, t += 1f / 64f) {
|
|
float x = bezier(t, x1, x2, x3, x4);
|
|
float y = bezier(t, y1, y2, y3, y4);
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(x, y, 64 + (int) (191 * Math.sin(t * Math.PI))))));
|
|
}
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
setState(State.BEZIER_HOVER);
|
|
break;
|
|
case BEZIER_PRESS_P2:
|
|
case BEZIER_PRESS_P3:
|
|
setState(State.BEZIER_HOVER2);
|
|
break;
|
|
case OVAL_PRESS:
|
|
float centerX = originX;
|
|
float centerY = originY;
|
|
float radiusX = Math.abs(e.getFloatX() / zoom - centerX);
|
|
float radiusY = Math.abs(e.getFloatY() / zoom - centerY);
|
|
int divisions = 64;//(int) Math.round(Math.max(radiusX, radiusY) * Math.PI);
|
|
if(divisions == 0) {
|
|
break;
|
|
}
|
|
client.getCommandQueue().offer(new CommandEntry(0, new CursorCommand(new Input(centerX + radiusX, centerY, 0xFF))));
|
|
float alpha = 0;
|
|
for(int i = 0; i <= divisions; i++, alpha += Math.PI * 2 / divisions) {
|
|
client.getCommandQueue().offer(new CommandEntry(0, new LineCommand(new Input(centerX + (float) Math.cos(alpha) * radiusX, centerY + (float) Math.sin(alpha) * radiusY, 0xFF))));
|
|
}
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
setState(State.OVAL_HOVER);
|
|
break;
|
|
case AD_PRESS:
|
|
if(e.getX() >= adPosition.x && e.getY() >= adPosition.y && e.getX() < adPosition.x + ad.getWidth() && e.getY() < adPosition.y + ad.getHeight()) {
|
|
try {
|
|
Class<?> desktopClass = Class.forName("java.awt.Desktop");
|
|
if((Boolean) desktopClass.getMethod("isDesktopSupported").invoke(null)) { // Desktop.isDesktopSupported()
|
|
Object desktop = desktopClass.getMethod("getDesktop").invoke(null); // Desktop.getDesktop()
|
|
Class<?> actionClass = Class.forName("java.awt.Desktop$Action");
|
|
if((Boolean) desktopClass.getMethod("isSupported", actionClass).invoke(desktop, actionClass.getField("BROWSE").get(null))) { // desktop.isSupported(java.awt.Desktop.Action.BROWSE)
|
|
desktopClass.getMethod("browse", URI.class).invoke(desktop, new URI("http://www.jotuntech.com/sketcher/")); // desktop.browse(uri);
|
|
}
|
|
}
|
|
} catch(Throwable t2) {
|
|
JOptionPane.showMessageDialog(client.getUserInterface(), "Visit www.jotuntech.com/sketcher/ to rent a Sketcher room!");
|
|
}
|
|
}
|
|
setState(State.AD_HOVER);
|
|
case SELECT_PRESS:
|
|
if(select.isEmpty()) {
|
|
System.err.println("Empty select!");
|
|
select = null;
|
|
}
|
|
setState(State.SELECT_HOVER);
|
|
repaint();
|
|
break;
|
|
case SELECT_MOVE:
|
|
setState(State.SELECT_INSIDE);
|
|
break;
|
|
case MOVE_HOVER:
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MergeCommand()));
|
|
select = null;
|
|
repaint();
|
|
break;
|
|
case MOVE_PRESS:
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
int transX = (int) (e.getX() / zoom - originX);
|
|
int transY = (int) (e.getY() / zoom - originY);
|
|
client.getCommandQueue().offer(new CommandEntry(0, new MoveCommand(select.x, select.y, select.x + transX, select.y + transY, select.width, select.height)));
|
|
select.translate(transX, transY);
|
|
++dashPhase;
|
|
repaint(Math.round(select.x * zoom) - 1, Math.round(select.y * zoom) - 1, Math.round(select.width * zoom) + 2, Math.round(select.height * zoom) + 2);
|
|
setState(State.MOVE_INSIDE);
|
|
break;
|
|
case SCROLL_PRESS:
|
|
restoreState();
|
|
break;
|
|
}
|
|
break;
|
|
case TabletEvent.BUTTON3:
|
|
switch(myState) {
|
|
case EYEDROP_PRESS:
|
|
eyedrop(e);
|
|
restoreState();
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void cursorScrolled(TabletEvent e) {
|
|
|
|
}
|
|
|
|
public void levelChanged(TabletEvent e) {
|
|
|
|
}
|
|
|
|
public BufferedImage getImage() {
|
|
return image;
|
|
}
|
|
|
|
public void setSmoothZoom(boolean enabled) {
|
|
this.smoothZoom = enabled;
|
|
}
|
|
|
|
public boolean isSmoothZoom() {
|
|
return smoothZoom;
|
|
}
|
|
|
|
public void setTagsEnabled(boolean tagsEnabled) {
|
|
this.tagsEnabled = tagsEnabled;
|
|
}
|
|
|
|
public boolean isTagsEnabled() {
|
|
return tagsEnabled;
|
|
}
|
|
|
|
public void setAd(BufferedImage ad) {
|
|
this.ad = ad;
|
|
}
|
|
|
|
public BufferedImage getAd() {
|
|
return ad;
|
|
}
|
|
|
|
public void setAdPosition(Point adPosition) {
|
|
this.adPosition = adPosition;
|
|
}
|
|
|
|
public void setAdPosition(int x, int y) {
|
|
this.adPosition = new Point(x, y);
|
|
}
|
|
|
|
public Point getAdPosition() {
|
|
return adPosition;
|
|
}
|
|
|
|
public Rectangle getSelect() {
|
|
return select;
|
|
}
|
|
|
|
public boolean isSoftwareCursorEnabled() {
|
|
return softwareCursorEnabled;
|
|
}
|
|
|
|
public void setSoftwareCursorEnabled(boolean softwareCursorEnabled) {
|
|
this.softwareCursorEnabled = softwareCursorEnabled;
|
|
}
|
|
}
|
|
|