Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.shape;
027
028import javafx.css.CssMetaData;
029import com.sun.javafx.css.converters.PaintConverter;
030import javafx.beans.property.DoubleProperty;
031import javafx.beans.property.DoublePropertyBase;
032import javafx.scene.paint.Color;
033
034import com.sun.javafx.geom.BaseBounds;
035import com.sun.javafx.geom.Line2D;
036import com.sun.javafx.geom.transform.BaseTransform;
037import com.sun.javafx.scene.DirtyBits;
038import com.sun.javafx.sg.PGLine;
039import com.sun.javafx.sg.PGNode;
040import com.sun.javafx.sg.PGShape.Mode;
041import com.sun.javafx.tk.Toolkit;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.List;
045import javafx.beans.value.WritableValue;
046import javafx.css.StyleableProperty;
047import javafx.scene.paint.Paint;
048
049
050/**
051 * This Line represents a line segment in {@code (x,y)}
052 * coordinate space. Example:
053 *
054<PRE>
055import javafx.scene.shape.*;
056
057Line line = new Line();
058line.setStartX(0.0f);
059line.setStartY(0.0f);
060line.setEndX(100.0f);
061line.setEndY(100.0f);
062}
063</PRE>
064 */
065public class Line extends Shape {
066
067    private final Line2D shape = new Line2D();
068
069    {
070        // overriding default values for fill and stroke
071        // Set through CSS property so that it appears to be a UA style rather
072        // that a USER style so that fill and stroke can still be set from CSS.
073        ((StyleableProperty)fillProperty()).applyStyle(null, null);
074        ((StyleableProperty)strokeProperty()).applyStyle(null, Color.BLACK);
075    }
076
077    /**
078     * Creates an empty instance of Line.
079     */
080    public Line() {
081    }
082
083    /**
084     * Creates a new instance of Line.
085     * @param startX the horizontal coordinate of the start point of the line segment
086     * @param startY the vertical coordinate of the start point of the line segment
087     * @param endX the horizontal coordinate of the end point of the line segment
088     * @param endY the vertical coordinate of the end point of the line segment
089     */
090    public Line(double startX, double startY, double endX, double endY) {
091        setStartX(startX);
092        setStartY(startY);
093        setEndX(endX);
094        setEndY(endY);
095    }
096
097    /**
098     * The X coordinate of the start point of the line segment.
099     *
100     * @defaultValue 0.0
101     */
102    private DoubleProperty startX;
103
104
105    public final void setStartX(double value) {
106        if (startX != null || value != 0.0) {
107            startXProperty().set(value);
108        }
109    }
110
111    public final double getStartX() {
112        return startX == null ? 0.0 : startX.get();
113    }
114
115    public final DoubleProperty startXProperty() {
116        if (startX == null) {
117            startX = new DoublePropertyBase() {
118
119                @Override
120                public void invalidated() {
121                    impl_markDirty(DirtyBits.NODE_GEOMETRY);
122                    impl_geomChanged();
123                }
124
125                @Override
126                public Object getBean() {
127                    return Line.this;
128                }
129
130                @Override
131                public String getName() {
132                    return "startX";
133                }
134            };
135        }
136        return startX;
137    }
138
139    /**
140     * The Y coordinate of the start point of the line segment.
141     *
142     * @defaultValue 0.0
143     */
144    private DoubleProperty startY;
145
146
147
148    public final void setStartY(double value) {
149        if (startY != null || value != 0.0) {
150            startYProperty().set(value);
151        }
152    }
153
154    public final double getStartY() {
155        return startY == null ? 0.0 : startY.get();
156    }
157
158    public final DoubleProperty startYProperty() {
159        if (startY == null) {
160            startY = new DoublePropertyBase() {
161
162                @Override
163                public void invalidated() {
164                    impl_markDirty(DirtyBits.NODE_GEOMETRY);
165                    impl_geomChanged();
166                }
167
168                @Override
169                public Object getBean() {
170                    return Line.this;
171                }
172
173                @Override
174                public String getName() {
175                    return "startY";
176                }
177            };
178        }
179        return startY;
180    }
181
182    /**
183     * The X coordinate of the end point of the line segment.
184     *
185     * @defaultValue 0.0
186     */
187    private DoubleProperty endX;
188
189
190
191    public final void setEndX(double value) {
192        if (endX != null || value != 0.0) {
193            endXProperty().set(value);
194        }
195    }
196
197    public final double getEndX() {
198        return endX == null ? 0.0 : endX.get();
199    }
200
201    public final DoubleProperty endXProperty() {
202        if (endX == null) {
203            endX = new DoublePropertyBase() {
204
205        @Override
206        public void invalidated() {
207            impl_markDirty(DirtyBits.NODE_GEOMETRY);
208            impl_geomChanged();
209        }
210
211        @Override
212        public Object getBean() {
213            return Line.this;
214        }
215
216        @Override
217        public String getName() {
218            return "endX";
219        }
220    };
221    }
222        return endX;
223    }
224
225    /**
226     * The Y coordinate of the end point of the line segment.
227     *
228     * @defaultValue 0.0
229     */
230    private DoubleProperty endY;
231
232    public final void setEndY(double value) {
233        if (endY != null || value != 0.0) {
234            endYProperty().set(value);
235        }
236    }
237
238    public final double getEndY() {
239        return endY == null ? 0.0 : endY.get();
240    }
241
242    public final DoubleProperty endYProperty() {
243        if (endY == null) {
244            endY = new DoublePropertyBase() {
245
246        @Override
247        public void invalidated() {
248            impl_markDirty(DirtyBits.NODE_GEOMETRY);
249            impl_geomChanged();
250        }
251
252        @Override
253        public Object getBean() {
254            return Line.this;
255        }
256
257        @Override
258        public String getName() {
259            return "endY";
260        }
261    };
262    }
263        return endY;
264    }
265
266    /**
267     * @treatAsPrivate implementation detail
268     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
269     */
270    @Deprecated
271    @Override
272    protected PGNode impl_createPGNode() {
273        return Toolkit.getToolkit().createPGLine();
274    }
275
276    PGLine getPGLine() {
277        return (PGLine)impl_getPGNode();
278    }
279
280    /**
281     * @treatAsPrivate implementation detail
282     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
283     */
284    @Deprecated
285    @Override
286    public BaseBounds impl_computeGeomBounds(BaseBounds bounds, BaseTransform tx) {
287
288        // Since line's only draw with strokes, if the mode is FILL or EMPTY
289        // then we simply return empty bounds
290        if (impl_mode == Mode.FILL || impl_mode == Mode.EMPTY ||
291            getStrokeType() == StrokeType.INSIDE)
292        {
293            return bounds.makeEmpty();
294        }
295
296        double x1 = getStartX();
297        double x2 = getEndX();
298        double y1 = getStartY();
299        double y2 = getEndY();
300        // Get the draw stroke, and figure out the bounds based on the stroke.
301        double wpad = getStrokeWidth();
302        if (getStrokeType() == StrokeType.CENTERED) {
303            wpad /= 2.0f;
304        }
305        // fast path the case of AffineTransform being TRANSLATE or identity
306        if (tx.isTranslateOrIdentity()) {
307            final double xpad;
308            final double ypad;
309            wpad = Math.max(wpad, 0.5f);
310            if (tx.getType() == BaseTransform.TYPE_TRANSLATION) {
311                final double ddx = tx.getMxt();
312                final double ddy = tx.getMyt();
313                x1 += ddx;
314                y1 += ddy;
315                x2 += ddx;
316                y2 += ddy;
317            }
318            if (y1 == y2 && x1 != x2) {
319                ypad = wpad;
320                xpad = (getStrokeLineCap() == StrokeLineCap.BUTT) ? 0.0f : wpad;
321            } else if (x1 == x2 && y1 != y2) {
322                xpad = wpad;
323                ypad = (getStrokeLineCap() == StrokeLineCap.BUTT) ? 0.0f : wpad;
324            } else {
325                if (getStrokeLineCap() == StrokeLineCap.SQUARE) {
326                    wpad *= Math.sqrt(2);
327                }
328                xpad = ypad = wpad;
329            }
330            if (x1 > x2) { final double t = x1; x1 = x2; x2 = t; }
331            if (y1 > y2) { final double t = y1; y1 = y2; y2 = t; }
332
333            x1 -= xpad;
334            y1 -= ypad;
335            x2 += xpad;
336            y2 += ypad;
337            bounds = bounds.deriveWithNewBounds((float)x1, (float)y1, 0.0f,
338                    (float)x2, (float)y2, 0.0f);
339            return bounds;
340        }
341
342        double dx = x2 - x1;
343        double dy = y2 - y1;
344        final double len = Math.sqrt(dx * dx + dy * dy);
345        if (len == 0.0f) {
346            dx = wpad;
347            dy = 0.0f;
348        } else {
349            dx = wpad * dx / len;
350            dy = wpad * dy / len;
351        }
352        final double ecx;
353        final double ecy;
354        if (getStrokeLineCap() != StrokeLineCap.BUTT) {
355            ecx = dx;
356            ecy = dy;
357        } else {
358            ecx = ecy = 0.0f;
359        }
360        final double corners[] = new double[] {
361            x1-dy-ecx, y1+dx-ecy,
362            x1+dy-ecx, y1-dx-ecy,
363            x2+dy+ecx, y2-dx+ecy,
364            x2-dy+ecx, y2+dx+ecy };
365        tx.transform(corners, 0, corners, 0, 4);
366        x1 = Math.min(Math.min(corners[0], corners[2]),
367                             Math.min(corners[4], corners[6]));
368        y1 = Math.min(Math.min(corners[1], corners[3]),
369                             Math.min(corners[5], corners[7]));
370        x2 = Math.max(Math.max(corners[0], corners[2]),
371                             Math.max(corners[4], corners[6]));
372        y2 = Math.max(Math.max(corners[1], corners[3]),
373                             Math.max(corners[5], corners[7]));
374        x1 -= 0.5f;
375        y1 -= 0.5f;
376        x2 += 0.5f;
377        y2 += 0.5f;
378        bounds = bounds.deriveWithNewBounds((float)x1, (float)y1, 0.0f,
379                (float)x2, (float)y2, 0.0f);
380        return bounds;
381    }
382
383    /**
384     * @treatAsPrivate implementation detail
385     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
386     */
387    @Deprecated
388    @Override
389        public Line2D impl_configShape() {
390        shape.setLine((float)getStartX(), (float)getStartY(), (float)getEndX(), (float)getEndY());
391        return shape;
392    }
393
394    /**
395     * @treatAsPrivate implementation detail
396     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
397     */
398    @Deprecated
399    @Override
400    public void impl_updatePG() {
401        super.impl_updatePG();
402
403        if (impl_isDirty(DirtyBits.NODE_GEOMETRY)) {
404            PGLine peer = getPGLine();
405            peer.updateLine((float)getStartX(),
406                (float)getStartY(),
407                (float)getEndX(),
408                (float)getEndY());
409        }
410    }
411
412    /***************************************************************************
413     *                                                                         *
414     *                         Stylesheet Handling                             *
415     *                                                                         *
416     **************************************************************************/
417
418    /** 
419     * Some sub-class of Shape, such as {@link Line}, override the
420     * default value for the {@link Shape#fill} property. This allows
421     * CSS to get the correct initial value.
422     * @treatAsPrivate Implementation detail
423     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
424     */
425    @Deprecated
426    protected Paint impl_cssGetFillInitialValue() {
427        return null;
428    }    
429    
430    /** 
431     * Some sub-class of Shape, such as {@link Line}, override the
432     * default value for the {@link Shape#stroke} property. This allows
433     * CSS to get the correct initial value.
434     * @treatAsPrivate Implementation detail
435     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
436     */
437    @Deprecated
438    protected Paint impl_cssGetStrokeInitialValue() {
439        return Color.BLACK;
440    }    
441    
442    /**
443     * Returns a string representation of this {@code Line} object.
444     * @return a string representation of this {@code Line} object.
445     */
446    @Override
447    public String toString() {
448        final StringBuilder sb = new StringBuilder("Line[");
449
450        String id = getId();
451        if (id != null) {
452            sb.append("id=").append(id).append(", ");
453        }
454
455        sb.append("startX=").append(getStartX());
456        sb.append(", startY=").append(getStartY());
457        sb.append(", endX=").append(getEndX());
458        sb.append(", endY=").append(getEndY());
459
460        Paint stroke = getStroke();
461        if (stroke != null) {
462            sb.append(", stroke=").append(stroke);
463            sb.append(", strokeWidth=").append(getStrokeWidth());
464        }
465
466        return sb.append("]").toString();
467    }
468}
469