/*
* JaamSim Discrete Event Simulation
* Copyright (C) 2011 Ausenco Engineering Canada Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jaamsim.ui;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowFocusListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
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.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import com.jaamsim.Graphics.DisplayEntity;
import com.jaamsim.Graphics.EntityLabel;
import com.jaamsim.basicsim.Entity;
import com.jaamsim.basicsim.ErrorException;
import com.jaamsim.basicsim.ObjectType;
import com.jaamsim.basicsim.Simulation;
import com.jaamsim.input.Input;
import com.jaamsim.input.InputAgent;
import com.jaamsim.units.Unit;
public class ObjectSelector extends FrameBox {
private static ObjectSelector myInstance;
// Tree view properties
private final DefaultMutableTreeNode top;
private final DefaultTreeModel treeModel;
private final JTree tree;
private final JScrollPane treeView;
public static Entity currentEntity;
private long entSequence;
private final int MAX_GENERATED_ENTITIES = 10000;
public ObjectSelector() {
super( "Object Selector" );
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(FrameBox.getCloseListener("ShowObjectSelector"));
addWindowFocusListener(new MyFocusListener());
top = new DefaultMutableTreeNode();
treeModel = new DefaultTreeModel(top);
tree = new JTree();
tree.setModel(treeModel);
tree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION );
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.setInvokesStopCellEditing(true);
treeView = new JScrollPane(tree);
getContentPane().add(treeView);
entSequence = 0;
setLocation(GUIFrame.COL1_START, GUIFrame.BOTTOM_START);
setSize(GUIFrame.COL1_WIDTH, GUIFrame.HALF_BOTTOM);
tree.addTreeSelectionListener( new MyTreeSelectionListener() );
treeModel.addTreeModelListener( new MyTreeModelListener(tree) );
tree.addMouseListener(new MyMouseListener());
tree.addKeyListener(new MyKeyListener());
}
@Override
public void setEntity(Entity ent) {
if (ent == currentEntity)
return;
currentEntity = ent;
if (tree == null)
return;
long curSequence = Entity.getEntitySequence();
if (entSequence != curSequence) {
entSequence = curSequence;
updateTree();
}
if (currentEntity == null) {
tree.setSelectionPath(null);
tree.setEditable(false);
return;
}
tree.setEditable(true);
DefaultMutableTreeNode root = (DefaultMutableTreeNode)tree.getModel().getRoot();
Enumeration<?> e = root.depthFirstEnumeration();
while (e.hasMoreElements()) {
DefaultMutableTreeNode aNode = (DefaultMutableTreeNode)e.nextElement();
if (aNode.getUserObject() == currentEntity) {
TreePath path = new TreePath(aNode.getPath());
tree.scrollPathToVisible(path);
tree.setSelectionPath(path);
return;
}
}
// Entity not found in the tree
tree.setSelectionPath(null);
tree.setEditable(false);
}
@Override
public void updateValues(double simTime) {
if (!this.isVisible())
return;
long curSequence = Entity.getEntitySequence();
if (entSequence != curSequence) {
entSequence = curSequence;
updateTree();
}
}
public static void allowUpdate() {
myInstance.entSequence = 0;
}
/**
* Returns the only instance of the Object Selector
*/
public static synchronized ObjectSelector getInstance() {
if (myInstance == null)
myInstance = new ObjectSelector();
myInstance.treeView.getHorizontalScrollBar().getModel().setValue(0);
return myInstance;
}
private synchronized static void killInstance() {
myInstance = null;
}
@Override
public void dispose() {
killInstance();
currentEntity = null;
super.dispose();
}
private void updateTree() {
if (tree == null || top == null)
return;
// Store all the expanded paths
Enumeration<TreePath> expandedPaths = tree.getExpandedDescendants(new TreePath(top));
// Identify the selected entity (cannot use currentEntity -- would race with setEntity)
Entity selectedEnt = null;
TreePath selectedPath = tree.getSelectionPath();
if (selectedPath != null) {
Object selectedObj = ((DefaultMutableTreeNode)selectedPath.getLastPathComponent()).getUserObject();
if (selectedObj instanceof Entity)
selectedEnt = (Entity)selectedObj;
}
// Clear the present tree
top.removeAllChildren();
// Add the instance for Simulation to the top of the tree as a single leaf node
top.add(new DefaultMutableTreeNode(Simulation.getInstance(), false));
// Add the instance for TLS if present
Entity tls = Entity.getNamedEntity("TLS");
if (tls != null)
top.add(new DefaultMutableTreeNode(tls, false));
// Create the tree structure for palettes and object types in the correct order
for (int i = 0; i < ObjectType.getAll().size(); i++) {
try {
final ObjectType type = ObjectType.getAll().get(i);
if (type == null)
continue;
// Find or create the node for the palette
DefaultMutableTreeNode paletteNode = getNodeFor_In(type.getPaletteName(), top);
if (paletteNode == null) {
paletteNode = new DefaultMutableTreeNode(type.getPaletteName());
top.add(paletteNode);
}
// Add the node for the Object Type to the palette
DefaultMutableTreeNode typeNode = new DefaultMutableTreeNode(type.getName(), true);
paletteNode.add(typeNode);
}
catch (IndexOutOfBoundsException e) {}
}
// Prepare a sorted list of entities
int numGenerated = 0;
ArrayList<Entity> entityList = new ArrayList<>();
for (int i = 0; i < Entity.getAll().size(); i++) {
try {
final Entity ent = Entity.getAll().get(i);
// The instance for Simulation has already been added
if (ent == Simulation.getInstance())
continue;
// The instance for TLS has already been added
if (ent == tls)
continue;
// Do not include the units or views
if (ent instanceof Unit || ent instanceof View)
continue;
// Skip an entity that is locked
if (ent.testFlag(Entity.FLAG_LOCKED))
continue;
// Apply an upper bound on the number of generated entities to display
if (ent.testFlag(Entity.FLAG_GENERATED)) {
if (numGenerated > MAX_GENERATED_ENTITIES)
continue;
numGenerated++;
}
entityList.add(ent);
}
catch (IndexOutOfBoundsException e) {}
}
try {
Collections.sort(entityList, selectorSortOrder);
}
catch (Throwable t) {}
// Loop through the entities in the model
for (int i=0; i<entityList.size(); i++) {
try {
final Entity ent = entityList.get(i);
// Determine the object type for this entity
final ObjectType type = ent.getObjectType();
if (type == null)
continue;
// Find the palette node for this entity
DefaultMutableTreeNode paletteNode = getNodeFor_In(type.getPaletteName(), top);
if (paletteNode == null)
continue;
// Find the object type node for this entity
DefaultMutableTreeNode typeNode = getNodeFor_In(type.getName(), paletteNode);
if (typeNode == null)
continue;
// Add the entity to the object type node
DefaultMutableTreeNode entityNode = new DefaultMutableTreeNode(ent, false);
typeNode.add(entityNode);
}
catch (IndexOutOfBoundsException e) {}
}
// Remove any object type tree nodes that have no entities
ArrayList<DefaultMutableTreeNode> nodesToRemove = new ArrayList<>();
Enumeration<DefaultMutableTreeNode> paletteEnum = top.children();
while (paletteEnum.hasMoreElements()) {
DefaultMutableTreeNode paletteNode = paletteEnum.nextElement();
Enumeration<DefaultMutableTreeNode> typeEnum = paletteNode.children();
while (typeEnum.hasMoreElements()) {
DefaultMutableTreeNode typeNode = typeEnum.nextElement();
if (typeNode.isLeaf())
nodesToRemove.add(typeNode);
}
for (DefaultMutableTreeNode typeNode : nodesToRemove) {
paletteNode.remove(typeNode);
}
nodesToRemove.clear();
}
// Remove any palettes that have no object types left
paletteEnum = top.children();
while (paletteEnum.hasMoreElements()) {
DefaultMutableTreeNode paletteNode = paletteEnum.nextElement();
// Do not remove any of the special nodes such as the instance for Simulation
if (!paletteNode.getAllowsChildren())
continue;
if (paletteNode.isLeaf())
nodesToRemove.add(paletteNode);
}
for (DefaultMutableTreeNode paletteNode : nodesToRemove) {
top.remove(paletteNode);
}
// Refresh the tree
treeModel.reload(top);
// Restore the path to the selected entity
if (selectedEnt != null) {
TreePath path = ObjectSelector.getPathToEntity(selectedEnt, top);
if (path != null)
tree.setSelectionPath(path);
}
// Restore all the expanded paths
while (expandedPaths != null && expandedPaths.hasMoreElements()) {
TreePath oldPath = expandedPaths.nextElement();
if (oldPath.getPathCount() < 2)
continue;
// Path to a palette
DefaultMutableTreeNode oldPaletteNode = (DefaultMutableTreeNode) (oldPath.getPath())[1];
String paletteName = (String) (oldPaletteNode.getUserObject());
DefaultMutableTreeNode paletteNode = getNodeFor_In(paletteName, top);
if (paletteNode == null)
continue;
if (oldPath.getPathCount() == 2) {
Object[] nodeList = { top, paletteNode };
tree.expandPath(new TreePath(nodeList));
continue;
}
// Path to an object type
DefaultMutableTreeNode oldTypeNode = (DefaultMutableTreeNode) (oldPath.getPath())[2];
String typeName = (String) (oldTypeNode.getUserObject());
DefaultMutableTreeNode typeNode = getNodeFor_In(typeName, paletteNode);
if (typeNode == null)
continue;
Object[] nodeList = { top, paletteNode, typeNode };
tree.expandPath(new TreePath(nodeList));
}
}
private static class EntityComparator implements Comparator<Entity> {
@Override
public int compare(Entity ent0, Entity ent1) {
// Put any null entities at the end of the list
if (ent0 == null && ent1 == null)
return 0;
if (ent0 != null && ent1 == null)
return -1;
if (ent0 == null && ent1 != null)
return 1;
// Otherwise, sort in natural order
return Input.uiSortOrder.compare(ent0, ent1);
}
}
private static final Comparator<Entity> selectorSortOrder = new EntityComparator();
/**
* Returns a tree node for the specified userObject in the specified parent.
* If a node, already exists for this parent, it is returned. If it does
* not exist, then null is returned.
* @param userObject - object for the tree node.
* @param parent - object's parent
* @return tree node for the object.
*/
private static DefaultMutableTreeNode getNodeFor_In(Object userObject, DefaultMutableTreeNode parent) {
// Loop through the parent's children
Enumeration<DefaultMutableTreeNode> enumeration = parent.children();
while (enumeration.hasMoreElements()) {
DefaultMutableTreeNode eachNode = enumeration.nextElement();
if (eachNode.getUserObject() == userObject ||
userObject instanceof String && ((String) userObject).equals(eachNode.getUserObject()) )
return eachNode;
}
return null;
}
private static TreePath getPathToEntity(Entity ent, DefaultMutableTreeNode root) {
final ObjectType type = ent.getObjectType();
if (type == null)
return null;
DefaultMutableTreeNode paletteNode = getNodeFor_In(type.getPaletteName(), root);
if (paletteNode == null)
return null;
DefaultMutableTreeNode typeNode = getNodeFor_In(type.getName(), paletteNode);
if (typeNode == null)
return null;
DefaultMutableTreeNode entityNode = getNodeFor_In(ent, typeNode);
if (entityNode == null)
return null;
Object[] nodeList = { root, paletteNode, typeNode, entityNode };
return new TreePath(nodeList);
}
static class MyTreeSelectionListener implements TreeSelectionListener {
@Override
public void valueChanged( TreeSelectionEvent e ) {
JTree tree = (JTree) e.getSource();
DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
if(node == null) {
// This occurs when we set no selected entity (null) and then
// force the tree to have a null selected node
return;
}
Object userObj = node.getUserObject();
if (userObj instanceof Entity) {
FrameBox.setSelectedEntity((Entity)userObj, false);
}
else {
FrameBox.setSelectedEntity(null, false);
}
}
}
static class MyTreeModelListener implements TreeModelListener {
private final JTree tree;
public MyTreeModelListener(JTree tree) {
this.tree = tree;
}
@Override
public void treeNodesChanged( TreeModelEvent e ) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)tree.getLastSelectedPathComponent();
String newName = ((String)node.getUserObject()).trim();
try {
InputAgent.renameEntity(currentEntity, newName);
if (currentEntity instanceof DisplayEntity) {
DisplayEntity dEnt = (DisplayEntity) currentEntity;
EntityLabel label = EntityLabel.getLabel(dEnt);
if (label != null)
label.updateForTargetNameChange();
}
}
catch (ErrorException err) {
GUIFrame.showErrorDialog("Input Error", err.getMessage());
}
finally {
node.setUserObject(currentEntity);
FrameBox.reSelectEntity();
}
}
@Override
public void treeNodesInserted(TreeModelEvent e) {}
@Override
public void treeNodesRemoved(TreeModelEvent e) {}
@Override
public void treeStructureChanged(TreeModelEvent e) {}
}
static class MyMouseListener implements MouseListener {
private final JPopupMenu menu= new JPopupMenu();
@Override
public void mouseClicked(MouseEvent e) {
if(e.getButton() != MouseEvent.BUTTON3)
return;
if(currentEntity == null)
return;
// Right mouse click on a movable DisplayEntity
menu.removeAll();
ContextMenu.populateMenu(menu, currentEntity, e.getX(), e.getY());
menu.show(e.getComponent(), e.getX(), e.getX());
}
@Override
public void mouseEntered(MouseEvent e) {}
@Override
public void mouseExited(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {}
@Override
public void mouseReleased(MouseEvent e) {}
}
static class MyKeyListener implements KeyListener {
@Override
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() != KeyEvent.VK_DELETE)
return;
if(currentEntity instanceof DisplayEntity ) {
DisplayEntity disp = (DisplayEntity)currentEntity;
if(! disp.isMovable())
return;
// Delete key was released on a movable DisplayEntity
disp.kill();
FrameBox.setSelectedEntity(null, false);
}
}
@Override
public void keyPressed(KeyEvent e) {}
@Override
public void keyTyped(KeyEvent e) {}
}
static class MyFocusListener implements WindowFocusListener {
@Override
public void windowGainedFocus(WindowEvent arg0) {}
@Override
public void windowLostFocus(WindowEvent e) {
// Complete any editing that has started
ObjectSelector.myInstance.tree.stopEditing();
}
}
}