/* * 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. */ package ganttviewer.pert; import ganttviewer.pert.model.GanttToSDMAdapter; import ganttviewer.util.GanttViewerLogger; import ganttviewer.util.PERTDiagram; import ilog.views.IlvApplyObject; import ilog.views.IlvGrapher; import ilog.views.IlvGraphic; import ilog.views.IlvManager; import ilog.views.IlvManagerViewInteractor; import ilog.views.IlvPoint; import ilog.views.IlvRect; import ilog.views.IlvTransformer; import ilog.views.event.InteractorListener; import ilog.views.event.TransformerListener; import ilog.views.gantt.IlvActivity; import ilog.views.gantt.IlvConstraint; import ilog.views.gantt.IlvGanttModel; import ilog.views.gantt.IlvGanttModelUtil; import ilog.views.gantt.event.SelectionEvent; import ilog.views.gantt.event.SelectionListener; import ilog.views.gantt.util.event.IlvHashedEventService; import ilog.views.interactor.IlvPanInteractor; import ilog.views.interactor.IlvSelectInteractor; import ilog.views.sdm.IlvSDMEngine; import ilog.views.sdm.IlvSDMView; import ilog.views.sdm.event.SDMEngineSelectionEvent; import ilog.views.sdm.event.SDMEngineSelectionListener; import ilog.views.sdm.event.SDMGraphLayoutRendererEvent; import ilog.views.sdm.event.SDMGraphLayoutRendererListener; import ilog.views.swing.IlvJScrollManagerView; import ilog.views.swing.IlvToolTipManager; import ilog.views.symbol.util.IlvSelectableSymbol; import ilog.views.util.event.IlvEventListenerCollection; import ilog.views.util.event.IlvEventListenerSet; import java.awt.AWTEvent; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.AWTEventListener; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import javax.swing.JPanel; import javax.swing.JRootPane; import javax.swing.JScrollBar; import javax.swing.SwingUtilities; import javax.swing.plaf.basic.BasicArrowButton; /** * Implementation of the PERT diagram. */ SuppressWarnings("serial") public class PERTDiagramImpl extends JRootPane implements PERTDiagram, SDMEngineSelectionListener { private static final Rectangle OVERVIEW_BOUNDS = new Rectangle(10, 10, 200, 100); /** * The overview. */ private Overview overview; /** * The manager view. */ private IlvJScrollManagerView scrollView; /** * The event service. */ private IlvHashedEventService eventService; /** * The selection listener(s). */ private IlvEventListenerCollection<SelectionListener> selectionListeners; /** * Selection listening enabled or disabled. */ private boolean listenToSelection = true; /** * Layout listener. */ private SDMGraphLayoutRendererListener layoutListener; /** * Apply collapse action. */ private IlvApplyObject applyCollapse; /** * Builds a <code>PERTDiagramImpl</code>. */ public PERTDiagramImpl() { initEventService(); // create the PERT diagram createPERTDiagram(); } /** * Creates a PERT diagram. */ protected void createPERTDiagram() { scrollView = new IlvJScrollManagerView(new IlvSDMView()); setContentPane(scrollView); // overview overview = new Overview((IlvSDMView) scrollView.getView()); // collapse action applyCollapse = new IlvApplyObject() { Override public void apply(IlvGraphic g, Object arg) { ((IlvGrapher) g).setCollapsed(true); } }; // layout listener layoutListener = new SDMGraphLayoutRendererListener() { Override public void performLayoutEnded(SDMGraphLayoutRendererEvent event) { overview.updateContent(); } Override public void performLayoutStarted(SDMGraphLayoutRendererEvent event) { } }; // glass pane layer customization initGlassPane(); } /** * Custom glass pane. * */ class CustomGlassPane extends JPanel implements AWTEventListener { /** * Builds a <code>CustomGlassPane</code>. */ public CustomGlassPane() { try { Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.MOUSE_EVENT_MASK); } catch (Exception e) { // doing nothing, the overview will not be displayed } } /** * @see javax.swing.JComponent.contains(int, int) */ Override public boolean contains(int x, int y) { // AWT system shows the cursor of the topmost visible component // In our case, the topmost visible component is this glass pane // and we want to display the current cursor of the IlvSDMView // This fix makes the glass pane completely transparent return false; } /** * @see java.awt.event.AWTEventListener.eventDispatched(java.awt.AWTEvent) */ Override public void eventDispatched(AWTEvent event) { if (event instanceof MouseEvent) { MouseEvent me = (MouseEvent) event; if (!SwingUtilities.isDescendingFrom(me.getComponent(), PERTDiagramImpl.this)) { return; } // make the overview visible or not depending on the current interaction if (isTranslation(me)) { overview.setVisible(true); } else if (stopTranslation(me)) { overview.setVisible(false); } } } } /** * Indicates whether the mouse event is considered as a translation interaction. * @param me The mouse event. * @return Whether the mouse event is considered as a translation interaction. */ protected boolean isTranslation(MouseEvent me) { if (me.getSource() instanceof IlvSDMView) { return (((IlvSDMView) me.getSource()).getInteractor() instanceof IlvPanInteractor) && (MouseEvent.MOUSE_PRESSED == me.getID()); } if ((me.getSource() instanceof JScrollBar) || (me.getSource() instanceof BasicArrowButton)) { return MouseEvent.MOUSE_PRESSED == me.getID(); } return false; } /** * Indicates whether the mouse event is considered as a stop-translation interaction. * @param me The mouse event. * @return Whether the mouse event is considered as a stop-translation interaction. */ protected boolean stopTranslation(MouseEvent me) { return MouseEvent.MOUSE_RELEASED == me.getID(); } /** * Customizes the glass pane to display an overview. */ protected void initGlassPane() { JPanel glassPane = new CustomGlassPane(); glassPane.setLayout(null); glassPane.setOpaque(false); glassPane.add(overview); overview.setBounds(PERTDiagramImpl.OVERVIEW_BOUNDS); overview.setVisible(false); setGlassPane(glassPane); glassPane.setVisible(true); } /** * Initializes the event services. */ private void initEventService() { eventService = new IlvHashedEventService(); selectionListeners = new IlvEventListenerSet<SelectionListener>(); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#initialize() */ Override public void initialize() { // activate the mouse wheel support scrollView.setWheelScrollingEnabled(true); IlvSDMView view = (IlvSDMView) scrollView.getView(); // activate tooltips IlvToolTipManager.registerView(view); // activate wheel zooming view.setWheelZoomingEnabled(true); // selection IlvSelectInteractor selectInteractor = new IlvSelectInteractor(); selectInteractor.setMoveAllowed(false); selectInteractor.setMultipleSelectionMode(true); view.setInteractor(selectInteractor); // listen to selection view.getSDMEngine().addSDMEngineSelectionListener(this); // a way to improve the performances: // no rendering done on selection or property change // selection is more reactive view.getSDMEngine().setRenderingDoneMode(IlvSDMEngine.NEVER); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#attachGanttModel(ilog.views.gantt.IlvGanttModel) */ Override public void attachGanttModel(IlvGanttModel ganttModel) { IlvSDMEngine engine = getSDMEngine(); engine.setAdjusting(true); try { // create the model adapter GanttToSDMAdapter sdmModel = new GanttToSDMAdapter(ganttModel); engine.setModel(sdmModel, true); engine.performNodeLayout(); } catch (Exception e) { GanttViewerLogger.error(e); } finally { engine.setAdjusting(false); } } /** * Returns the collapsed activities. * @return The collapsed activities. */ protected Set<IlvActivity> getCollapsedNodes() { IlvSDMEngine engine = getSDMEngine(); Set<IlvActivity> collapsedNodes = new HashSet<IlvActivity>(); // retrieve collapsed nodes Enumeration<?> objects = engine.getAllObjects(); while (objects.hasMoreElements()) { Object obj = objects.nextElement(); if (obj instanceof IlvActivity) { IlvGraphic graphic = engine.getGraphic(obj, false); if (graphic instanceof IlvGrapher) { IlvGrapher grapher = (IlvGrapher) graphic; if (grapher.isCollapsed()) { collapsedNodes.add((IlvActivity) obj); } } } } return collapsedNodes; } /** * Collapses the nodes corresponding to the given activities. * @param activities The activities. */ protected void setCollapsedNodes(Set<IlvActivity> activities) { if ((activities == null) || (activities.size() == 0)) { return; } IlvSDMEngine engine = getSDMEngine(); Iterator<IlvActivity> it = activities.iterator(); while (it.hasNext()) { IlvGrapher grapher = (IlvGrapher) engine.getGraphic(it.next(), false); grapher.getGraphicBag().applyToObject(grapher, applyCollapse, grapher, false); } // perform layout engine.performNodeLayout(); } /** * Gets the selected objects. * @return The selected objects. */ protected Set<Object> getSelectedObjects() { IlvSDMEngine engine = getSDMEngine(); Set<Object> selectedObjects = new HashSet<Object>(); // retrieve collapsed nodes Enumeration<?> objects = engine.getSelectedObjects(); while (objects.hasMoreElements()) { selectedObjects.add(objects.nextElement()); } return selectedObjects; } /** * Internal selection of the given objects. * @param selectedObjects The objects to select. */ protected void setSelectedObjects(Set<Object> selectedObjects) { if ((selectedObjects == null) || (selectedObjects.size() == 0)) { return; } listenToSelection = false; Iterator<Object> objects = selectedObjects.iterator(); while (objects.hasNext()) { Object obj = objects.next(); if (obj instanceof IlvActivity) { setSelectedImpl((IlvActivity) obj, true); } else if (obj instanceof IlvConstraint) { setSelectedImpl((IlvConstraint) obj, true); } } listenToSelection = true; } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#applyStyle(java.lang.String) * @proofread */ Override public void applyStyle(String css) throws Exception { Set<IlvActivity> collapsedNodes = null; IlvSDMEngine engine = getSDMEngine(); // if this is not the first stylesheet: if (engine.getNodeLayoutRenderer() != null) { // remove the previous layout listener engine.getNodeLayoutRenderer().removeGraphLayoutRendererListener( layoutListener); // retrieve the collapsed nodes to set them collapsed after applying the new stylesheet collapsedNodes = getCollapsedNodes(); } // get selection Set<Object> selectedObjects = getSelectedObjects(); // apply stylesheet getSDMView().setStyleSheets(0, css); // restaure collapsed nodes setCollapsedNodes(collapsedNodes); // restaure selection setSelectedObjects(selectedObjects); // add the layout listener again engine.getNodeLayoutRenderer().addGraphLayoutRendererListener( layoutListener); engine.performNodeLayout(); } /** * Returns the SDM view. * @return The SDM view. */ public IlvSDMView getSDMView() { return (IlvSDMView) scrollView.getView(); } /** * Returns the SDM engine. * @return The SDM engine. */ public IlvSDMEngine getSDMEngine() { return getSDMView().getSDMEngine(); } /** * Returns the loaded Gantt data model. * @return The Gantt data model. */ Override public IlvGanttModel getGanttModel() { GanttToSDMAdapter sdmModel = (GanttToSDMAdapter) this.getSDMEngine() .getModel(); return sdmModel.getGanttModel(); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#isSelected(ilog.views.gantt.IlvActivity) */ Override public boolean isSelected(IlvActivity activity) { return getSDMEngine().isSelected(activity); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#deselectAll() */ Override public void deSelectAll() { getSDMEngine().deselectAllObjects(); } /** * Selects or deselects the given activity. * @param activity The activity. * @param selected The desired selection status. */ protected void setSelectedImpl(IlvActivity activity, boolean selected) { GanttToSDMAdapter model = (GanttToSDMAdapter) getSDMEngine().getModel(); if (model.isNode(activity) && getGanttModel().contains(activity) && !IlvGanttModelUtil.hasChildren(getGanttModel(), activity)) { getSDMEngine().setSelected(activity, selected); } // select or deselect the activity IlvGraphic activityGraphic = getSDMEngine().getGraphic(activity, false); if (activityGraphic instanceof IlvSelectableSymbol) { ((IlvSelectableSymbol) activityGraphic).selectionStateChanged(); } } /** * Selects or deselects the given constraint. * @param constraint The constraint. * @param selected The desired selection status. */ protected void setSelectedImpl(IlvConstraint constraint, boolean selected) { GanttToSDMAdapter model = (GanttToSDMAdapter) getSDMEngine().getModel(); if (model.isLink(constraint) && getGanttModel().contains(constraint)) { getSDMEngine().setSelected(constraint, selected); } } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#select(ilog.views.gantt.IlvActivity, boolean) */ Override public void setSelected(IlvActivity activity, boolean selected) { setSelectedImpl(activity, selected); } /** * Listens to selection changes. * @param event The selection event. * @proofread */ Override public void selectionChanged(SDMEngineSelectionEvent event) { if (!listenToSelection) { return; } if (event.getDataObject() instanceof IlvActivity) { IlvActivity activity = (IlvActivity) event.getDataObject(); boolean selected = event.getEngine().isSelected(activity); // select or deselect the activity IlvGraphic activityGraphic = event.getEngine() .getGraphic(activity, false); if (activityGraphic instanceof IlvSelectableSymbol) { ((IlvSelectableSymbol) activityGraphic).selectionStateChanged(); } // forward the event only if this is an activity fireSelectionEvent(event); // get selected constraints Enumeration<?> e = event.getEngine().getSelectedObjects(); Set<Object> selectedConstraints = new HashSet<Object>(); while (e.hasMoreElements()) { Object obj = e.nextElement(); if (((GanttToSDMAdapter) getSDMEngine().getModel()).isLink(obj)) { selectedConstraints.add(obj); } } if (!selected) { // we only deselect constraints that are not connected to a selected activity Iterator<?> constraints = selectedConstraints.iterator(); while (constraints.hasNext()) { IlvConstraint constraint = (IlvConstraint) constraints.next(); if ((constraint.getFromActivity().equals(activity) && !event .getEngine().isSelected(constraint.getToActivity())) || (constraint.getToActivity().equals(activity) && !event .getEngine().isSelected(constraint.getFromActivity()))) { setSelectedImpl(constraint, false); } } } else { // if an activity has been selected, highlight or unhighlight related constraints IlvGanttModel model = getGanttModel(); Iterator<?> constraints = model.constraintIteratorFromActivity(activity); while (constraints.hasNext()) { IlvConstraint constraint = (IlvConstraint) constraints.next(); setSelectedImpl(constraint, true); } constraints = model.constraintIteratorToActivity(activity); while (constraints.hasNext()) { IlvConstraint constraint = (IlvConstraint) constraints.next(); setSelectedImpl(constraint, true); } } } } /** * Fires a <code>SelectionEvent</code> event. * @param event An SDM engine event that will be transformed into a Gantt selection event. */ private void fireSelectionEvent(SDMEngineSelectionEvent event) { // Create a Gantt selection event. SelectionEvent e = new SelectionEvent(event.getDataObject(), getSDMEngine() .isSelected(event.getDataObject())); eventService.publish(e); for (Iterator<SelectionListener> i = selectionListeners.getListeners(); i.hasNext();) { SelectionListener listener = i.next(); listener.selectionChanged(e); } } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#addInteractorListener(ilog.views.InteractorListener) */ Override public void addInteractorListener(InteractorListener listener) { scrollView.getView().addInteractorListener(listener); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#addSelectionListener(ilog.views.gantt.event.SelectionListener) */ Override public void addSelectionListener(SelectionListener listener) { selectionListeners.addListener(listener); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#addTransformerListener(ilog.views.TransformerListener) */ Override public void addTransformerListener(TransformerListener listener) { scrollView.getView().addTransformerListener(listener); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#removeInteractorListener(ilog.views.event.InteractorListener) */ Override public void removeInteractorListener(InteractorListener listener) { scrollView.getView().removeInteractorListener(listener); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#removeSelectionListener(ilog.views.gantt.event.SelectionListener) */ Override public void removeSelectionListener(SelectionListener listener) { selectionListeners.removeListener(listener); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#removeTransformerListener(ilog.views.event.TransformerListener) */ Override public void removeTransformerListener(TransformerListener listener) { scrollView.getView().removeTransformerListener(listener); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#zoom(ilog.views.IlvPoint, double, double) */ Override public void zoom(IlvPoint point, double sx, double sy) { scrollView.getView().zoom(point, sx, sy, true); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#zoomToFit() */ Override public void zoomToFit() { scrollView.getView().fitTransformerToContent(); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#getInteractor() */ Override public IlvManagerViewInteractor getInteractor() { return scrollView.getView().getInteractor(); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#setInteractor(ilog.views.IlvManagerViewInteractor) */ Override public void setInteractor(IlvManagerViewInteractor interactor) { scrollView.getView().setInteractor(interactor); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#getTransformer() */ Override public IlvTransformer getTransformer() { return scrollView.getView().getTransformer(); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#setTransformer(ilog.views.IlvTransformer) */ Override public void setTransformer(IlvTransformer t) { scrollView.getView().setTransformer(t); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#boundingBox() */ Override public IlvRect boundingBox() { return getSDMView().getSDMEngine().getGrapher().boundingBox(); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#boundingBox(IlvActivity) */ Override public IlvRect boundingBox(IlvActivity activity) { IlvGraphic graphic = getSDMEngine().getGraphic(activity, false); if (graphic == null) { return new IlvRect(); } return graphic.boundingBox(((IlvManager) graphic.getGraphicBag()) .getTopLevelTransformer()); } /* (non-Javadoc) * @see ganttviewer.pert.PERTDiagram#visibleRect() */ Override public IlvRect visibleRect() { Rectangle visibleRect = getSDMView().visibleRect(); return new IlvRect(visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height); } /** * Overview panel. */ class Overview extends JPanel { /** * Cached image to render. */ private BufferedImage cachedImage = null; /** * Transparency. */ private AlphaComposite alpha; /** * The associated view. */ private IlvSDMView view; /** * Visible area color */ private final Color visibleAreaColor = new Color(100, 100, 255, 100); /** * Internal transformer. */ private IlvTransformer transformer; /** * Builds an <code>Overview</code> * @param view The view from which we want to create an overview. */ public Overview(IlvSDMView view) { this.view = view; // alpha transparency management alpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.2f); // internal transformer transformer = new IlvTransformer(); // Transparent panel setOpaque(false); } /** * Recreates the cached image. */ protected void rebuildCachedImage() { Rectangle bounds = getBounds(); // create a new background image BufferedImage snapshot = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = snapshot.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // source and destination rectangles IlvRect srcRect = boundingBox(); IlvRect dstRect = new IlvRect(0, 0, bounds.width, bounds.height); // fit to content double sx = dstRect.width / srcRect.width; double sy = dstRect.height / srcRect.height; double scale = Math.min(sx, sy); double x0 = -scale * srcRect.x + ((sx > sy) ? (dstRect.width - scale * srcRect.width) / 2 : 0); double y0 = -scale * srcRect.y + ((sx <= sy) ? (dstRect.height - scale * srcRect.height) / 2 : 0); transformer.setValues(scale, 0., 0., scale, x0, y0); // draw getSDMView().getSDMEngine().getGrapher().print(g2d, srcRect, view, transformer, false); g2d.dispose(); // create the image to be rendered cachedImage = new BufferedImage(bounds.width, bounds.height, BufferedImage.TYPE_INT_ARGB); g2d = cachedImage.createGraphics(); g2d.setComposite(alpha); g2d.setColor(Color.LIGHT_GRAY); g2d.fillRect(0, 0, bounds.width, bounds.height); g2d.drawImage(snapshot, 0, 0, bounds.width, bounds.height, null); // dispose resources g2d.dispose(); snapshot.flush(); } /** * Refreshes the overview. */ public void updateContent() { Rectangle bounds = getBounds(); if ((bounds.width > 0) && (bounds.height > 0)) { rebuildCachedImage(); } } /** * Rendering. * @param g Graphic context. */ Override public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; Rectangle bounds = getBounds(); super.paint(g2d); if (cachedImage == null) { return; } // draw g2d.drawImage(cachedImage, 0, 0, bounds.width, bounds.height, null); // highlight visible area displayVisibleArea(g2d); } /** * Displays the visible area. * @param g Graphic context. */ protected void displayVisibleArea(Graphics g) { // apply transformer on visible area IlvRect visibleRect = visibleRect(); // transformation view.getTransformer().inverse(visibleRect); transformer.applyFloor(visibleRect); // draw the rectangle g.setColor(visibleAreaColor); g.fillRect((int) visibleRect.x, (int) visibleRect.y, (int) visibleRect.width + 1, (int) visibleRect.height + 1); } } }