You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

552 lines
18 KiB

//==============================================================================
//
// Copyright (c) 2002-
// Authors:
// * Andrew Hinton <ug60axh@cs.bham.ac.uk> (University of Birmingham)
// * Mark Kattenbelt <mark.kattenbelt@comlab.ox.ac.uk> (University of Oxford, formerly University of Birmingham)
// * Dave Parker <david.parker@comlab.ox.ac.uk> (University of Oxford, formerly University of Birmingham)
//
//------------------------------------------------------------------------------
//
// This file is part of PRISM.
//
// PRISM is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// PRISM 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with PRISM; if not, write to the Free Software Foundation,
// Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//==============================================================================
package userinterface.simulator;
import simulator.*;
import prism.*;
import userinterface.util.*;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.font.*;
public class GUISimulatorPathTable extends GUIGroupedTable
{
private SimulatorEngine engine;
private GUISimulator simulator;
// Table model
private GUISimulator.PathTableModel ptm;
// Component on left hand side to show path loops
private JList loopIndicator;
private LoopIndicatorListModel loopIndicatorModel;
/** Creates a new instance of GUISimulatorPathTable */
public GUISimulatorPathTable(GUISimulator simulator, GUISimulator.PathTableModel ptm, SimulatorEngine engine)
{
super(ptm);
this.ptm = ptm;
this.simulator = simulator;
this.engine = engine;
// Table
setColumnSelectionAllowed(false);
getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
setDefaultRenderer(Object.class, new PathChangeTableRenderer(true));
// Loop indicator
loopIndicatorModel = new LoopIndicatorListModel();
loopIndicator = new JList(loopIndicatorModel);
loopIndicator.setBackground(new JPanel().getBackground());
loopIndicator.setFixedCellWidth(25);
loopIndicator.setFixedCellHeight(getRowHeight());
loopIndicator.setCellRenderer(new LoopIndicatorRenderer(this));
}
/** Override set font to set row height(s) */
public void setFont(Font font)
{
super.setFont(font);
setRowHeight(getFontMetrics(font).getHeight() + 4);
if (loopIndicator != null) loopIndicator.setFixedCellHeight(getRowHeight());
}
public boolean usingChangeRenderer()
{
return ((PathChangeTableRenderer)getDefaultRenderer(Object.class)).onlyShowChange();
}
public void switchToChangeRenderer()
{
setDefaultRenderer(Object.class, new PathChangeTableRenderer(true));
repaint();
}
public void switchToBoringRenderer()
{
setDefaultRenderer(Object.class, new PathChangeTableRenderer(false));
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
loopIndicatorModel.updateIndicator();
}
public Component getPathLoopIndicator()
{
return loopIndicator;
}
// Cell renderer for list representing loop indicator (left of path table)
class LoopIndicatorRenderer extends JPanel implements ListCellRenderer
{
boolean startLoop, midLoop, endLoop;
LoopIndicatorRenderer(JTable table)
{
/*JTableHeader header = table.getTableHeader();
setOpaque(true);
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
setHorizontalAlignment(CENTER);
setForeground(header.getForeground());
setBackground(header.getBackground());
setFont(header.getFont());*/
}
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus)
{
//setText((value == null) ? "" : value.toString());
//setBorder(new LineBorder(Color.black, 1));
if(ptm.isPathLooping())
{
if(index == ptm.getLoopEnd() && index == ptm.getLoopStart())
{
startLoop = true;
endLoop = true;
midLoop = false;
}
else if(index == ptm.getLoopEnd())
{
startLoop = false;
midLoop = false;
endLoop = true;
}
else if(index == ptm.getLoopStart())
{
startLoop = true;
midLoop = false;
endLoop = false;
}
else if(index > ptm.getLoopStart() && index < ptm.getLoopEnd())
{
startLoop = false;
midLoop = true;
endLoop = false;
}
else
{
startLoop = false;
midLoop = false;
endLoop = false;
}
}
else
{
startLoop = false;
midLoop = false;
endLoop = false;
}
return this;
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(getBackground());
g2.fillRect(0,0,getWidth(), getHeight());
if (!ptm.isDisplayPathLoops()) return;
g2.setColor(Color.black);
if(startLoop && endLoop)
{
GeneralPath gp = new GeneralPath();
gp.moveTo(getWidth(), (getHeight()/2)+5);
gp.lineTo((getWidth()/2)+5, (getHeight()/2)+5);
gp.quadTo(getWidth()/2, (getHeight()/2)+5, getWidth()/2, (getHeight()/2));
gp.quadTo(getWidth()/2, (getHeight()/2)-5, (getWidth()/2)+5, (getHeight()/2)-5);
gp.lineTo((getWidth()), (getHeight()/2)-5);
g2.draw(gp);
gp = new GeneralPath();
gp.moveTo(getWidth(), (getHeight()/2)-5);
gp.lineTo(getWidth()-5, (getHeight()/2) - 8);
gp.lineTo(getWidth()-5, (getHeight()/2) - 2);
gp.closePath();
g2.fill(gp);
}
else if(startLoop)
{
GeneralPath gp = new GeneralPath();
gp.moveTo(getWidth(), getHeight()/2);
gp.lineTo((getWidth()/2)+5, getHeight()/2);
gp.quadTo(getWidth()/2, getHeight()/2, getWidth()/2, (getHeight()/2)+5);
gp.lineTo(getWidth()/2, getHeight());
g2.draw(gp);
gp = new GeneralPath();
gp.moveTo(getWidth(), getHeight()/2);
gp.lineTo(getWidth()-5, (getHeight()/2) - 3);
gp.lineTo(getWidth()-5, (getHeight()/2) + 3);
gp.closePath();
g2.fill(gp);
}
else if(midLoop)
{
g2.drawLine(getWidth()/2, 0, getWidth()/2, getHeight());
}
else if(endLoop)
{
GeneralPath gp = new GeneralPath();
gp.moveTo(getWidth(), getHeight()/2);
gp.lineTo((getWidth()/2)+5, getHeight()/2);
gp.quadTo(getWidth()/2, getHeight()/2, getWidth()/2, (getHeight()/2)-5);
gp.lineTo(getWidth()/2, 0);
g2.draw(gp);
}
}
}
// Model for list representing loop indicator
class LoopIndicatorListModel extends AbstractListModel
{
public Object getElementAt(int index)
{
return "";
}
public int getSize()
{
return ptm.getRowCount();
}
public void updateIndicator()
{
fireContentsChanged(this, 0, ptm.getRowCount());
}
}
// Renderer for cells in path table
class PathChangeCellRenderer extends JPanel
{
private PathChangeTableRenderer pctr;
private Object value;
private String stringValue;
private boolean isSelected;
private int row;
public PathChangeCellRenderer(PathChangeTableRenderer pctr, Object value, boolean isSelected, int row)
{
super();
this.pctr = pctr;
this.value = value;
this.isSelected = isSelected;
this.row = row;
if (value instanceof String)
{
stringValue = (String)value;
this.setToolTipText("State " + row);
}
else if (value instanceof GUISimulator.ActionValue)
{
GUISimulator.ActionValue actionValue = (GUISimulator.ActionValue)value;
if (actionValue.isActionValueUnknown())
{
// unused:
stringValue = "?";
this.setToolTipText("Module name or [action] label for transition from state " + (row - 1) + " to " + (row) + " (not yet known)");
}
else
{
stringValue = actionValue.getValue();
String tooltip;
if (row == 0) {
tooltip = null;
} else {
tooltip = "Module name or [action] label for transition from state " + (row - 1) + " to " + (row);
}
this.setToolTipText(tooltip);
}
}
else if (value instanceof GUISimulator.TimeValue)
{
GUISimulator.TimeValue timeValue = (GUISimulator.TimeValue)value;
if (timeValue.isTimeValueUnknown())
{
stringValue = "?";
if (timeValue.isCumulative())
this.setToolTipText("Cumulative time up until entering state " + (row) + " (not yet known)");
else
this.setToolTipText("Time spent in state " + (row) + " (not yet known)");
}
else
{
stringValue = (simulator.formatDouble((Double)timeValue.getValue()));
if (timeValue.isCumulative())
this.setToolTipText("Cumulative time up until entering state " + (row));
else
this.setToolTipText("Time spent in state " + (row));
}
}
else if (value instanceof GUISimulator.VariableValue)
{
GUISimulator.VariableValue variableValue = (GUISimulator.VariableValue)value;
stringValue = (variableValue.getValue() instanceof Double) ? (simulator.formatDouble(((Double)variableValue.getValue()))) : variableValue.getValue().toString();
this.setToolTipText("Value of variable \"" + variableValue.getVariable().getName() + "\" in state " + (row));
}
else if (value instanceof GUISimulator.RewardStructureValue)
{
GUISimulator.RewardStructureValue rewardValue = (GUISimulator.RewardStructureValue)value;
String rewardName = rewardValue.getRewardStructureColumn().getRewardStructure().getColumnName();
if (rewardValue.isRewardValueUnknown())
{
stringValue = "?";
if (rewardValue.getRewardStructureColumn().isCumulativeReward())
this.setToolTipText("Cumulative reward of reward structure " + rewardName + " up until state " + (row) + " (not yet known)");
if (rewardValue.getRewardStructureColumn().isStateReward())
this.setToolTipText("State reward of reward structure " + rewardName + " in state " + (row) + " (not yet known)");
if (rewardValue.getRewardStructureColumn().isTransitionReward())
this.setToolTipText("Transition reward of reward structure " + rewardName + " from state " + (row) + " to " + (row + 1) + " (not yet known)");
}
else
{
stringValue = simulator.formatDouble(rewardValue.getRewardValue());
if (rewardValue.getRewardStructureColumn().isCumulativeReward())
this.setToolTipText("Cumulative reward of reward structure " + rewardName + " up until state " + (row));
if (rewardValue.getRewardStructureColumn().isStateReward())
this.setToolTipText("State reward of reward structure " + rewardName + " in state " + (row));
if (rewardValue.getRewardStructureColumn().isTransitionReward())
this.setToolTipText("Transition reward of reward structure " + rewardName + " from state " + (row) + " to " + (row + 1));
}
}
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Rectangle rect;
int width, height, x, y;
// Get graphics context
Graphics2D g2 = (Graphics2D)g;
// Get some info about the string
rect = getStringBounds(stringValue, g2);
width = (int)Math.ceil(rect.getWidth());
height = (int)Math.ceil(rect.getHeight());
// State index/action
if (value instanceof String || value instanceof GUISimulator.ActionValue) {
// Position (horiz centred, vert centred)
x = (getWidth()/2) - (width/2);
y = (getHeight()/2) + (height/2);
// Write value
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.setColor(Color.black);
g2.drawString(stringValue, x, y);
}
// Variable value
else if (value instanceof GUISimulator.VariableValue) {
GUISimulator.VariableValue variableValue = (GUISimulator.VariableValue)value;
// Position (horiz centred, vert centred)
x = (getWidth()/2) - (width/2);
y = (getHeight()/2) + (height/2);
// Prepare box/colour
RoundRectangle2D.Double rec = new RoundRectangle2D.Double(x-5, 2, width+10, getHeight()-5, 8, 8);
Color color = (variableValue.hasChanged()) ? (Color.black) : (Color.lightGray);
// "Render changes" view
if (pctr.onlyShowChange()) {
// Vertical line in background
g2.setColor(Color.black);
g2.drawLine(getWidth()/2, 0, getWidth()/2, getHeight());
// Only display box/value if there was a change
if (isSelected || variableValue.hasChanged()) {
g2.setColor(Color.white);
g2.fill(rec);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.draw(rec);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.drawString(stringValue, x, y);
}
}
// "Render all values" view
else {
// Just display value
g2.setColor(color);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.drawString(stringValue, x, y);
}
}
// Reward value
else if (value instanceof GUISimulator.RewardStructureValue) {
GUISimulator.RewardStructureValue rewardValue = (GUISimulator.RewardStructureValue)value;
// Default case (everything except cumulative for CTMCs)
if (!(ptm.getView().canShowTime() && rewardValue.getRewardStructureColumn().isCumulativeReward())) {
// Position (horiz centred, vert centred)
x = (getWidth()/2) - (width/2);
y = (getHeight()/2) + (height/2);
// Prepare box/colour
RoundRectangle2D.Double rec = new RoundRectangle2D.Double(x-5, 2, width+10, getHeight()-5, 8, 8);
Color color = (rewardValue.hasChanged() || rewardValue.isRewardValueUnknown()) ? (Color.black) : (Color.lightGray);
// "Render changes" view
if (pctr.onlyShowChange()) {
// Vertical line in background
g2.setColor(Color.black);
g2.drawLine(getWidth()/2, 0, getWidth()/2, getHeight());
// Only display box/value if there was a change
if ((isSelected || rewardValue.hasChanged())) {
g2.setColor(Color.white);
g2.fill(rec);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(color);
g2.draw(rec);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.drawString(stringValue, x, y);
}
}
// "Render all values" view
else {
// Just display value
g2.setColor(color);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.drawString(stringValue, x, y);
}
}
// For cumulative rewards on CTMCs, we left-align (like for display of time)
else {
// Position (left aligned, vert centred)
x = 3;
y = (getHeight()/2) + (height/2);
// Write text
g2.setColor(Color.black);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.drawString(stringValue, x, y);
}
}
// Time value
else if (value instanceof GUISimulator.TimeValue)
{
// Position (left aligned, vert centred)
x = 3;
y = (getHeight()/2) + (height/2);
// Write text
g2.setColor(Color.black);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
g2.drawString(stringValue, x, y);
}
}
}
class PathChangeTableRenderer implements TableCellRenderer
{
private boolean onlyShowChange;
private Color defaultColor;
private Color selectedColor;
private Color labelColor;
private Color selectedLabelColor;
public PathChangeTableRenderer(boolean onlyShowChange)
{
super();
this.onlyShowChange = onlyShowChange;
defaultColor = Color.white;
selectedColor = new Color(defaultColor.getRed()-20, defaultColor.getGreen()-20, defaultColor.getBlue());
selectedLabelColor = new Color(selectedColor.getRed()-20, selectedColor.getGreen(), selectedColor.getBlue()-20);
labelColor = new Color(defaultColor.getRed()-50, defaultColor.getGreen(), defaultColor.getBlue()-50);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column)
{
PathChangeCellRenderer pctr = new PathChangeCellRenderer(this, value, isSelected, row);
boolean shouldColourRow = ptm.shouldColourRow(row);
Color backGround = defaultColor;
if(isSelected && !shouldColourRow)
backGround = selectedColor;
else if (isSelected && shouldColourRow)
backGround = selectedLabelColor;
else if (!isSelected && shouldColourRow)
backGround = labelColor;
pctr.setBackground(backGround);
return pctr;
}
public boolean onlyShowChange()
{
return onlyShowChange;
}
}
/** Method which computes rectangle bounds of a string for a given Graphics2D object
*/
public static Rectangle getStringBounds(String s, Graphics2D g2)
{
// catch special cases...
// ...TextLayout constructor crashes with null or zero-length string
if (s == null) return new Rectangle(0, 0);
if (s.length() == 0) return new Rectangle(0, 0);
TextLayout layout = new TextLayout(s, g2.getFont(), g2.getFontRenderContext());
return layout.getOutline(new AffineTransform()).getBounds();
}
/** Method which computes width of a string for a given Graphics2D object
*/
public static double getStringWidth(String s, Graphics2D g2)
{
return getStringBounds(s, g2).getWidth();
}
/** Method which computes height of a string for a given Graphics2D object
*/
public static double getStringHeight(String s, Graphics2D g2)
{
return getStringBounds(s, g2).getHeight();
}
}