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    }