Implement Operation Touch and Drag
[aster:aster.git] / src / org / zeroxlab / aster / AsterWorkspace.java
1 /*
2  * Copyright (C) 2011 0xlab - http://0xlab.org/
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  * Authored by Kan-Ru Chen <kanru@0xlab.org>
17  *             Wei-Ning Huang <azhuang@0xlab.org>
18  *             Julian Chu <walkingice@0xlab.org>
19  */
20
21 package org.zeroxlab.aster;
22
23 import java.awt.Color;
24 import java.awt.event.ComponentEvent;
25 import java.awt.event.ComponentListener;
26 import java.awt.event.MouseEvent;
27 import java.awt.event.MouseListener;
28 import java.awt.event.MouseMotionListener;
29 import java.awt.geom.AffineTransform;
30 import java.awt.Graphics;
31 import java.awt.image.AffineTransformOp;
32 import java.awt.image.BufferedImage;
33 import java.awt.Point;
34 import java.awt.Rectangle;
35 import java.util.Vector;
36 import javax.swing.JComponent;
37
38 import org.zeroxlab.aster.AsterCommand;
39 import org.zeroxlab.aster.OpDrag;
40 import org.zeroxlab.aster.OpTouch;
41
42 public class AsterWorkspace extends JComponent implements ComponentListener, MouseListener, MouseMotionListener {
43
44     public final static int LANDSCAPE_WIDTH  = 400;
45     public final static int LANDSCAPE_HEIGHT = 240;
46     public final static int PORTRAIT_WIDTH  = 240;
47     public final static int PORTRAIT_HEIGHT = 400;
48
49     private BufferedImage mSourceImage;
50     private BufferedImage mDrawingBuffer;
51
52     private DragListener mDragListener;
53     private TouchListener mTouchListener;
54
55     private Rectangle mImgRect;
56     private int mSourceWidth;
57     private int mSourceHeight;
58     private int mWidth;
59     private int mHeight;
60
61     private boolean mValid;
62     private int mPressX;
63     private int mPressY;
64
65     private final int NONE = 0;
66     private final int POINT_L = 1;
67     private final int POINT_C = 2;
68     private final int POINT_R = 3;
69     private final int POINT_D = 4;
70     private int mMoving = NONE;
71     private ClipRegion mRegion;
72
73     public AsterWorkspace() {
74         this(new BufferedImage(PORTRAIT_WIDTH, PORTRAIT_HEIGHT, BufferedImage.TYPE_INT_ARGB));
75     }
76
77     public AsterWorkspace(BufferedImage img) {
78         mRegion = new ClipRegion();
79         mImgRect = new Rectangle();
80         mDrawingBuffer = new BufferedImage(PORTRAIT_WIDTH, PORTRAIT_HEIGHT, BufferedImage.TYPE_INT_ARGB);
81         addComponentListener(this);
82         addMouseListener(this);
83         addMouseMotionListener(this);
84         setImage(img);
85         generateDrawingBuffer();
86     }
87
88     public void setImage(BufferedImage img) {
89         mSourceImage = img;
90         if (mSourceImage != null) {
91             updateDrawingBuffer(mSourceImage);
92         }
93     }
94
95     public void fillCmd(AsterCommand cmd) {
96         AsterOperation[] ops = cmd.getOperations();
97         if (ops == null) {
98             System.err.println("You are asking me to fill an empty command");
99             return;
100         }
101
102         for (int i = 0; i < ops.length; i++) {
103             mRegion.setVisible(false);
104             mRegion.moveD(-1, -1); // hide
105             setDragListener(null);
106             setTouchListener(null);
107             ops[i].record();
108         }
109     }
110
111     public void setDragListener(DragListener listener) {
112         mDragListener = listener;
113     }
114
115     public void setTouchListener(TouchListener listener) {
116         mTouchListener = listener;
117     }
118
119     public void paintComponent(Graphics g) {
120         g.setColor(Color.BLACK);
121         g.fillRect(0, 0, mWidth, mHeight);
122         g.drawImage(mDrawingBuffer, mImgRect.x, mImgRect.y, null);
123
124         mRegion.paint(g);
125     }
126
127     public void componentHidden(ComponentEvent e) {
128     }
129
130     public void componentMoved(ComponentEvent e) {
131     }
132
133     public void componentResized(ComponentEvent e) {
134         mWidth  = getWidth();
135         mHeight = getHeight();
136         generateDrawingBuffer();
137         updateDrawingBuffer(mSourceImage);
138         repaint();
139     }
140
141     public void componentShown(ComponentEvent e) {
142     }
143
144     public void mouseClicked(MouseEvent e) {
145         int x = e.getX();
146         int y = e.getY();
147
148         if (!mImgRect.contains(x, y)) {
149             return;
150         }
151
152         mRegion.moveC(x, y);
153         x = (int)((mSourceWidth * (x - mImgRect.x)) / mImgRect.width);
154         y = (int)((mSourceHeight * (y - mImgRect.y)) / mImgRect.height);
155         if (mTouchListener != null) {
156             mTouchListener.clicked(x, y);
157         } else if (mDragListener != null) {
158             int ex = mRegion.pD.x;
159             int ey = mRegion.pD.y;
160             mDragListener.dragged(x, y, ex, ey);
161         }
162     }
163
164     public void mouseEntered(MouseEvent e) {
165     }
166
167     public void mouseExited(MouseEvent e) {
168     }
169
170     public void mousePressed(MouseEvent e) {
171         mPressX = e.getX();
172         mPressY = e.getY();
173
174         if (mRegion.inLT(mPressX, mPressY)) {
175             mMoving = POINT_L;
176         } else if (mRegion.inRB(mPressX, mPressY)) {
177             mMoving = POINT_R;
178         } else if (mRegion.inD(mPressX, mPressY)) {
179             mMoving = POINT_D;
180         } else {
181             mMoving = NONE;
182         }
183     }
184
185     public void mouseReleased(MouseEvent e) {
186         double distance;
187         int rX = e.getX();
188         int rY = e.getY();
189         int pX = mPressX;
190         int pY = mPressY;
191
192         if (!mImgRect.contains(pX, pY) || !mImgRect.contains(rX, rY)) {
193             return;
194         }
195
196         /* if the drag distance too short, ignore it */
197         distance = Math.pow(rX - pX, 2);
198         distance += Math.pow(rY - pY, 2);
199         if (distance < 16) {
200             return;
201         }
202
203         if (mMoving == NONE) {
204             mRegion.moveC(mPressX, mPressY);
205             mRegion.moveD(e.getX(), e.getY());
206         }
207         pX = (int)((mSourceWidth  * (mRegion.pC.x - mImgRect.x)) / mImgRect.width);
208         pY = (int)((mSourceHeight * (mRegion.pC.y - mImgRect.y)) / mImgRect.height);
209         rX = (int)((mSourceWidth  * (mRegion.pD.x - mImgRect.x)) / mImgRect.width);
210         rY = (int)((mSourceHeight * (mRegion.pD.y - mImgRect.y)) / mImgRect.height);
211         if (mDragListener != null){
212             mDragListener.dragged(pX, pY, rX, rY);
213         }
214     }
215
216     public void mouseDragged(MouseEvent e) {
217         int x = e.getX();
218         int y = e.getY();
219         if (mMoving == POINT_L) {
220             mRegion.moveL(x, y);
221         } else if (mMoving == POINT_R) {
222             mRegion.moveR(x, y);
223         } else if (mMoving == POINT_D) {
224             mRegion.moveD(x, y);
225         }
226         repaint();
227     }
228
229     public void mouseMoved(MouseEvent e) {
230     }
231
232     private void generateDrawingBuffer() {
233         boolean isLandscape = (mSourceImage.getWidth() > mSourceImage.getHeight());
234
235         if (isLandscape) {
236             mImgRect.width  = LANDSCAPE_WIDTH;
237             mImgRect.height = LANDSCAPE_HEIGHT;
238         } else {
239             mImgRect.width  = PORTRAIT_WIDTH;
240             mImgRect.height = PORTRAIT_HEIGHT;
241         }
242
243         mImgRect.x = (mWidth  - mImgRect.width) / 2;
244         mImgRect.y = (mHeight - mImgRect.height) / 2;
245         mImgRect.x = Math.max(mImgRect.x, 0);
246         mImgRect.y = Math.max(mImgRect.y, 0);
247
248         if (mDrawingBuffer == null
249                 || mDrawingBuffer.getWidth() != mImgRect.width
250                 || mDrawingBuffer.getHeight() != mImgRect.height) {
251             mDrawingBuffer = new BufferedImage(mImgRect.width, mImgRect.height, BufferedImage.TYPE_INT_ARGB);
252         }
253     }
254
255     private void updateDrawingBuffer(BufferedImage source) {
256         mSourceWidth  = source.getWidth();
257         mSourceHeight = source.getHeight();
258         mDrawingBuffer.getGraphics().drawImage(
259                 source, 0, 0,
260                 mDrawingBuffer.getWidth(),
261                 mDrawingBuffer.getHeight(),
262                 null
263                 );
264     }
265
266     public interface DragListener {
267         public void dragged(int startX, int startY, int endX, int endY);
268     }
269
270     public interface TouchListener {
271         public void clicked(int x, int y);
272     }
273
274     public OpTouch getOpTouch() {
275         return new MyTouch();
276     }
277
278     public OpDrag getOpDrag() {
279         return new MyDrag();
280     }
281
282     class ClipRegion {
283         final int W = 10;
284         final int H = 10;
285         int dx, dy;
286
287         final Point pL, pC, pR, pD;
288
289         private boolean mVisible;
290
291         ClipRegion() {
292             pL = new Point(0, 0);
293             pC = new Point(30, 30);
294             pR = new Point(60, 60);
295             pD = new Point(-1, -1);
296             mVisible = false;
297         }
298
299         public void setVisible(boolean visible) {
300             mVisible = visible;
301         }
302
303         public void paint(Graphics g) {
304             if (!mVisible) {
305                 return;
306             }
307
308             g.setColor(Color.RED);
309             g.drawRect(pL.x, pL.y, pR.x - pL.x, pR.y - pL.y);
310             g.setColor(Color.BLUE);
311             g.fillRect(pL.x, pL.y, W, H);
312             g.fillRect(pR.x - W, pR.y - H, W, H);
313             g.fillRect(pC.x - (int)(W / 2), pC.y - (int)(H / 2), W, H);
314
315             if (pD.x != -1 || pD.y != -1) {
316                 g.drawLine(pC.x, pC.y, pD.x, pD.y);
317                 g.fillRect(pD.x - (int)(W / 2), pD.y - (int)(H / 2), W, H);
318             }
319         }
320
321         public void moveC(int x, int y) {
322             dx = x - pC.x;
323             dy = y - pC.y;
324             pC.setLocation(x, y);
325             pL.translate(dx, dy);
326             pR.translate(dx, dy);
327             if (pD.x != -1 && pD.y != -1) {
328                 pD.translate(dx, dy);
329             }
330             repaint();
331         }
332
333         public void moveD(int x, int y) {
334             pD.setLocation(x, y);
335         }
336
337         public void moveL(int x, int y) {
338             dx = x - pC.x;
339             dy = y - pC.y;
340             if (dx > -1 * W || dy > -1 * H) {
341                 return;
342             }
343
344             pL.setLocation(x , y);
345             repaint();
346         }
347
348         public void moveR(int x, int y) {
349             dx = x - pC.x;
350             dy = y - pC.y;
351             if (dx < W || dy < H) {
352                 return;
353             }
354
355             pR.setLocation(x , y);
356             repaint();
357         }
358
359         public boolean inLT(int x, int y) {
360             return (x >= pL.x && x <= (pL.x + W)
361                     && y >= pL.y && y <= (pL.y + H));
362         }
363
364         public boolean inRB(int x, int y) {
365             return (x >= (pR.x - W) && x <= pR.x
366                     && y >= (pR.y - H) && y <= pR.y);
367         }
368
369         public boolean inD(int x, int y) {
370             if (pD.x == -1 || pD.y == -1) {
371                 return false;
372             }
373
374             dx = x - pD.x;
375             dy = y - pD.y;
376             return (Math.abs(dx) < (W / 2) || Math.abs(dy) < (H / 2));
377         }
378     }
379
380     class MyTouch extends OpTouch implements TouchListener {
381         public void clicked(int x, int y) {
382             setPoint(x, y);
383         }
384
385         public void setPoint(int x, int y) {
386             x = Math.max(0, x);
387             y = Math.max(0, y);
388             x = Math.min(x, mSourceWidth);
389             y = Math.min(y, mSourceHeight);
390             System.out.println("Set point:" + x + "," + y);
391             super.set(x, y);
392         }
393
394         public String getName() {
395             return "Touch";
396         }
397
398         public void record() {
399             int x = super.getX();
400             int y = super.getY();
401             if (x > 0 && y > 0 && x < mSourceWidth && y < mSourceHeight) {
402                 // valid location
403                 x = (int)((x / mSourceWidth) * mImgRect.width);
404                 y = (int)((y / mSourceHeight) * mImgRect.height);
405             } else {
406                 // move to center by default
407                 x = (int)(mImgRect.width / 2);
408                 y = (int)(mImgRect.height / 2);
409             }
410             mRegion.moveC(x + mImgRect.x, y + mImgRect.y);
411             mRegion.setVisible(true);
412             setTouchListener(this);
413         }
414     }
415
416     class MyDrag extends OpDrag implements DragListener {
417         public String getName() {
418             return "Drag";
419         }
420
421         public void record() {
422             int sX = super.getStartX();
423             int sY = super.getStartY();
424             int eX = super.getEndX();
425             int eY = super.getEndY();
426             Rectangle r = new Rectangle(0, 0, mSourceWidth, mSourceHeight);
427             if (r.contains(sX, sY) && r.contains(eX, eY)) {
428                 // valid location
429                 sX = (int)((sX / mSourceWidth) * mImgRect.width);
430                 sY = (int)((sY / mSourceHeight) * mImgRect.height);
431                 eX = (int)((eX / mSourceWidth) * mImgRect.width);
432                 eY = (int)((eY / mSourceHeight) * mImgRect.height);
433             } else {
434                 // move to center by default
435                 sX = (int)(mImgRect.width / 2);
436                 sY = (int)(mImgRect.height / 2);
437                 eX = sX + 100;
438                 eY = sY;
439             }
440             mRegion.moveC(sX + mImgRect.x, sY + mImgRect.y);
441             mRegion.moveD(eX + mImgRect.x, eY + mImgRect.y);
442             mRegion.setVisible(true);
443             setDragListener(this);
444         }
445
446         public void dragged(int sx, int sy, int ex, int ey) {
447             sx = Math.max(0, sx);
448             sy = Math.max(0, sy);
449             sx = Math.min(sx, mSourceWidth);
450             sy = Math.min(sy, mSourceHeight);
451             ex = Math.max(0, ex);
452             ey = Math.max(0, ey);
453             ex = Math.min(ex, mSourceWidth);
454             ey = Math.min(ey, mSourceHeight);
455             System.out.println("Set point:(" + sx + "," + sy
456                     + ") to (" + ex + "," + ey + ")");
457             super.set(sx, sy, ex, ey);
458         }
459     }
460 }