/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © Copyright IBM Corp. 2009, 2014 * © Copyright ILOG 1996, 2009 * All Rights Reserved. * * Note to U.S. Government Users Restricted Rights: * The Software and Documentation were developed at private expense and * are "Commercial Items" as that term is defined at 48 CFR 2.101, * consisting of "Commercial Computer Software" and * "Commercial Computer Software Documentation", as such terms are * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4, * as applicable. */ import java.awt.Image; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.beans.SimpleBeanInfo; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.Timer; import ilog.views.IlvGrapher; import ilog.views.IlvGraphic; import ilog.views.IlvGraphicEnumeration; import ilog.views.diagrammer.IlvDiagrammer; import ilog.views.diagrammer.application.IlvDiagrammerAction; import ilog.views.sdm.IlvSDMEngine; import ilog.views.util.IlvImageUtil; /** * A base class for "running" a diagram, that is, dynamically changing * properties of a diagram's nodes and/or links to simulate a real-time data * feed. */ public abstract class DiagramRunner extends Timer implements ActionListener { // The diagram component that we want to animate. // IlvDiagrammer diagrammer; private boolean looping; private static final String runnerProperty = "_IlvDiagrammer_DiagramRunner"; private static final String loopProperty = "_IlvDiagrammer_DiagramRunner_Loop"; /** * Creates a new diagram runner. * * @param delay * The delay between two animation steps. * @param diagrammer * The diagrammer to be animated. */ public DiagramRunner(IlvDiagrammer diagrammer, int delay) { super(delay, null); setInitialDelay(0); addActionListener(this); this.diagrammer = diagrammer; diagrammer.putClientProperty(runnerProperty, this); diagrammer.getEngine().applyStyle(this, "DiagramRunner", null); if (Boolean.TRUE.equals(diagrammer.getClientProperty(loopProperty))) { setLooping(true); } diagrammer.putClientProperty(loopProperty, null); } /** * Returns the diagram runner attached to an IlvDiagrammer object. * * @param diagrammer * The diagram component. */ public static DiagramRunner getRunner(IlvDiagrammer diagrammer) { return (DiagramRunner) diagrammer.getClientProperty(runnerProperty); } /** * Returns true if the animation loops continuously. */ public boolean isLooping() { return looping; } /** * If looping is true, the animation loops continuously. */ public void setLooping(boolean looping) { this.looping = looping; } /** * Calls the "step" method inside an adjustment sequence. */ Override public void actionPerformed(ActionEvent e) { diagrammer.setAdjusting(true); step(); diagrammer.setAdjusting(false); } /** * Called on all objects when the animation is started. */ protected abstract void init(Object object); /** * This method is called at every step of the animation. */ protected abstract void step(); Override public void start() { super.start(); diagrammer.setAdjusting(true); Iterator<?> it = diagrammer.getAllObjects(); while (it.hasNext()) { init(it.next()); } diagrammer.setAdjusting(false); } Override public void stop() { super.stop(); if (looping) { setInitialDelay(getDelay()); start(); } else { IlvDiagrammerAction.updateActions(diagrammer.getRootPane(), diagrammer); } } /** * A subclass of DiagramRunner that modifies some properties of one object * after another. */ public static abstract class Sequence extends DiagramRunner { // The current object, that is, the last object that was modified. // private Object current; public Sequence(IlvDiagrammer diagrammer, int delay) { super(diagrammer, delay); } /** * Calls the "next" method to determine which object to modify next. Then * calls "leave" on the previous object, and "enter" on the new object. */ Override protected void step() { Object next = next(current); if (current != null) { leave(current); } if (next != null) { current = next; enter(current); } else { stop(); } } Override public void start() { current = null; super.start(); } Override public void stop() { super.stop(); if (current != null) { leave(current); current = null; } } /** * Returns the next object to animate. If this method returns null, the * animation stops. * * @param previous * The current object. */ protected abstract Object next(Object previous); /** * Called at each animation step on the previous current object. * * @param previous * The previous current object. */ protected abstract void leave(Object previous); /** * Called at each animation step on the new current object. * * @param next * The new current object. */ protected abstract void enter(Object next); } /** * Bean info class */ public class SequenceBeanInfo extends SimpleBeanInfo { /** * Constructor */ public SequenceBeanInfo() { } } public static class Flow extends Sequence { public Flow(IlvDiagrammer diagrammer, int delay) { super(diagrammer, delay); } /** * Returns the next object, following the flow of the diagram: - The first * object is the one that has no incoming links. - The next object of a link * is its destination node. - When a node has several outgoing links, one of * the links is chosen randomly, otherwise the next object is the outgoing * link. * * @param previous * The current object. */ Override protected Object next(Object previous) { if (previous == null) { Iterator<?> selection = diagrammer.getSelectedObjects(); if (selection.hasNext()) { return selection.next(); } else { Iterator<?> objs = diagrammer.getAllObjects(); while (objs.hasNext()) { Object o = objs.next(); if (getLinks(o, false).size() == 0) { return o; } } objs = diagrammer.getAllObjects(); if (objs.hasNext()) return objs.next(); else return null; } } else if (diagrammer.isLink(previous)) { return diagrammer.getTargetNode(previous); } else { List<Object> links = getLinks(previous, true); if (links.size() == 0) { return null; } else if (links.size() == 1) { return links.get(0); } else { // See if there is a "preferred" path. // for (int i = 0; i < links.size(); i++) { Object link = links.get(i); if (diagrammer.getObjectProperty(link, "sdm:preferred") != null) { return link; } } // Otherwise choose randomly. // return links.get((int) Math.round(Math.random() * (links.size() - 1))); } } } private List<Object> getLinks(Object node, boolean from) { IlvSDMEngine engine = diagrammer.getEngine(); IlvGrapher grapher = engine.getGrapher(); IlvGraphic nodeGraphic = engine.getGraphic(node, false); IlvGraphicEnumeration linksEnum = from ? grapher.getLinksFrom(nodeGraphic) : grapher.getLinksTo(nodeGraphic); List<Object> links = new ArrayList<Object>(); while (linksEnum.hasMoreElements()) { links.add(engine.getObject(linksEnum.nextElement())); } return links; } Override protected void init(Object object) { diagrammer.setSelected(object, false); // Simply set the property value to empty // This will ensure that the active nor the // done state is represented diagrammer.setObjectProperty(object, "status", ""); } /** * Selects the next object. */ Override protected void enter(Object next) { diagrammer.setSelected(next, true); diagrammer.setObjectProperty(next, "status", "active"); diagrammer.scrollToObject(next); } /** * Deselects the previous object. */ Override protected void leave(Object previous) { diagrammer.setSelected(previous, false); diagrammer.setObjectProperty(previous, "status", "done"); } } /** * Bean info class */ public class FlowBeanInfo extends SimpleBeanInfo { /** * Constructor */ public FlowBeanInfo() { } } /** * An action that starts/stops a diagram runner. */ public static abstract class Run extends IlvDiagrammerAction.ToggleAction { public Run() { super("Run", ResourceBundle.getBundle("runner")); } Override protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception { return diagrammer != null && DiagramRunner.getRunner(diagrammer) != null && DiagramRunner.getRunner(diagrammer).isRunning(); } Override protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception { if (diagrammer != null) { DiagramRunner runner = DiagramRunner.getRunner(diagrammer); if (selected) { if (runner == null) { runner = createRunner(diagrammer); } runner.start(); } else if (runner != null) { boolean oldLooping = runner.isLooping(); runner.setLooping(false); runner.stop(); runner.setLooping(oldLooping); setIcon(Action.SMALL_ICON); } } } Override protected void setSelected(boolean selected) { super.setSelected(selected); if (selected) { setIcon("StopIcon"); } else { setIcon(Action.SMALL_ICON); } } private void setIcon(String key) { try { String s = getResourceBundle().getString(getPrefix() + "." + key); Image image = null; image = IlvImageUtil.getImageFromFile(getClass(), s); putValue(Action.SMALL_ICON, new ImageIcon(image)); } catch (IOException e) { e.printStackTrace(); } } Override protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception { return true; } protected abstract DiagramRunner createRunner(IlvDiagrammer diagrammer); } /** * An action that toggle continuous looping of the animation. */ public static class Loop extends IlvDiagrammerAction.ToggleAction { public Loop() { super("Loop", ResourceBundle.getBundle("runner")); } Override protected boolean isSelected(IlvDiagrammer diagrammer) throws Exception { return diagrammer != null && ((DiagramRunner.getRunner(diagrammer) != null && DiagramRunner.getRunner(diagrammer).isLooping()) || Boolean.TRUE.equals(diagrammer.getClientProperty(loopProperty))); } Override protected void setSelected(IlvDiagrammer diagrammer, boolean selected) throws Exception { if (diagrammer != null) { DiagramRunner runner = DiagramRunner.getRunner(diagrammer); if (runner != null) { runner.setLooping(selected); } else { diagrammer.putClientProperty(loopProperty, Boolean.valueOf(selected)); } } } Override protected boolean isEnabled(IlvDiagrammer diagrammer) throws Exception { return true; } } }