/*
* EquipmentTreeTableModel.java
* Copyright 2011 Connor Petty <cpmeister@users.sourceforge.net>
*
* 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; either
* version 2.1 of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Created on Jan 29, 2011, 1:57:14 PM
*/
package pcgen.gui2.tabs.equip;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreePath;
import pcgen.facade.core.CharacterFacade;
import pcgen.facade.core.EquipmentSetFacade;
import pcgen.facade.core.EquipmentSetFacade.EquipNode;
import pcgen.facade.core.EquipmentSetFacade.EquipmentTreeEvent;
import pcgen.facade.core.EquipmentSetFacade.EquipmentTreeListener;
import pcgen.facade.util.ListFacade;
import pcgen.facade.util.event.ListEvent;
import pcgen.facade.util.event.ListListener;
import pcgen.gui2.util.treetable.TreeTableModel;
import pcgen.gui2.util.treetable.TreeTableNode;
import pcgen.util.CollectionMaps;
import pcgen.util.ListMap;
/**
* The model backing the selected table on the equipping tab. This controls the
* tree structure showing the equipment by its equipped location for a
* particular character and equipment set.
*
* <br>
*
* @author Connor Petty <cpmeister@users.sourceforge.net>
*/
public class EquipmentTreeTableModel implements TreeTableModel, ListListener<EquipNode>, EquipmentTreeListener
{
private EventListenerList listenerList = new EventListenerList();
private CharacterFacade character;
private EquipmentSetFacade equipSet;
private Object root = new Object();
private ListMap<EquipNode, EquipNode, List<EquipNode>> pathMap;
private List<EquipNode> bodySlotNodes;
private Comparator<EquipNode> pathComparator = new NodeComparator();
public EquipmentTreeTableModel(CharacterFacade character, EquipmentSetFacade equipSet)
{
this.character = character;
this.equipSet = equipSet;
pathMap = CollectionMaps.createListMap(HashMap.class, ArrayList.class);
bodySlotNodes = new ArrayList<>();
initPathMap();
equipSet.getNodes().addListListener(this);
equipSet.addEquipmentTreeListener(this);
}
private void initPathMap()
{
ListFacade<EquipNode> equipNodes = equipSet.getNodes();
for (EquipNode equipNode : equipNodes)
{
EquipNode parent = equipNode.getParent();
while (parent != null && !pathMap.containsValue(parent, equipNode))
{
addNode(parent, equipNode);
equipNode = parent;
parent = equipNode.getParent();
}
if (parent == null && !bodySlotNodes.contains(equipNode))
{
addBodyNode(equipNode);
}
}
}
@Override
public boolean isCellEditable(Object node, int column)
{
return column == 0;
}
@Override
public Class<?> getColumnClass(int column)
{
switch (column)
{
case 0:
return TreeTableNode.class;
case 1:
case 2:
return String.class;
case 3:
return Integer.class;
case 4:
return Float.class;
default:
return Object.class;
}
}
@Override
public int getColumnCount()
{
return 5;
}
@Override
public String getColumnName(int column)
{
return null;
}
@Override
public void setValueAt(Object aValue, Object node, int column)
{
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Object getValueAt(Object node, int column)
{
EquipNode pathNode = (EquipNode) node;
if (column == 0)
{
return pathNode;
}
switch (pathNode.getNodeType())
{
case BODY_SLOT:
switch (column)
{
case 1:
return "Type";
case 2:
return "Located";
case 3:
return "Qty";
case 4:
return "Wgt";
}
case PHANTOM_SLOT:
switch (column)
{
case 2:
return equipSet.getLocation(pathNode);
default:
return null;
}
case EQUIPMENT:
switch (column)
{
case 1:
return pathNode.getEquipment().getTypes()[0];
case 2:
return equipSet.getLocation(pathNode);
case 3:
return equipSet.getQuantity(pathNode);
case 4:
return character.getInfoFactory().getWeight(pathNode.getEquipment());
}
default:
return null;
}
}
@Override
public Object getRoot()
{
return root;
}
@Override
public Object getChild(Object parent, int index)
{
if (parent == root)
{
return bodySlotNodes.get(index);
}
else
{
return pathMap.get(parent, index);
}
}
@Override
public int getChildCount(Object parent)
{
if (parent == root)
{
return bodySlotNodes.size();
}
else
{
return pathMap.size(parent);
}
}
@Override
public boolean isLeaf(Object node)
{
if (root == node)
{
return false;
}
return !pathMap.containsKey(node);
}
@Override
public void valueForPathChanged(TreePath path, Object newValue)
{
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public int getIndexOfChild(Object parent, Object child)
{
if (parent == root)
{
EquipNode path = (EquipNode) child;
return bodySlotNodes.indexOf(path);
}
else
{
return pathMap.indexOf(parent, child);
}
}
@Override
public void addTreeModelListener(TreeModelListener l)
{
listenerList.add(TreeModelListener.class, l);
}
@Override
public void removeTreeModelListener(TreeModelListener l)
{
listenerList.remove(TreeModelListener.class, l);
}
private void addBodyNode(EquipNode bodyNode)
{
int insertion_index = Collections.binarySearch(bodySlotNodes, bodyNode, pathComparator);
bodySlotNodes.add(-(insertion_index + 1), bodyNode);
}
private int addNode(EquipNode parent, EquipNode child)
{
List<EquipNode> children = pathMap.get(parent);
if (children == null)
{
children = Collections.emptyList();
}
int insertion_index = 1 + Collections.binarySearch(children, child, pathComparator);
if (insertion_index < 0)
{
// The item wasn't already in the list so the search gave us a negative index of where to add the item.
insertion_index *= -1;
}
pathMap.add(parent, insertion_index, child);
return insertion_index;
}
@Override
public void elementAdded(ListEvent<EquipNode> e)
{
EquipNode child = e.getElement();
EquipNode parent = child.getParent();
int index = addNode(parent, child);
fireTreeNodesInserted(this, getPathToRoot(parent), new int[]
{
index
}, new Object[]
{
child
});
}
@Override
public void elementRemoved(ListEvent<EquipNode> e)
{
EquipNode child = e.getElement();
EquipNode parent = child.getParent();
List<EquipNode> children = pathMap.get(parent);
int index = children.indexOf(child);
pathMap.remove(parent, index);
fireTreeNodesRemoved(this, getPathToRoot(parent), new int[]
{
index
}, new Object[]
{
child
});
}
@Override
public void elementsChanged(ListEvent<EquipNode> e)
{
pathMap.clear();
initPathMap();
fireTreeStructureChanged(this, new Object[]
{
root
}, null, null);
}
@Override
public void elementModified(ListEvent<EquipNode> e)
{
}
@Override
public void quantityChanged(EquipmentTreeEvent e)
{
EquipNode child = e.getNode();
EquipNode parent = child.getParent();
List<EquipNode> children = pathMap.get(parent);
int index = Collections.binarySearch(children, child, pathComparator);
fireTreeNodesChanged(this, getPathToRoot(parent), new int[]
{
index
}, new Object[]
{
child
});
}
private Object[] getPathToRoot(EquipNode node)
{
return getPathToRoot(node, 0);
}
/**
* Builds the parents of node up to and including the root node,
* where the original node is the last element in the returned array.
* The length of the returned array gives the node's depth in the
* tree.
*
* @param aNode the TreeNode to get the path for
* @param depth an int giving the number of steps already taken towards
* the root (on recursive calls), used to size the returned array
* @return an array of TreeNodes giving the path from the root to the
* specified node
*/
private Object[] getPathToRoot(EquipNode aNode, int depth)
{
Object[] retNodes;
// This method recurses, traversing towards the root in order
// size the array. On the way back, it fills in the nodes,
// starting from the root and working back to the original node.
/* Check for null, in case someone passed in a null node, or
they passed in an element that isn't rooted at root. */
if (aNode == null)
{
if (depth == 0)
{
return null;
}
else
{
retNodes = new Object[depth + 1];
retNodes[0] = root;
}
}
else
{
depth++;
retNodes = getPathToRoot(aNode.getParent(), depth);
retNodes[retNodes.length - depth] = aNode;
}
return retNodes;
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @param source the node being changed
* @param path the path to the root node
* @param childIndices the indices of the changed elements
* @param children the changed elements
* @see EventListenerList
*/
protected void fireTreeNodesChanged(Object source, Object[] path,
int[] childIndices,
Object[] children)
{
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
TreeModelEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2)
{
if (listeners[i] == TreeModelListener.class)
{
// Lazily create the event:
if (e == null)
{
e = new TreeModelEvent(source, path,
childIndices, children);
}
((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @param source the node where new elements are being inserted
* @param path the path to the root node
* @param childIndices the indices of the new elements
* @param children the new elements
* @see EventListenerList
*/
protected void fireTreeNodesInserted(Object source, Object[] path,
int[] childIndices,
Object[] children)
{
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
TreeModelEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2)
{
if (listeners[i] == TreeModelListener.class)
{
// Lazily create the event:
if (e == null)
{
e = new TreeModelEvent(source, path,
childIndices, children);
}
((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @param source the node where elements are being removed
* @param path the path to the root node
* @param childIndices the indices of the removed elements
* @param children the removed elements
* @see EventListenerList
*/
protected void fireTreeNodesRemoved(Object source, Object[] path,
int[] childIndices,
Object[] children)
{
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
TreeModelEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2)
{
if (listeners[i] == TreeModelListener.class)
{
// Lazily create the event:
if (e == null)
{
e = new TreeModelEvent(source, path,
childIndices, children);
}
((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
}
}
}
/**
* Notifies all listeners that have registered interest for
* notification on this event type. The event instance
* is lazily created using the parameters passed into
* the fire method.
*
* @param source the node where the tree model has changed
* @param path the path to the root node
* @param childIndices the indices of the affected elements
* @param children the affected elements
* @see EventListenerList
*/
protected void fireTreeStructureChanged(Object source, Object[] path,
int[] childIndices,
Object[] children)
{
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
TreeModelEvent e = null;
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length - 2; i >= 0; i -= 2)
{
if (listeners[i] == TreeModelListener.class)
{
// Lazily create the event:
if (e == null)
{
e = new TreeModelEvent(source, path,
childIndices, children);
}
((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
}
}
}
private static class NodeComparator implements Comparator<EquipNode>
{
@Override
public int compare(EquipNode o1, EquipNode o2)
{
return o1.compareTo(o2);
}
}
}