/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.display.primitive; import java.beans.PropertyChangeEvent; import java.util.Collection; import java.util.Collections; import java.util.EventObject; import java.util.List; import java.util.Objects; import javax.swing.event.EventListenerList; import org.apache.sis.measure.NumberRange; import static org.apache.sis.util.ArgumentChecks.ensureNonNull; import org.apache.sis.util.Classes; import org.geotoolkit.display.DisplayElement; import org.geotoolkit.display.SceneVisitor; import org.geotoolkit.display.canvas.Canvas; import org.geotoolkit.gui.swing.tree.Trees; import org.geotoolkit.util.collection.CollectionChangeEvent; import org.geotoolkit.util.collection.CollectionChangeListener; import org.geotoolkit.util.collection.NotifiedCheckedList; import org.opengis.display.primitive.Graphic; /** * A scene node is an element in the graphic container. * * @author Johann Sorel (Geomatys) */ public class SceneNode extends DisplayElement implements Graphic { /** * The name of the {@linkplain PropertyChangeEvent property change event} fired when the * {@linkplain SceneNode#getParent node parent} changed. */ public static final String PARENT_KEY = "parent"; /** * The name of the {@linkplain PropertyChangeEvent property change event} fired when the * {@linkplain SceneNode#getVisible node visibility} changed. */ public static final String VISIBLE_KEY = "visible"; /** * The name of the {@linkplain PropertyChangeEvent property change event} fired when the * {@linkplain SceneNode#getName node name} changed. */ public static final String NAME_KEY = "name"; private final List<SceneNode> children; protected SceneNode parent; protected boolean visible = true; protected String name = ""; /** * The canvas that own this scene node, or {@code null} if none. */ protected final Canvas canvas; /** * Used to listen to children events and push them back to the root. */ private CollectionChangeListener childListener; /** * Create a default scene node which allows children. */ public SceneNode(Canvas canvas) { this(canvas, true); } /** * * @param allowChildren indicate this node allows children. */ public SceneNode(final Canvas canvas, final boolean allowChildren) { ensureNonNull("canvas", canvas); this.canvas = canvas; if(!allowChildren){ children = Collections.EMPTY_LIST; }else{ childListener = new CollectionChangeListener() { @Override public void collectionChange(CollectionChangeEvent event) { } }; children = new NotifiedCheckedList<SceneNode>(SceneNode.class, 0) { @Override protected void notifyAdd(SceneNode item, int index) { if (item != null) { //remove node from previous parent if any SceneNode parent = item.getParent(); if(parent != null){ parent.getChildren().remove(item); } //set this as parent item.setParent(SceneNode.this); item.addChildrenListener(childListener); } //fire event fireChildrenChange(CollectionChangeEvent.ITEM_ADDED, Collections.singleton(item), NumberRange.create(index, true, index, true)); } @Override protected void notifyAdd(Collection<? extends SceneNode> items, NumberRange<Integer> range) { if (items != null) { for(SceneNode item : items){ if (item != null) { //remove node from previous parent if any SceneNode parent = item.getParent(); if(parent != null){ parent.getChildren().remove(item); } //set this as parent item.setParent(SceneNode.this); item.addChildrenListener(childListener); } } } //fire event fireChildrenChange(CollectionChangeEvent.ITEM_ADDED,items, range); } @Override protected void notifyChange(SceneNode oldItem, SceneNode newItem, int index) { if (oldItem != null) { //remove parent in old item oldItem.setParent(null); oldItem.removeChildrenListener(childListener); } if (newItem != null) { //remove node from previous parent if any final SceneNode parent = newItem.getParent(); if(parent != null){ parent.getChildren().remove(newItem); } //set this as parent newItem.setParent(SceneNode.this); newItem.addChildrenListener(childListener); } //fire event fireChildrenChange(CollectionChangeEvent.ITEM_CHANGED, Collections.singleton(newItem), NumberRange.create(index, true, index, true)); } @Override protected void notifyRemove(SceneNode item, int index) { if (item != null) { //remove parent on item item.setParent(null); item.removeChildrenListener(childListener); } //fire event fireChildrenChange(CollectionChangeEvent.ITEM_REMOVED, Collections.singleton(item), NumberRange.create(index, true, index, true)); } @Override protected void notifyRemove(Collection<? extends SceneNode> items, NumberRange<Integer> range) { if (items != null) { for(SceneNode item : items){ if (item != null) { //remove parent on item item.setParent(null); item.removeChildrenListener(childListener); } } } //fire event fireChildrenChange(CollectionChangeEvent.ITEM_REMOVED,items, range); } }; } } /** * If this display object is contained in a canvas, returns the canvas that own it. * Otherwise, returns {@code null}. * * @return Canvas, The canvas that this graphic listen to. */ public Canvas getCanvas() { return canvas; } /** * Get the parent scene node. * @return SceneNode , can be null */ public SceneNode getParent() { return parent; } /** * Set this node parent. * @param parent , can be null */ private void setParent(SceneNode parent){ if (Objects.equals(parent, this.parent)) return; final SceneNode old = this.parent; this.parent = parent; firePropertyChange(PARENT_KEY, old, parent); } /** * Get scene node children, modifiable list. * * @return List, never null, can be empty */ public List<SceneNode> getChildren(){ return children; } @Override public boolean isVisible() { return visible; } @Override public void setVisible(boolean visible) { if (visible == this.visible) return; this.visible = visible; firePropertyChange(VISIBLE_KEY, !visible, visible); } public String getName() { return name; } public void setName(String name) { if (Objects.equals(name, this.name)) return; final String old = this.name; this.name = name; firePropertyChange(NAME_KEY, old, name); } @Override public String toString() { return Trees.toString(name+" ("+Classes.getShortClassName(this)+")", children); } @Override public void dispose() { for(SceneNode child : getChildren()){ child.dispose(); } } public void addChildrenListener(CollectionChangeListener listener){ getListenerList(true).add(CollectionChangeListener.class, listener); } public void removeChildrenListener(CollectionChangeListener listener){ getListenerList(true).remove(CollectionChangeListener.class, listener); } /** * Accepts a visitor. */ public Object accept(SceneVisitor visitor, Object extraData){ return visitor.visit(this, extraData); } protected void fireChildrenChange(final int type, final SceneNode item, final NumberRange<Integer> range, final EventObject orig) { final EventListenerList lst = getListenerList(false); if(lst==null) return; final CollectionChangeListener[] lists = lst.getListeners(CollectionChangeListener.class); if(lists.length==0) return; final CollectionChangeEvent<SceneNode> event = new CollectionChangeEvent<>(this, item, type, range, orig); for (CollectionChangeListener listener : lists){ listener.collectionChange(event); } } protected void fireChildrenChange(final int type, final Collection<? extends SceneNode> item, final NumberRange<Integer> range){ final EventListenerList lst = getListenerList(false); if(lst==null) return; final CollectionChangeListener[] lists = lst.getListeners(CollectionChangeListener.class); if(lists.length==0) return; final CollectionChangeEvent<SceneNode> event = new CollectionChangeEvent<>(this,item,type,range, null); for (CollectionChangeListener listener : lists){ listener.collectionChange(event); } } }