/******************************************************************************* * CogTool Copyright Notice and Distribution Terms * CogTool 1.3, Copyright (c) 2005-2013 Carnegie Mellon University * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * CogTool 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. * * CogTool 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 CogTool; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * CogTool makes use of several third-party components, with the * following notices: * * Eclipse SWT version 3.448 * Eclipse GEF Draw2D version 3.2.1 * * Unless otherwise indicated, all Content made available by the Eclipse * Foundation is provided to you under the terms and conditions of the Eclipse * Public License Version 1.0 ("EPL"). A copy of the EPL is provided with this * Content and is also available at http://www.eclipse.org/legal/epl-v10.html. * * CLISP version 2.38 * * Copyright (c) Sam Steingold, Bruno Haible 2001-2006 * This software is distributed under the terms of the FSF Gnu Public License. * See COPYRIGHT file in clisp installation folder for more information. * * ACT-R 6.0 * * Copyright (c) 1998-2007 Dan Bothell, Mike Byrne, Christian Lebiere & * John R Anderson. * This software is distributed under the terms of the FSF Lesser * Gnu Public License (see LGPL.txt). * * Apache Jakarta Commons-Lang 2.1 * * This product contains software developed by the Apache Software Foundation * (http://www.apache.org/) * * jopt-simple version 1.0 * * Copyright (c) 2004-2013 Paul R. Holser, Jr. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Mozilla XULRunner 1.9.0.5 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/. * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * The J2SE(TM) Java Runtime Environment version 5.0 * * Copyright 2009 Sun Microsystems, Inc., 4150 * Network Circle, Santa Clara, California 95054, U.S.A. All * rights reserved. U.S. * See the LICENSE file in the jre folder for more information. ******************************************************************************/ package edu.cmu.cs.hcii.cogtool.view; import java.util.ArrayList; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.draw2d.ColorConstants; import org.eclipse.draw2d.FigureListener; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Label; import org.eclipse.draw2d.LightweightSystem; import org.eclipse.draw2d.RectangleFigure; import org.eclipse.draw2d.TreeSearch; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseMoveListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import edu.cmu.cs.hcii.cogtool.model.ResultStep; import edu.cmu.cs.hcii.cogtool.ui.PERTChartSelectionState; import edu.cmu.cs.hcii.cogtool.uimodel.PERTChartOperatorBar; import edu.cmu.cs.hcii.cogtool.uimodel.PERTChartOperatorBar.PERTStepDependency; import edu.cmu.cs.hcii.cogtool.util.GraphicsUtil; import edu.cmu.cs.hcii.cogtool.util.AlertHandler; import edu.cmu.cs.hcii.cogtool.util.ITimeSliceDisplayable; import edu.cmu.cs.hcii.cogtool.util.PrecisionUtilities; import edu.cmu.cs.hcii.cogtool.util.SWTContextMenuUtil.MenuListener; /** * Canvas class that displays a PERT visualization of PERTChartOperatorBar * objects, which get their data from model.StandaloneAlgo.ModelStep objects. * * Subclasses may want to display additional information within the panel. The * margin values define an interior region in which the visualization may be * drawn, which allows additional data to be drawn outside of that region. The * PERTInteractivePanel draws a timeline in the upper margin, for example. * * @author jbc * */ public class PERTPanel extends Canvas implements ITimeSliceDisplayable { /** * Figure for displaying a transparent blue selection halo * * @author Jason Cornwell * */ public class SelectionHalo extends RectangleFigure { public PERTChartOperatorBar target = null; public FigureListener targetListener = null; public int offset = 0; public Color color = null; public SelectionHalo() { super(); color = new Color(null, GraphicsUtil.getRGBFromColor(0x00CCFF)); setToolTip(new Label("")); setVisible(true); // setCursor(WindowUtil.getCursor(SWT.CURSOR_HAND)); targetListener = new FigureListener() { public void figureMoved(IFigure source) { setBoundsFromFigure(source); } }; } /** * Sets the bounds to be greater than the target figure by 40% of the * height of the target figure in each direction. * * @param figure */ protected void setBoundsFromFigure(IFigure figure) { Rectangle tBounds = figure.getBounds(); offset = 5; //(int) (tBounds.height * 0.4); this.setBounds(new Rectangle(tBounds.x - offset, tBounds.y - offset, tBounds.width + (2 * offset), tBounds.height + (2 * offset))); } public PERTChartOperatorBar getTarget() { return target; } public void dispose() { color.dispose(); //getCursor().dispose(); } /** * Sets the target figure for the halo to appear around * * @param bar */ public void setTarget(PERTChartOperatorBar bar) { if (target != null) { target.removeFigureListener(targetListener); } target = bar; if (target != null) { setBoundsFromFigure(target); // Get tooltip and save it String toolTipText = "Selected Operation"; try { toolTipText = target.getStep().toString(); } catch (NullPointerException ex) { Label targetToolTip = (Label) target.getToolTip(); if (targetToolTip != null) { toolTipText = targetToolTip.getText(); } else { toolTipText = "Selected Operation"; } } //((Label) this.getToolTip()).setText(toolTipText); setToolTip(new Label(toolTipText)); target.addFigureListener(targetListener); } else { offset = 0; this.setBounds(new Rectangle(0, 0, 0, 0)); } } /* (non-Javadoc) * Draws the halo * * @see org.eclipse.draw2d.Figure#paint(org.eclipse.draw2d.Graphics) */ @Override public void paint(Graphics g) { if (target != null) { g.pushState(); try { g.setBackgroundColor(color); g.setAlpha(50); g.fillRoundRectangle(bounds, 2 * offset, 2 * offset); } finally { g.popState(); } } } } /** * List of PERTChartOperatorBar objects to draw */ protected List<PERTChartOperatorBar> bars; protected List<PERTStepDependency> connections; /** * The lightweight system is the interface layer between SWT and draw2d. * */ protected LightweightSystem lws; protected IFigure contents; protected IFigure background; protected List<SelectionHalo> selectionBoxes = new ArrayList<SelectionHalo>(); /** * The number of resources used by the model, which indicates the number of * rows on which PERTChartOperatorBar objects may be drawn */ protected int numRows; /** * Duration of the model run, in ms. */ protected double totalTime; protected double nativeTotalTime; protected double displayStartTime = 0; protected double displayEndTime = 0; /** * Margin values define the interior region in which the visualization is * drawn */ protected int marginTop; protected int marginBottom; protected int marginLeft; protected int marginRight; protected int barHeight; // For recovery of the alert handler protected PERTChartSelectionState chartSelectionState; protected static final Map<String, Integer> colorMap = new HashMap<String, Integer>(); static { colorMap.put(ResultStep.FRAME_RESOURCE, new Integer(0x999999)); colorMap.put(ResultStep.PRODUCTIONS_RESOURCE, new Integer(0xaaaaaa)); colorMap.put(ResultStep.SYSTEM_RESOURCE, new Integer(0xcccccc)); colorMap.put(ResultStep.VISION_RESOURCE, new Integer(0xa24ff7)); colorMap.put(ResultStep.VISION_EXEC_RESOURCE, new Integer(0x450289)); colorMap.put(ResultStep.VISION_PREP_RESOURCE, new Integer(0xa24ff7)); colorMap.put(ResultStep.VISION_ENC_RESOURCE, new Integer(0xd7b9f6)); colorMap.put(ResultStep.MOTOR_RIGHT_EXEC_RESOURCE, new Integer(0xFF3333)); colorMap.put(ResultStep.MOTOR_RIGHT_INIT_RESOURCE, new Integer(0xFF6666)); colorMap.put(ResultStep.MOTOR_RIGHT_PREP_RESOURCE, new Integer(0xFF9999)); colorMap.put(ResultStep.MOTOR_LEFT_EXEC_RESOURCE, new Integer(0xFF3333)); colorMap.put(ResultStep.MOTOR_LEFT_INIT_RESOURCE, new Integer(0xFF6666)); colorMap.put(ResultStep.MOTOR_LEFT_PREP_RESOURCE, new Integer(0xFF9999)); colorMap.put(ResultStep.SPEECH_PREP_RESOURCE, colorMap.get(ResultStep.MOTOR_RIGHT_PREP_RESOURCE)); colorMap.put(ResultStep.SPEECH_EXEC_RESOURCE, colorMap.get(ResultStep.MOTOR_RIGHT_EXEC_RESOURCE)); // TODO (hear kludge) remove this when (if ever?) // we do things correctly with the aural module // of ACT-R. colorMap.put(ResultStep.HEAR_RESOURCE, colorMap.get(ResultStep.VISION_ENC_RESOURCE)); } protected static TreeSearch barFilter = new TreeSearch () { // TODO: we need to be able to select a small neighbor, // but also not lose selection when you click in the halo. public boolean accept(IFigure figure) { return (figure instanceof PERTChartOperatorBar); } public boolean prune(IFigure figure) { return false; } }; /** * Constructor with default margin values of 5 pixels. * * @param parent containing Composite * @param steps List of ResultStep objects to visualize * @param resourceLabels List of Strings describing model resources * @param barHeight height of each PERTChartOperatorBar object when drawn */ public PERTPanel(Composite parent, List<ResultStep> steps, List<String> resourceLabels, int bHeight) { this(parent, steps, resourceLabels, bHeight, 5, 5, 5, 5); } /** * PERTPanel Constructor * * @param parent containing Composite * @param steps List of ResultStep objects to visualize * @param resourceLabels List of Strings describing model resources * @param barHeight height of each PERTChartOperatorBar object when drawn * @param marginT upper margin height, in pixels * @param marginB lower margin height, in pixels * @param marginL left margin width, in pixels * @param marginR right margin width, in pixels */ public PERTPanel(Composite parent, List<ResultStep> steps, List<String> resourceLabels, int bHeight, int marginT, int marginB, int marginL, int marginR) { super(parent, SWT.NONE); // create lightweight system lws = new LightweightSystem(this); contents = new RectangleFigure(); contents.setBackgroundColor(ColorConstants.white); contents.setForegroundColor(ColorConstants.white); ((RectangleFigure) contents).setFill(true); lws.setContents(contents); // set margins marginTop = marginT; marginBottom = marginB; marginLeft = marginL; marginRight = marginR; barHeight = bHeight; numRows = resourceLabels.size(); // create list of PERTChartOperatorBar from the ModelStep list Iterator<ResultStep> stepIterator = steps.iterator(); bars = new ArrayList<PERTChartOperatorBar>(); connections = new ArrayList<PERTStepDependency>(); // TODO: Look into not saving the connections nativeTotalTime = 0.0; ResultStep step; PERTChartOperatorBar bar; // create a map so that we can copy the dependency structure from the // ResultSteps to the PERTChartOperatorBars // TODO: save this state, and use this instead of bars Map<ResultStep, PERTChartOperatorBar> barMap = new HashMap<ResultStep, PERTChartOperatorBar>(); List<PERTChartOperatorBar> operators = new ArrayList<PERTChartOperatorBar>(); // iterate through once and create operator bars while (stepIterator.hasNext()) { step = stepIterator.next(); bar = new PERTChartOperatorBar(step, resourceLabels, barHeight); bar.setColorWithMap(colorMap); barMap.put(step, bar); bars.add(bar); if (bar.isCascade()) { contents.add(bar); } else { operators.add(bar); } // compare to find the total time nativeTotalTime = Math.max(nativeTotalTime, step.startTime + step.duration); } // iterator through operators, and add them now: Iterator<PERTChartOperatorBar> opIter = operators.iterator(); while (opIter.hasNext()) { contents.add(opIter.next()); } // iterate through again setting dependencies on each bar object // based on the dependencies in each ResultStep object ResultStep.ResultStepDependency rStepDep; Iterator<ResultStep.ResultStepDependency> depStepIter; stepIterator = steps.iterator(); while (stepIterator.hasNext()) { step = stepIterator.next(); bar = barMap.get(step); depStepIter = step.getDependencies().iterator(); while (depStepIter.hasNext()) { rStepDep = depStepIter.next(); PERTChartOperatorBar otherBar = barMap.get(rStepDep.dependency); PERTStepDependency dep = bar.addDependency(otherBar, rStepDep); contents.add(dep); connections.add(dep); } } // set initial start and end times to show entire region displayEndTime = nativeTotalTime; displayStartTime = 0.0; // set initial total time to the native time totalTime = nativeTotalTime; addListener(SWT.Resize, new Listener() { public void handleEvent(Event e) { resizeVisualization(e); } }); } /** * Returns the PERTChartOperatorBar under the given coordinates. Note that * this * @param x * @param y * @return */ public PERTChartOperatorBar findBarAt(int x, int y) { return (PERTChartOperatorBar) contents.findFigureAt(x, y, barFilter); // PERTChartOperatorBar bar; // Iterator<PERTChartOperatorBar> barIt = bars.iterator(); // while (barIt.hasNext()) // { // bar = barIt.next(); // if (bar.containsPoint(x, y)) // { // return bar; // } // } // return null; } /** * @return an integer containing the width of the actual drawn * visualization in pixels. This does not include the margins! */ public int getVisualizationWidth() { return getBounds().width - (marginLeft + marginRight); } /** * @return an integer containing the height of the actual drawn * visualization in pixels. This does not include the margins! */ public int getVisualizationHeight() { return getBounds().height - (marginTop + marginBottom); } public int getTopMargin() { return marginTop; } public int getBottomMargin() { return marginBottom; } public int getLeftMargin() { return marginLeft; } public int getRightMargin() { return marginRight; } public void setTotalTime(double tTime) { totalTime = Math.max(nativeTotalTime, tTime); resizeVisualization(null); } public double getNativeTotalTime() { return nativeTotalTime; } /** * Resets the position and size of the PERTChartOperatorBar objects * when the panel is resized. * * @param e resize Event to respond to. */ public void resizeVisualization(Event e) { int totalWidth = getVisualizationWidth(); int totalHeight = getVisualizationHeight(); int regionHeight = PrecisionUtilities.round((totalHeight - 10) / Math.max(numRows, 1)); Iterator<PERTChartOperatorBar> barIterator = bars.iterator(); PERTChartOperatorBar bar; int barW; int barH; int barX; int barY; // rescale the width of each bar based on the ratio of it's duration to // the total time, and set its new position double displayedDuration = displayEndTime - displayStartTime; if ((totalTime > 0) && (displayedDuration > 0)) { while (barIterator.hasNext()) { bar = barIterator.next(); // Calculate position based on the position of the right of the // bar to avoid oscillating widths int barRightPos = marginLeft + PrecisionUtilities.round(((bar.getStartTime() + bar.getDuration() - displayStartTime) / displayedDuration) * (totalWidth - 10)); // // barW = PrecisionUtilities.round((bar.getDuration() * (totalWidth - 10)) // / displayedDuration) + 1; barX = marginLeft + PrecisionUtilities.round(((bar.getStartTime() - displayStartTime) / displayedDuration) * (totalWidth - 10)); barW = barRightPos - barX + 1; if (bar.isCascade()) { int y1 = Math.min(bar.getRow(), bar.getEndRow()); int y2 = Math.max(bar.getRow(), bar.getEndRow()); barY = marginTop + (y1 * regionHeight) + PrecisionUtilities.round(0.5 * barHeight); barH = (y2 - y1) * regionHeight; if (barW < 1) { barW = 1; } } else { barY = marginTop + (bar.getRow() * regionHeight); barH = barHeight; if (barW < 3) { barX -= 1; barW = 3; } } bar.setBounds(new Rectangle(barX, barY, barW, barH)); } } lws.getUpdateManager().performUpdate(); } /** * @return List of PERTChartOperatorBar objects that are being displayed */ public List<PERTChartOperatorBar> getOperatorBarList() { return bars; } /** * @return total time of the trace being visualized, in ms */ public double getTotalTime() { return totalTime; } /** * Add a mouse handler for mouse move and click events * * @param mouseState */ public void addMouseHandler(MenuListener mouseState, MouseMoveListener moveListener) { if (mouseState != null) { addMouseListener(mouseState); } if (moveListener != null) { addMouseMoveListener(moveListener); } } /** * Associates this panel with a PERTChartSelectionState so that when a new * operator is selected, this panel will redraw itself accordingly. * * @param selectionState */ public void observeSelectionState(PERTChartSelectionState selectionState) { chartSelectionState = selectionState; AlertHandler handler = new AlertHandler() { public void handleAlert(EventObject alert) { // TODO: Change this to map or set List<ResultStep> selectedSteps = ((PERTChartSelectionState.SelectionChange) alert).selectedSteps; // clear selection boxes Iterator<SelectionHalo> haloIterator = selectionBoxes.iterator(); SelectionHalo deadHalo = null; while (haloIterator.hasNext()) { deadHalo = haloIterator.next(); contents.remove(deadHalo); deadHalo.dispose(); } selectionBoxes.clear(); Iterator<PERTChartOperatorBar> barIterator = bars.iterator(); PERTChartOperatorBar bar; while (barIterator.hasNext()) { bar = barIterator.next(); if (selectedSteps.contains(bar.getStep())) { bar.setSelected(true); SelectionHalo halo = new SelectionHalo(); halo.setTarget(bar); contents.add(halo); selectionBoxes.add(halo); } else { bar.setSelected(false); } } //redraw(); contents.repaint(); } }; chartSelectionState.addHandler(this, PERTChartSelectionState.SelectionChange.class, handler); } @Override public void dispose() { //iterate through PERTChartOperatorBars and dispose them Iterator<PERTChartOperatorBar> pIterator = bars.iterator(); while (pIterator.hasNext()) { pIterator.next().dispose(); } Iterator<SelectionHalo> haloIter = selectionBoxes.iterator(); while (haloIter.hasNext()) { haloIter.next().dispose(); } chartSelectionState.removeAllHandlers(this); super.dispose(); } public void figureMoved(IFigure arg0) { // TODO Auto-generated method stub } public void displayTimeSlice(double start, double end) { displayStartTime = start; displayEndTime = end; resizeVisualization(null); } public void setResultSteps(List<ResultStep> steps, List<String> resourceLabels) { // First remove seleciton halos Iterator<SelectionHalo> haloIterator = selectionBoxes.iterator(); SelectionHalo deadHalo = null; while (haloIterator.hasNext()) { deadHalo = haloIterator.next(); contents.remove(deadHalo); deadHalo.dispose(); } selectionBoxes.clear(); // Then remove existing operator bars Iterator<PERTChartOperatorBar> barIt = bars.iterator(); PERTChartOperatorBar deadBar = null; while (barIt.hasNext()) { deadBar = barIt.next(); Iterator<PERTStepDependency> depIt = deadBar.getDependencies().iterator(); while (depIt.hasNext()) { PERTStepDependency deadDependency = depIt.next(); contents.remove(deadDependency); } contents.remove(deadBar); //deadBar.dispose(); } bars.clear(); Iterator<ResultStep> stepIterator = steps.iterator(); ResultStep step; PERTChartOperatorBar bar; // create a map so that we can copy the dependency structure from the // ResultSteps to the PERTChartOperatorBars // TODO: save this state, and use this instead of bars Map<ResultStep, PERTChartOperatorBar> barMap = new HashMap<ResultStep, PERTChartOperatorBar>(); List<PERTChartOperatorBar> operators = new ArrayList<PERTChartOperatorBar>(); // iterate through once and create operator bars while (stepIterator.hasNext()) { step = stepIterator.next(); bar = new PERTChartOperatorBar(step, resourceLabels, barHeight); bar.setColorWithMap(colorMap); barMap.put(step, bar); bars.add(bar); if (bar.isCascade()) { contents.add(bar); } else { operators.add(bar); } // coompare to find the total time totalTime = Math.max(totalTime, step.startTime + step.duration); } // iterator through operators, and add them now: Iterator<PERTChartOperatorBar> opIter = operators.iterator(); while (opIter.hasNext()) { contents.add(opIter.next()); } // iterate through again setting dependencies on each bar object // based on the dependencies in each ResultStep object ResultStep.ResultStepDependency rStepDep; Iterator<ResultStep.ResultStepDependency> depStepIter; stepIterator = steps.iterator(); double ntvTotalTime = 0.0d; while (stepIterator.hasNext()) { step = stepIterator.next(); bar = barMap.get(step); depStepIter = step.getDependencies().iterator(); ntvTotalTime = Math.max(ntvTotalTime, step.startTime + step.duration); while (depStepIter.hasNext()) { rStepDep = depStepIter.next(); PERTChartOperatorBar otherBar = barMap.get(rStepDep.dependency); PERTStepDependency dep = bar.addDependency(otherBar, rStepDep); contents.add(dep); connections.add(dep); } } nativeTotalTime = ntvTotalTime; totalTime = nativeTotalTime; numRows = resourceLabels.size(); resizeVisualization(null); } }