001 /**
002 * Copyright (c) 2008-2009, Piet Blok
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without
006 * modification, are permitted provided that the following conditions
007 * are met:
008 *
009 * * Redistributions of source code must retain the above copyright
010 * notice, this list of conditions and the following disclaimer.
011 * * Redistributions in binary form must reproduce the above
012 * copyright notice, this list of conditions and the following
013 * disclaimer in the documentation and/or other materials provided
014 * with the distribution.
015 * * Neither the name of the copyright holder nor the names of the
016 * contributors may be used to endorse or promote products derived
017 * from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
020 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
021 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
022 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
023 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
024 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
025 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
026 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
027 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
029 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031
032 package org.pbjar.jxlayer.plaf.misc;
033
034 import java.awt.BasicStroke;
035 import java.awt.Color;
036 import java.awt.Dimension;
037 import java.awt.Graphics2D;
038 import java.awt.Rectangle;
039 import java.awt.Toolkit;
040 import java.awt.event.AWTEventListener;
041 import java.awt.event.ActionEvent;
042 import java.awt.event.MouseEvent;
043 import java.awt.geom.AffineTransform;
044 import java.awt.geom.Line2D;
045 import java.awt.geom.NoninvertibleTransformException;
046 import java.awt.geom.Point2D;
047 import java.util.ArrayList;
048 import java.util.List;
049
050 import javax.swing.AbstractAction;
051 import javax.swing.Action;
052 import javax.swing.JColorChooser;
053 import javax.swing.JComponent;
054 import javax.swing.SwingUtilities;
055
056 import org.jdesktop.jxlayer.JXLayer;
057
058 /**
059 * A mouse drawing UI.
060 *
061 * @author Piet Blok
062 */
063 public class MouseDrawingUI extends
064 GeneralLayerUI<JComponent, MouseDrawingUI.DrawingState> {
065
066 /**
067 * Holds state information.
068 */
069 protected static class DrawingState {
070
071 public Color lineColor = Color.RED;
072
073 public ColoredLine coloredLine = null;
074
075 public List<ColoredLine> lineList = new ArrayList<ColoredLine>();
076
077 public Dimension preferred = null;
078
079 public Rectangle innerArea = new Rectangle();
080
081 public Rectangle newInnerArea = new Rectangle();
082
083 public AffineTransform transform = new AffineTransform();
084
085 public AffineTransform inverseTransform = new AffineTransform();
086
087 private final JXLayer<JComponent> layer;
088
089 private double scale = 1.0;
090
091 public DrawingState(JXLayer<JComponent> layer) {
092 this.layer = layer;
093 }
094
095 public void addPoint(Point2D point) {
096 if (coloredLine == null) {
097 coloredLine = new ColoredLine(lineColor);
098 lineList.add(coloredLine);
099 }
100 updateSize();
101 coloredLine.addPoint(inverseTransform.transform(point, point));
102 }
103
104 public void clear() {
105 lineList.clear();
106 }
107
108 public Color getLineColor() {
109 return lineColor;
110 }
111
112 public void setLineColor(Color color) {
113 this.lineColor = color;
114 }
115
116 public void terminateLine() {
117 coloredLine = null;
118 }
119
120 /**
121 * Not implemented.
122 * <p>
123 * This is for future use and meant to ensure that the drawing scales
124 * with the scaling of the underlying component. But it interferes a bit
125 * with the current testing on other types of components.
126 * </p>
127 */
128 public void updateSize() {
129 // updateSizeImpl();
130 }
131
132 private double getOffset(double scale, double imageSize, double center) {
133 return (center - (imageSize * scale / 2.0)) / scale;
134 }
135
136 @SuppressWarnings("unused")
137 private void updateSizeImpl() {
138 boolean dirty = false;
139 SwingUtilities.calculateInnerArea(layer, newInnerArea);
140 if (!newInnerArea.equals(innerArea)) {
141 innerArea.setRect(newInnerArea);
142 dirty = true;
143 }
144 Dimension newPreferred = layer.getPreferredSize();
145 if (!newPreferred.equals(preferred)) {
146 preferred = newPreferred;
147 dirty = true;
148 }
149 if (dirty) {
150 transform.setToIdentity();
151 scale = Math.min(innerArea.getWidth() / preferred.getWidth(),
152 innerArea.getHeight() / preferred.getHeight());
153 transform.scale(scale, scale);
154
155 transform.translate(getOffset(scale, preferred.getWidth(),
156 innerArea.getCenterX()), getOffset(scale, preferred
157 .getHeight(), innerArea.getCenterY()));
158
159 try {
160 inverseTransform = transform.createInverse();
161 } catch (NoninvertibleTransformException e) {
162 e.printStackTrace();
163 }
164 }
165 }
166 }
167
168 /**
169 * Defines one line.
170 */
171 protected static class ColoredLine {
172
173 public final List<Point2D> path = new ArrayList<Point2D>();
174
175 public final Color color;
176
177 public ColoredLine(Color color) {
178 this.color = color;
179 }
180
181 public void addPoint(Point2D point) {
182 path.add(point);
183 }
184
185 }
186
187 /**
188 * Create an instance with {@link AWTEventListener} disabled.
189 */
190 public MouseDrawingUI() {
191 this(false);
192 }
193
194 /**
195 * Create an instance.
196 * <p>
197 * Via the enableAWTEventListener argument, {@link AWTEventListener} can be
198 * enabled or disabled.
199 * </p>
200 *
201 * @param enableAWTEventListener
202 * if {@code true}, {@link AWTEventListener} will be enabled.
203 * Specifying {@code true} Solves a problem with non editable
204 * {@code JTextComponent}s.
205 * @throws SecurityException
206 * when enableAWTEventListener is {@code true} and if a security
207 * manager exists and its {@code checkPermission} method doesn't
208 * allow the operation.
209 * @see Toolkit#addAWTEventListener(AWTEventListener, long)
210 * */
211 public MouseDrawingUI(boolean enableAWTEventListener) {
212 super(enableAWTEventListener);
213 }
214
215 /**
216 * Returns {@link Action}s for:
217 * <ol>
218 * <li>Clear the current drawing.</li>
219 * <li>Set the color for the next line to be drawn.</li>
220 * </ol>
221 */
222 public List<Action> getActions(final JXLayer<JComponent> layer) {
223 ArrayList<Action> actionList = new ArrayList<Action>();
224 actionList.addAll(super.getActions(layer));
225 /*
226 * Clear
227 */
228 actionList.add(new AbstractAction("Clear") {
229
230 private static final long serialVersionUID = 1L;
231
232 @Override
233 public void actionPerformed(ActionEvent event) {
234 MouseDrawingUI.this.getStateObject(layer).clear();
235 setDirty(true);
236 }
237 });
238 /*
239 * Set line color
240 */
241 actionList.add(new AbstractAction("Set line color") {
242
243 private static final long serialVersionUID = 1L;
244
245 @Override
246 public void actionPerformed(ActionEvent event) {
247 DrawingState state = MouseDrawingUI.this.getStateObject(layer);
248 Color color = state.getLineColor();
249 color = JColorChooser.showDialog(layer,
250 "Choose a new line color", color);
251 if (color != null) {
252 state.setLineColor(color);
253 }
254 }
255 });
256
257 return actionList;
258 }
259
260 @Override
261 protected DrawingState createStateObject(JXLayer<JComponent> layer) {
262 return new DrawingState(layer);
263 }
264
265 protected void paintLayer(Graphics2D g2, JXLayer<JComponent> layer) {
266 super.paintLayer(g2, layer);
267 DrawingState state = getStateObject(layer);
268 state.updateSize();
269 g2.transform(state.transform);
270 g2.setStroke(new BasicStroke(4f / (float) state.scale));
271 Line2D line = new Line2D.Double();
272 for (ColoredLine coloredLine : state.lineList) {
273 g2.setColor(coloredLine.color);
274 Point2D oldPoint = null;
275 for (Point2D point : coloredLine.path) {
276 if (oldPoint != null) {
277 line.setLine(oldPoint, point);
278 g2.draw(line);
279 }
280 oldPoint = point;
281 }
282 }
283 }
284
285 @Override
286 protected void processMouseEvent(MouseEvent e, JXLayer<JComponent> layer) {
287 if (e.getID() == MouseEvent.MOUSE_RELEASED) {
288 DrawingState state = getStateObject(layer);
289 state.terminateLine();
290 setDirty(true);
291 }
292 }
293
294 @Override
295 protected void processMouseMotionEvent(MouseEvent e,
296 JXLayer<JComponent> layer) {
297 if (e.getID() == MouseEvent.MOUSE_DRAGGED) {
298 DrawingState state = getStateObject(layer);
299 state.addPoint(SwingUtilities.convertPoint(e.getComponent(), e
300 .getPoint(), layer));
301 setDirty(true);
302 }
303 }
304
305 }