/**
* This file is part of VisiCut.
* Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de>
* RWTH Aachen University - 52062 Aachen, Germany
*
* VisiCut 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, either version 3 of the License, or
* (at your option) any later version.
*
* VisiCut 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with VisiCut. If not, see <http://www.gnu.org/licenses/>.
**/
package com.t_oster.visicut.gui.mapping;
import com.t_oster.visicut.misc.Helper;
import com.t_oster.visicut.model.graphicelements.GraphicObject;
import com.t_oster.visicut.model.graphicelements.GraphicSet;
import com.t_oster.visicut.model.mapping.FilterSet;
import com.t_oster.visicut.model.mapping.Mapping;
import com.t_oster.visicut.model.mapping.MappingFilter;
import java.awt.Color;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/**
*
* @author Thomas Oster <thomas.oster@rwth-aachen.de>
*/
public class MappingJTree extends JTree implements TreeModel, TreeSelectionListener
{
private Color mappedBackgroundColor = new Color(253, 248, 183);
private String bgHtml = Helper.toHtmlRGB(mappedBackgroundColor);
protected FilterSet selectedFilterSet = null;
public static final String PROP_SELECTEDFILTERSET = "selectedFilterSet";
/**
* Get the value of selectedFilterSet
*
* @return the value of selectedFilterSet
*/
public FilterSet getSelectedFilterSet()
{
return selectedFilterSet;
}
/**
* Set the value of selectedFilterSet
* and fires a property change event
* this does NOT alter the real selected value
* of the JTree, except if the value is null,
* then the selection is cleared
*
* @param selectedFilterSet new value of selectedFilterSet
*/
public void setSelectedFilterSet(FilterSet selectedFilterSet)
{
FilterSet oldSelectedFilterSet = this.selectedFilterSet;
this.selectedFilterSet = selectedFilterSet;
firePropertyChange(PROP_SELECTEDFILTERSET, oldSelectedFilterSet, selectedFilterSet);
}
/**
* Selects the path which leads to the given filter set,
* or clears the selection if the path is not in the tree
* @param fs
*/
public void representFilterSet(FilterSet fs)
{
if (fs == null)
{
this.setSelectionPath(new TreePath(new Object[]{dummyRoot, EVERYTHING_ELSE}));
}
else if (fs.size() == 0)
{
this.setSelectionPath(new TreePath(new Object[]{dummyRoot, root}));
}
else
{
List<Object> path = new LinkedList<Object>();
path.add(dummyRoot);
path.add(root);
Object current = root;
for (MappingFilter f : fs)
{
boolean attributeFound = false;
for (Object c : getChildren(current))
{
if (c instanceof AttributeNode && ((AttributeNode) c).getAttribute().equals(f.getAttribute()))
{
current = c;
path.add(c);
attributeFound = true;
break;
}
}
if (attributeFound)
{
boolean valueFound = false;
for(Object c : getChildren(current))
{
if (c instanceof FilterSetNode && ((FilterSetNode) c).getLast().equals(f))
{
//TODO: need to check inverted and compare???
valueFound = true;
current = c;
path.add(c);
break;
}
}
if (!valueFound)
{
//tree does not contain selected value
path = null;
break;
}
}
else
{
//tree doesnt contain attribute
path = null;
break;
}
}
if (path != null)
{
this.setSelectionPath(new TreePath(path.toArray()));
}
else
{
this.clearSelection();
}
}
}
public void valueChanged(TreeSelectionEvent evt)
{
if (evt.getNewLeadSelectionPath() != null && evt.getNewLeadSelectionPath().getPathCount() >= 1)
{
Object selected = evt.getNewLeadSelectionPath().getLastPathComponent();
if (selected == null || selected == EVERYTHING_ELSE)
{
this.setSelectedFilterSet(null);
}
else if (selected instanceof FilterSet)
{
this.setSelectedFilterSet(((FilterSet) selected).clone());
}
}
}
private class FilterSetNode extends FilterSet
{
private List<AttributeNode> children;
public List<AttributeNode> getChildren()
{
if (children == null)
{
children = new LinkedList<AttributeNode>();
if (MappingJTree.this.getGraphicObjects() != null)
{
List<GraphicObject> gos = this.getMatchingObjects(MappingJTree.this.getGraphicObjects());
List<String> visitedAttributes = new LinkedList<String>();
for (GraphicObject g : gos)
{
for (String attribute : g.getAttributes())
{
if (!visitedAttributes.contains(attribute))
{
visitedAttributes.add(attribute);
AttributeNode node = new AttributeNode();
node.addAll(this);
node.setAttribute(attribute);
if (!children.contains(node) && node.getChildren().size() > 1)
{
children.add(node);
}
}
}
}
}
}
return children;
}
}
private class AttributeNode extends FilterSet
{
private String attribute;
private List<FilterSetNode> children;
public void setAttribute(String attribute)
{
this.attribute = attribute;
}
String getAttribute()
{
return attribute;
}
public List<FilterSet> getChildren()
{
if (children == null)
{
children = new LinkedList<FilterSetNode>();
GraphicSet gos = this.getMatchingObjects(MappingJTree.this.getGraphicObjects());
List<Object> visitedValues = new LinkedList<Object>();
for (Object value : gos.getAttributeValues(attribute))
{
if (!visitedValues.contains(value))
{
visitedValues.add(value);
List<MappingFilter> possibleFilters = new ArrayList<MappingFilter>(4);
//create all 4 possible filters
MappingFilter pf = new MappingFilter(attribute, value);
possibleFilters.add(pf);
pf = new MappingFilter(attribute, value);
pf.setInverted(true);
possibleFilters.add(pf);
if (value instanceof Number)
{
pf = new MappingFilter(attribute, value);
pf.setCompare(true);
possibleFilters.add(pf);
pf = new MappingFilter(attribute, value);
pf.setInverted(true);
pf.setCompare(true);
possibleFilters.add(pf);
}
for (MappingFilter f : possibleFilters)
{
int newrest = f.getMatchingElements(gos).size();
//Check if filter makes a difference
if (newrest != 0 && newrest != gos.size())
{
FilterSetNode node = new FilterSetNode();
node.addAll(this);
node.add(f);
if (!children.contains(node))
{
children.add(node);
}
}
}
}
}
}
return (List) children;
}
@Override
public String toString()
{
return (this.isEmpty() ? GraphicSet.translateAttVal("WHERE") : GraphicSet.translateAttVal("AND")) + " " + GraphicSet.translateAttVal(attribute);
}
@Override
public boolean equals(Object o)
{
if (o instanceof AttributeNode)
{
return ((AttributeNode) o).attribute.equals(attribute) && super.equals(o);
}
return super.equals(o);
}
@Override
public int hashCode()
{
int hash = 7;
hash = 89 * hash + (this.attribute != null ? this.attribute.hashCode() : 0);
hash = 89 * hash + (this.children != null ? this.children.hashCode() : 0);
return hash;
}
}
public MappingJTree()
{
this.setModel(this);
this.setRootVisible(false);
this.setCellRenderer(new DefaultTreeCellRenderer()
{
@Override
public Component getTreeCellRendererComponent(JTree jtree, Object o, boolean bln, boolean bln1, boolean bln2, int i, boolean bln3)
{
Component c = super.getTreeCellRendererComponent(jtree, o, bln, bln1, bln2, i, bln3);
if (c instanceof JLabel)
{
JLabel l = (JLabel) c;
if (o == EVERYTHING_ELSE)
{
l.setText(GraphicSet.translateAttVal("EVERYTHING_ELSE"));
}
else if (o instanceof FilterSet)
{
FilterSet fs = (FilterSet) o;
if (fs instanceof FilterSetNode)
{
MappingFilter f = fs.peekLast();
if (f != null)
{
if (f.getValue() instanceof Color)
{
l.setText("<html><table><tr><td>" + (f.isInverted() ? GraphicSet.translateAttVal("IS NOT") : GraphicSet.translateAttVal("IS")) + "</td><td border=1 bgcolor=" + Helper.toHtmlRGB((Color) f.getValue()) + "> </td></tr></table></html>");
}
else
{
l.setText(f.getValueString());
}
}
}
}
}
return c;
}
});
this.getSelectionModel().addTreeSelectionListener(this);
}
protected GraphicSet graphicObjects = null;
/**
* Get the value of graphicObjects
*
* @return the value of graphicObjects
*/
public GraphicSet getGraphicObjects()
{
return graphicObjects;
}
/**
* Set the value of graphicObjects
*
* @param graphicObjects new value of graphicObjects
*/
public void setGraphicObjects(GraphicSet graphicObjects)
{
this.graphicObjects = graphicObjects;
this.root = new FilterSetNode();
this.roots[0] = root;
this.valueForPathChanged(new TreePath(new Object[]
{
this.getRoot()
}), this.getRoot());
}
private String dummyRoot = "DUMMY";
private FilterSet root = new FilterSetNode();
public Object getRoot()
{
return dummyRoot;
}
public Object getChild(Object o, int i)
{
return getChildren(o).get(i);
}
private String EVERYTHING_ELSE = "dummy";
private Object[] roots = new Object[]{root, EVERYTHING_ELSE};
private List getChildren(Object o)
{
if (o == dummyRoot)
{
return Arrays.asList(roots);
}
else if (o instanceof FilterSetNode)
{
return ((FilterSetNode) o).getChildren();
}
else if (o instanceof AttributeNode)
{
return ((AttributeNode) o).getChildren();
}
else
{
return new LinkedList();
}
}
public int getChildCount(Object o)
{
return getChildren(o).size();
}
public boolean isLeaf(Object o)
{
return this.getChildCount(o) == 0;
}
public void valueForPathChanged(TreePath tp, Object o)
{
for (TreeModelListener l : listeners)
{
l.treeStructureChanged(new TreeModelEvent(o, tp));
}
//throw new UnsupportedOperationException("Not supported yet.");
}
public int getIndexOfChild(Object parent, Object child)
{
if (parent == null || child == null)
{
return -1;
}
int i = 0;
for (Object o : this.getChildren(parent))
{
if (o.equals(child))
{
return i;
}
i++;
}
return -1;
}
private List<TreeModelListener> listeners = new LinkedList<TreeModelListener>();
public void addTreeModelListener(TreeModelListener tl)
{
listeners.add(tl);
}
public void removeTreeModelListener(TreeModelListener tl)
{
listeners.remove(tl);
}
protected List<GraphicObject> matchingElements = null;
public static final String PROP_MATCHINGELEMENTS = "matchingElements";
protected List<Mapping> mappings = null;
/**
* Get the value of mappings
*
* @return the value of mappings
*/
public List<Mapping> getMappings()
{
return mappings;
}
/**
* Set the value of mappings
*
* @param mappings new value of mappings
*/
public void setMappings(List<Mapping> mappings)
{
this.mappings = mappings;
}
}