/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2014, 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.gui.swing.style;
import java.beans.PropertyChangeEvent;
import java.util.Collection;
import java.util.EventObject;
import java.util.List;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import org.geotoolkit.style.FeatureTypeStyleListener;
import org.geotoolkit.style.MutableFeatureTypeStyle;
import org.geotoolkit.style.MutableRule;
import org.geotoolkit.style.MutableStyle;
import org.geotoolkit.style.RuleListener;
import org.geotoolkit.style.StyleListener;
import org.geotoolkit.style.StyleUtilities;
import org.geotoolkit.util.collection.CollectionChangeEvent;
import org.opengis.style.FeatureTypeStyle;
import org.opengis.style.Rule;
import org.opengis.style.SemanticType;
import org.opengis.style.Style;
import org.opengis.style.Symbolizer;
import org.opengis.util.GenericName;
/**
*
* @author Johann Sorel (Geomatys)
*/
public final class StyleTreeModel implements TreeModel, StyleListener, FeatureTypeStyleListener, RuleListener {
// style elements listeners
private final StyleListener.Weak weakStyleListener = new StyleListener.Weak(null, this);
private final FeatureTypeStyleListener.Weak weakFTSListener = new FeatureTypeStyleListener.Weak(null, this);
private final RuleListener.Weak weakRuleListener = new RuleListener.Weak(null, this);
private final EventListenerList listeners = new EventListenerList();
private Object root;
public StyleTreeModel(Object root) {
setRoot(root);
}
/**
* Set the model Style
* @param style , can't be null
*/
public void setRoot(final Object style) {
weakStyleListener.dispose();
weakFTSListener.dispose();
weakRuleListener.dispose();
this.root = style;
if(this.root != null){
if(style instanceof MutableStyle){
weakStyleListener.registerSource((MutableStyle)style);
}else if(style instanceof MutableFeatureTypeStyle){
weakFTSListener.registerSource((MutableFeatureTypeStyle)style);
}else if(style instanceof MutableRule){
weakRuleListener.registerSource((MutableRule)style);
}
fireStructureChanged(new TreeModelEvent(this, new TreePath(root)));
}
}
@Override
public Object getRoot() {
return root;
}
@Override
public Object getChild(Object parent, int index) {
if(parent instanceof Style){
return ((Style)parent).featureTypeStyles().get(index);
}else if(parent instanceof FeatureTypeStyle){
return ((FeatureTypeStyle)parent).rules().get(index);
}else if(parent instanceof Rule){
return ((Rule)parent).symbolizers().get(index);
}else{
return -1;
}
}
public void removeChild(Object parent, Object child) {
if(parent instanceof Style){
((Style)parent).featureTypeStyles().remove(child);
}else if(parent instanceof FeatureTypeStyle){
((FeatureTypeStyle)parent).rules().remove(child);
}else if(parent instanceof Rule){
((Rule)parent).symbolizers().remove(child);
}
}
/**
* duplicate a node
*/
public void duplicateNode(Object parent, Object child) {
//style events will refresh the style
if (child instanceof MutableFeatureTypeStyle) {
final MutableFeatureTypeStyle fts = StyleUtilities.copy((MutableFeatureTypeStyle) child);
final int index = getIndexOfChild((MutableStyle) parent, (MutableFeatureTypeStyle) child) + 1;
((MutableStyle) parent).featureTypeStyles().add(index, fts);
} else if (child instanceof MutableRule) {
final MutableRule rule = StyleUtilities.copy((MutableRule) child);
final int index = getIndexOfChild((MutableFeatureTypeStyle) parent, (MutableRule) child) + 1;
((MutableFeatureTypeStyle) parent).rules().add(index, rule);
} else if (child instanceof Symbolizer) {
//no need to copy symbolizer, they are immutable
final Symbolizer symbol = (Symbolizer) child;
final int index = getIndexOfChild((MutableRule) parent, (Symbolizer) child) + 1;
((MutableRule) parent).symbolizers().add(index, symbol);
}
}
@Override
public int getChildCount(Object parent) {
if(parent instanceof Style){
return ((Style)parent).featureTypeStyles().size();
}else if(parent instanceof FeatureTypeStyle){
return ((FeatureTypeStyle)parent).rules().size();
}else if(parent instanceof Rule){
return ((Rule)parent).symbolizers().size();
}else{
return 0;
}
}
@Override
public boolean isLeaf(Object node) {
return node instanceof Symbolizer;
}
@Override
public void valueForPathChanged(TreePath path, Object newValue) {
}
@Override
public int getIndexOfChild(Object parent, Object child) {
if(parent instanceof Style){
return ((Style)parent).featureTypeStyles().indexOf(child);
}else if(parent instanceof FeatureTypeStyle){
return ((FeatureTypeStyle)parent).rules().indexOf(child);
}else if(parent instanceof Rule){
return ((Rule)parent).symbolizers().indexOf(child);
}else{
return -1;
}
}
@Override
public void addTreeModelListener(TreeModelListener l) {
listeners.add(TreeModelListener.class, l);
}
@Override
public void removeTreeModelListener(TreeModelListener l) {
listeners.remove(TreeModelListener.class, l);
}
// style element events
@Override
public void featureTypeStyleChange(CollectionChangeEvent<MutableFeatureTypeStyle> event) {
fireEvent(new TreePath(root), event);
}
@Override
public void propertyChange(PropertyChangeEvent event) {
fireEvent(new TreePath(root), event);
}
@Override
public void ruleChange(CollectionChangeEvent<MutableRule> event) {
fireEvent(new TreePath(root), event);
}
@Override
public void featureTypeNameChange(CollectionChangeEvent<GenericName> event) {
fireEvent(new TreePath(root), event);
}
@Override
public void semanticTypeChange(CollectionChangeEvent<SemanticType> event) {
}
@Override
public void symbolizerChange(CollectionChangeEvent<Symbolizer> event) {
fireEvent(new TreePath(root), event);
}
private void fireNodesChanged(TreeModelEvent event){
final TreeModelListener[] lsts = listeners.getListeners(TreeModelListener.class);
for(TreeModelListener lst : lsts){
lst.treeNodesChanged(event);
}
}
private void fireNodesInserted(TreeModelEvent event){
final TreeModelListener[] lsts = listeners.getListeners(TreeModelListener.class);
for(TreeModelListener lst : lsts){
lst.treeNodesInserted(event);
}
}
private void fireNodesRemoved(TreeModelEvent event){
final TreeModelListener[] lsts = listeners.getListeners(TreeModelListener.class);
for(TreeModelListener lst : lsts){
lst.treeNodesRemoved(event);
}
}
private void fireStructureChanged(TreeModelEvent event){
final TreeModelListener[] lsts = listeners.getListeners(TreeModelListener.class);
for(TreeModelListener lst : lsts){
lst.treeStructureChanged(event);
}
}
private void fireEvent(TreePath path, EventObject event){
if(event instanceof CollectionChangeEvent){
final CollectionChangeEvent cevent = (CollectionChangeEvent) event;
final int type = cevent.getType();
//we might can events on featuretype names or semantics
//do not propagante those
final Collection col = cevent.getItems();
if(col==null || col.isEmpty()) return;
final Object candidate = col.iterator().next();
if(!(candidate instanceof Rule
|| candidate instanceof Symbolizer
|| candidate instanceof FeatureTypeStyle)){
return;
}
if(type == CollectionChangeEvent.ITEM_ADDED){
final Object[] objs = cevent.getItems().toArray();
final int[] indices = new int[objs.length];
indices[0] = (int) cevent.getRange().getMinDouble();
for(int i=1;i<indices.length;i++){
indices[i] = indices[i-1]+1;
}
final TreeModelEvent te = new TreeModelEvent(this, path, indices, objs);
fireNodesInserted(te);
}else if(type == CollectionChangeEvent.ITEM_REMOVED){
final Object[] objs = cevent.getItems().toArray();
final int[] indices = new int[objs.length];
indices[0] = (int) cevent.getRange().getMinDouble();
for(int i=1;i<indices.length;i++){
indices[i] = indices[i-1]+1;
}
final TreeModelEvent te = new TreeModelEvent(this, path, indices, objs);
fireNodesRemoved(te);
}else if(type == CollectionChangeEvent.ITEM_CHANGED){
if(cevent.getChangeEvent()!=null){
//children event
final Object[] objs = cevent.getItems().toArray();
path = path.pathByAddingChild(objs[0]);
fireEvent(path, cevent.getChangeEvent());
}else{
//changed the object at given index
final Object[] objs = cevent.getItems().toArray();
final int[] indices = new int[objs.length];
indices[0] = (int) cevent.getRange().getMinDouble();
for(int i=1;i<indices.length;i++){
indices[i] = indices[i-1]+1;
}
final TreeModelEvent te = new TreeModelEvent(this, path, indices, objs);
fireNodesRemoved(te);
//the change event contain the old element
final Object parent = path.getLastPathComponent();
if(parent instanceof Style){
final List lst = ((Style)parent).featureTypeStyles();
for(int i=0;i<indices.length;i++)objs[i]=lst.get(indices[i]);
}else if(parent instanceof FeatureTypeStyle){
final List lst = ((FeatureTypeStyle)parent).rules();
for(int i=0;i<indices.length;i++)objs[i]=lst.get(indices[i]);
}else if(parent instanceof Rule){
final List lst = ((Rule)parent).symbolizers();
for(int i=0;i<indices.length;i++)objs[i]=lst.get(indices[i]);
}
fireNodesInserted(te);
}
}
}else if(event instanceof PropertyChangeEvent){
final TreePath parentPath = path.getParentPath();
if(parentPath==null){
//root changed
fireNodesChanged(new TreeModelEvent(this, path));
}else{
//child node changed
final Object[] objs = {path.getLastPathComponent()};
final Object parent = parentPath.getLastPathComponent();
final int[] indices = {getIndexOfChild(parent, objs[0])};
fireNodesChanged(new TreeModelEvent(this, parentPath,indices,objs));
}
}
}
}