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 java.util.List;
029
030import javafx.beans.property.ObjectProperty;
031import javafx.beans.property.ObjectPropertyBase;
032import javafx.collections.ListChangeListener.Change;
033import javafx.collections.ObservableList;
034import javafx.geometry.BoundingBox;
035import javafx.geometry.Bounds;
036import javafx.scene.paint.Color;
037
038import com.sun.javafx.collections.TrackableObservableList;
039import javafx.css.CssMetaData;
040import com.sun.javafx.geom.Path2D;
041import com.sun.javafx.scene.DirtyBits;
042import com.sun.javafx.scene.shape.PathUtils;
043import com.sun.javafx.sg.PGNode;
044import com.sun.javafx.sg.PGPath;
045import com.sun.javafx.tk.Toolkit;
046import java.util.Collection;
047import javafx.css.StyleableProperty;
048import javafx.scene.paint.Paint;
049
050/**
051 * The {@code Path} class represents a simple shape
052 * and provides facilities required for basic construction
053 * and management of a geometric path.  Example:
054 *
055<PRE>
056import javafx.scene.shape.*;
057
058Path path = new Path();
059
060MoveTo moveTo = new MoveTo();
061moveTo.setX(0.0f);
062moveTo.setY(0.0f);
063
064HLineTo hLineTo = new HLineTo();
065hLineTo.setX(70.0f);
066
067QuadCurveTo quadCurveTo = new QuadCurveTo();
068quadCurveTo.setX(120.0f);
069quadCurveTo.setY(60.0f);
070quadCurveTo.setControlX(100.0f);
071quadCurveTo.setControlY(0.0f);
072
073LineTo lineTo = new LineTo();
074lineTo.setX(175.0f);
075lineTo.setY(55.0f);
076
077ArcTo arcTo = new ArcTo();
078arcTo.setX(50.0f);
079arcTo.setY(50.0f);
080arcTo.setRadiusX(50.0f);
081arcTo.setRadiusY(50.0f);
082
083path.getElements().add(moveTo);
084path.getElements().add(hLineTo);
085path.getElements().add(quadCurveTo);
086path.getElements().add(lineTo);
087path.getElements().add(arcTo);
088
089</PRE>
090 */
091public class Path extends Shape {
092
093    private Path2D path2d = null;
094
095    /**
096     * Creates an empty instance of Path.
097     */
098    public Path() {
099    }
100
101    /**
102     * Creates a new instance of Path
103     * @param elements Elements of the Path
104     */
105    public Path(PathElement... elements) {
106        if (elements != null) {
107            this.elements.addAll(elements);
108        }
109    }
110    
111    /**
112     * Creates new instance of Path
113     * @param elements The collection of the elements of the Path
114     * @since 2.2
115     */
116    public Path(Collection<? extends PathElement> elements) {
117        if (elements != null) {
118            this.elements.addAll(elements);
119        }
120    }
121
122    static com.sun.javafx.sg.PGPath.FillRule toPGFillRule(FillRule rule) {
123        if (rule == FillRule.NON_ZERO) {
124            return PGPath.FillRule.NON_ZERO;
125        } else {
126            return PGPath.FillRule.EVEN_ODD;
127        }
128    }
129
130    {
131        // overriding default values for fill and stroke
132        // Set through CSS property so that it appears to be a UA style rather
133        // that a USER style so that fill and stroke can still be set from CSS.
134        ((StyleableProperty)fillProperty()).applyStyle(null, null);
135        ((StyleableProperty)strokeProperty()).applyStyle(null, Color.BLACK);
136    }
137
138    void markPathDirty() {
139        path2d = null;
140        impl_markDirty(DirtyBits.NODE_CONTENTS);
141        impl_geomChanged();
142    }
143
144    /**
145     * Defines the filling rule constant for determining the interior of the path.
146     * The value must be one of the following constants:
147     * {@code FillRile.EVEN_ODD} or {@code FillRule.NON_ZERO}.
148     * The default value is {@code FillRule.NON_ZERO}.
149     *
150     * @defaultValue FillRule.NON_ZERO
151     */
152    private ObjectProperty<FillRule> fillRule;
153
154    public final void setFillRule(FillRule value) {
155        if (fillRule != null || value != FillRule.NON_ZERO) {
156            fillRuleProperty().set(value);
157        }
158    }
159
160    public final FillRule getFillRule() {
161        return fillRule == null ? FillRule.NON_ZERO : fillRule.get();
162    }
163
164    public final ObjectProperty<FillRule> fillRuleProperty() {
165        if (fillRule == null) {
166            fillRule = new ObjectPropertyBase<FillRule>(FillRule.NON_ZERO) {
167
168                @Override
169                public void invalidated() {
170                    impl_markDirty(DirtyBits.NODE_CONTENTS);
171                    impl_geomChanged();
172                }
173
174                @Override
175                public Object getBean() {
176                    return Path.this;
177                }
178
179                @Override
180                public String getName() {
181                    return "fillRule";
182                }
183            };
184        }
185        return fillRule;
186    }
187
188    private boolean isPathValid;
189    /**
190     * Defines the array of path elements of this path.
191     *
192     * @defaultValue empty
193     */
194    private final ObservableList<PathElement> elements = new TrackableObservableList<PathElement>() {
195        @Override
196        protected void onChanged(Change<PathElement> c) {
197            List<PathElement> list = c.getList();
198            boolean firstElementChanged = false;
199            while (c.next()) {
200                List<PathElement> removed = c.getRemoved();
201                for (int i = 0; i < c.getRemovedSize(); ++i) {
202                    removed.get(i).removeNode(Path.this);
203                }
204                for (int i = c.getFrom(); i < c.getTo(); ++i) {
205                    list.get(i).addNode(Path.this);
206                }
207                firstElementChanged |= c.getFrom() == 0;
208            }
209
210            //Note: as ArcTo may create a various number of PathElements,
211            // we cannot count the number of PathElements removed (fast enough).
212            // Thus we can optimize only if some elements were added to the end
213            if (path2d != null) {
214                c.reset();
215                c.next();
216                // we just have to check the first change, as more changes cannot come after such change
217                if (c.getFrom() == c.getList().size() && !c.wasRemoved() && c.wasAdded()) {
218                    // some elements added
219                    for (int i = c.getFrom(); i < c.getTo(); ++i) {
220                        list.get(i).impl_addTo(path2d);
221                    }
222                } else {
223                    path2d = null;
224                }
225            }
226            if (firstElementChanged) {
227                isPathValid = impl_isFirstPathElementValid();
228            }
229
230            impl_markDirty(DirtyBits.NODE_CONTENTS);
231            impl_geomChanged();
232        }
233    };
234
235    /**
236     * Gets observable list of path elements of this path.
237     * @return Elements of this path
238     */
239    public final ObservableList<PathElement> getElements() { return elements; }
240
241    /**
242     * @treatAsPrivate implementation detail
243     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
244     */
245    @Deprecated
246    @Override
247    protected PGNode impl_createPGNode() {
248        return Toolkit.getToolkit().createPGPath();
249    }
250
251    /**
252     * @treatAsPrivate implementation detail
253     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
254     */
255    @Deprecated
256    public PGPath impl_getPGPath() {
257        return (PGPath)impl_getPGNode();
258    }
259
260    /**
261     * @treatAsPrivate implementation detail
262     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
263     */
264    @Deprecated
265    @Override
266        public Path2D impl_configShape() {
267        if (isPathValid) {
268            if (path2d == null) {
269                path2d = PathUtils.configShape(getElements(), getFillRule() == FillRule.EVEN_ODD);
270            } else {
271                path2d.setWindingRule(getFillRule() == FillRule.NON_ZERO ?
272                                      Path2D.WIND_NON_ZERO : Path2D.WIND_EVEN_ODD);
273            }
274            return path2d;
275        } else {
276            return new Path2D();
277        }
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    protected Bounds impl_computeLayoutBounds() {
287       if (isPathValid) {
288           return super.impl_computeLayoutBounds();
289       }
290       return new BoundingBox(0, 0, -1, -1); //create empty bounds
291    }
292
293    private boolean impl_isFirstPathElementValid() {
294        ObservableList<PathElement> _elements = getElements();
295        if (_elements != null && _elements.size() > 0) {
296            PathElement firstElement = _elements.get(0);
297            if (!firstElement.isAbsolute()) {
298                System.err.printf("First element of the path can not be relative. Path: %s\n", this);
299                return false;
300            } else if (firstElement instanceof MoveTo) {
301                return true;
302            } else {
303                System.err.printf("Missing initial moveto in path definition. Path: %s\n", this);
304                return false;
305            }
306        }
307        return true;
308    }
309
310    /**
311     * @treatAsPrivate implementation detail
312     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
313     */
314    @Deprecated
315    @Override
316    public void impl_updatePG() {
317        super.impl_updatePG();
318
319        if (impl_isDirty(DirtyBits.NODE_CONTENTS)) {
320            PGPath peer = impl_getPGPath();
321            if (peer.acceptsPath2dOnUpdate()) {
322                peer.updateWithPath2d(impl_configShape());
323            } else {
324                peer.reset();
325                if (isPathValid) {
326                    peer.setFillRule(toPGFillRule(getFillRule()));
327                    for (final PathElement elt : getElements()) {
328                        elt.addTo(peer);
329                    }
330                    peer.update();
331                }
332            }
333        }
334    }
335
336    /***************************************************************************
337     *                                                                         *
338     *                         Stylesheet Handling                             *
339     *                                                                         *
340     **************************************************************************/
341
342    /** 
343     * Some sub-class of Shape, such as {@link Line}, override the
344     * default value for the {@link Shape#fill} property. This allows
345     * CSS to get the correct initial value.
346     * @treatAsPrivate Implementation detail
347     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
348     */
349    @Deprecated
350    protected Paint impl_cssGetFillInitialValue() {
351        return null;
352    }    
353    
354    /** 
355     * Some sub-class of Shape, such as {@link Line}, override the
356     * default value for the {@link Shape#stroke} property. This allows
357     * CSS to get the correct initial value.
358     * @treatAsPrivate Implementation detail
359     * @deprecated This is an internal API that is not intended for use and will be removed in the next version
360     */
361    @Deprecated
362    protected Paint impl_cssGetStrokeInitialValue() {
363        return Color.BLACK;
364    }    
365
366    /**
367     * Returns a string representation of this {@code Path} object.
368     * @return a string representation of this {@code Path} object.
369     */
370    @Override
371    public String toString() {
372        final StringBuilder sb = new StringBuilder("Path[");
373
374        String id = getId();
375        if (id != null) {
376            sb.append("id=").append(id).append(", ");
377        }
378
379        sb.append("elements=").append(getElements());
380
381        sb.append(", fill=").append(getFill());
382        sb.append(", fillRule=").append(getFillRule());
383
384        Paint stroke = getStroke();
385        if (stroke != null) {
386            sb.append(", stroke=").append(stroke);
387            sb.append(", strokeWidth=").append(getStrokeWidth());
388        }
389
390        return sb.append("]").toString();
391    }
392}