package xbneditor;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.lang.Math;
import java.util.*;
import javax.swing.*;
/**
* This class contains all of the information for drawing the
* corresponding blocks on to the panel and allows the user to
* manipulate them.
*
* @author Laura Kruse
* @version v1.7
*/
public class GraphicalBlock extends JPanel {
private Graphics2D g2;
private LinkedList img;
private Rectangle area;
private boolean pressOut = false;
private boolean saved = true;
private Item active;
private Ellipse2D.Double current;
private int mode; // 0 - add, 1 - connect, 2 - delete
private boolean newnode;
final XBN parent;
private Item first;
private Item focus;
private String firstname;
private PopupMenu popup = new PopupMenu();
private MenuItem insert = new MenuItem ("Insert State");
private MenuItem delete = new MenuItem ("Delete State");
private MenuItem remove = new MenuItem ("Delete Node");
private final int width = 80;
private final int height = 50;
private final int boardwidth = 2000;
private final int boardheight = 1000;
/**
* Constructs a new instance of the drawing area with a
* default background and starts out with no nodes that
*
* @param parent the parent frame, this is needed so that
* static updates can be done relative to the location of
* the node that is currently being moved
*/
public GraphicalBlock(XBN frame) {
parent = frame;
mode = 0;
img = new LinkedList();
area = new Rectangle(boardwidth, boardheight);
this.setSize(boardwidth, boardheight);
this.add(popup);
popup.add(insert);
insert.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
parent.redrawEditNodeArea(active,1,false);
}
});
popup.add(delete);
delete.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
parent.deleteDialog((ChanceBlock) active.getItem());
}
});
popup.addSeparator();
popup.add(remove);
remove.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
remove(active);
repaint();
}
});
// probally add the popup menu to the objects that need it
this.setBackground(Color.white);
this.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
if(!pressOut && mode!=1) {
updateLocation(e);
}
}
});
this.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
parent.addundo();
if((e.getModifiers()==InputEvent.BUTTON3_MASK) && (active=getItem(e))!=null) {
popup.show(e.getComponent(),e.getX(),e.getY());
} else {
press(e);
}
}
public void mouseReleased(MouseEvent e) {
release(e);
}
public void mouseClicked(MouseEvent e) {
click(e);
saved = false;
}
});
this.setVisible(true);
}
/**
* Updates the current location of the node that is being
* moved around by the user.
*
* @param e contains the location of the objects current position
*/
public void updateLocation(MouseEvent e) {
if(first!=null) {
first.getItem().setCoordinates(e.getX(),e.getY());
}
current.setFrame(e.getX() - width / 2, e.getY() - height / 2, current.getWidth(), current.getHeight());
checkImage();
//if(checkImage()) {
// XBN.label.setText("Image located at " + current.getX() + ", " + current.getY());
//}
this.repaint();
}
/**
* Repaints the graphical manipulation area
*
* @param g the Graphics that needs to be repainted
*/
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D tmp = (Graphics2D) g;
tmp.setStroke(new BasicStroke());
tmp.setPaint(Color.white);
tmp.fillRect(0, 0, boardwidth, boardheight);
tmp.setPaint(Color.black);
// Draw the lines connecting the nodes first.
Item line;
for(int loc=0;loc<img.size();loc++) {
line = (Item) img.get(loc);
int num;
if((num=line.numChild())!=0) {
for(int i=0;i<num;i++) {
double x1 = line.getItem().getx();
double y1 = line.getItem().gety();
double x2 = line.getChild(i).getItem().getx();
double y2 = line.getChild(i).getItem().gety();
int startx = (int) x1;
int starty = (int) y1;
int endx = (int) x2;
int endy = (int) y2;
drawArc(x1,x2,y1,y2,tmp);
}
}
}
// Now draw the nodes.
Item tmp1;
for(int loc=0;loc<img.size();loc++) {
tmp1 = (Item) img.get(loc);
Ellipse2D.Double tmp2 = tmp1.getEllipse();
String name = tmp1.getItem().getBlockName();
long len = name.length();
tmp.setPaint(new Color(255, 255, 180));
tmp.fill(tmp2);
tmp.setColor(Color.black);
if(len > 10) {
name = name.substring(0, 10);
name = name.concat("...");
len = 10;
}
tmp.setStroke(new BasicStroke(2.0f));
tmp.drawString(name, new Double(tmp2.getX() + 4 * (10 -len)).intValue(), new Double(tmp2.getY() + 30).intValue());
if(tmp2 != current) {
tmp.setStroke(new BasicStroke());
}
tmp.draw(tmp2);
}
/*
Item line;
for(int loc=0;loc<img.size();loc++) {
line = (Item) img.get(loc);
int num;
if((num=line.numChild())!=0) {
for(int i=0;i<num;i++) {
double x1 = line.getItem().getx();
double y1 = line.getItem().gety();
double x2 = line.getChild(i).getItem().getx();
double y2 = line.getChild(i).getItem().gety();
int startx = (int) x1;
int starty = (int) y1;
int endx = (int) x2;
int endy = (int) y2;
drawArc(x1,x2,y1,y2,tmp);
}
}
}
*/
}
private boolean cyclic(String name, Item child) {
if(name.equals(child.getItem().getBlockName())) {
return true;
}
for(int i=0;i<child.numChild();i++) {
if(cyclic(name, child.getChild(i))) {
return true;
}
}
return false;
}
/**
* Checks to make sure that the image is located within the
* boundry of the drawing area. If the user trys to move it
* outside the area then it just redraws it at the nearest edge.
*/
private boolean checkImage() {
// need to do some changing to make sure that the arrows
// don't draw off the screen either
if(area==null) {
return false;
}
if(area.contains(current.x, current.y, width, height)) {
return true;
}
double newx = current.x;
double newy = current.y;
if(current.x + width > area.getWidth()) {
newx = (int) area.getWidth() - width - 1;
}
if(current.x < 0) {
newx = 0;
}
if(current.y + height > area.getHeight()) {
newy = (int) area.getHeight() - height - 1;
}
if(current.y < 0) {
newy = 0;
}
current.setFrame(newx, newy, current.getWidth(), current.getHeight());
focus.getItem().setCoordinates(newx + width / 2, newy + height / 2);
return false;
}
/**
* Sets the drawing type, this allows for deletions and insersions
*
* @param mode the current draw type
*/
public void setMode(int mode) {
this.mode = mode;
}
/**
* Returns the item that was being affected by the most recent
* mouse event.
*
* @param e the Mouse Event that took place
* @return Item - the item that was affected by it
*/
private Item getItem(MouseEvent e) {
Item tmp;
for(int loc=0;loc<img.size();loc++) {
tmp = (Item) img.get(loc);
if(tmp.getEllipse().contains(e.getX(),e.getY())) {
current = tmp.getEllipse();
return tmp;
}
}
return null;
}
/**
* Registers that the mouse has been pressed and
* then runs through the list of images to find out
* which particular node caused the event.
*
* @param e the MouseEvent that took place
*/
private void press(MouseEvent e) {
// check through the list of images
Item tmp;
if((tmp=getItem(e))!=null) {
first = tmp;
firstname = tmp.getItem().getBlockName();
newnode = false;
focus=tmp;
} else if(mode==0) {
ChanceBlock cb;
Ellipse2D.Double ed;
int x;
int y;
x = new Double(e.getX()).intValue();
y = new Double(e.getY()).intValue();
cb = new ChanceBlock("C" + img.size(), x, y, true);
ed = new Ellipse2D.Double(x, y, width, height);
tmp = new Item(cb, ed);
img.add(tmp);
((Item) img.getLast()).getItem().setCoordinates(x, y);
focus = (Item) img.getLast();
current = focus.getEllipse();
first = null;
newnode = true;
}
if(mode!=1) updateLocation(e);
}
/**
* Registeres that the mouse has been released and
* then runs through the list of images to find out
* which particular node(s) caused the event. If the
* mode is set to 1 that means two nodes are being
* connected togeather, and a line needs to be drawn
* to connect them. In addition the parents need to
* be set for the corresponding nodes.
*
* @param e the MouseEvent that took place
*/
private void release(MouseEvent e) {
if(mode==1) {
Item tmp;
if((tmp=getItem(e))!=null) {
// update parent child list
if(firstname!=tmp.getItem().getBlockName() && first!=null) {
if(!cyclic(first.getItem().getBlockName(), tmp)) {
tmp.setParent(first);
first.setChild(tmp);
} else {
JOptionPane.showMessageDialog(null, "Bayesian Networks are not allowed to be cyclic.", "Error", JOptionPane.ERROR_MESSAGE);
}
}
first=null;
this.repaint();
}
} else {
if(current.contains(e.getX(),e.getY())) {
updateLocation(e);
} else {
pressOut = false;
}
}
}
/**
* Registers that the mouse has been clicked.
* This is always preformed after press and
* release are called.
*
* @param e the MouseEvent that took place
*/
private void click(MouseEvent e) {
// find the image and update the edit panel
Item tmp = getItem(e);
if(mode!=2 && tmp!=null) {
tmp.getItem().setCoordinates(e.getX(),e.getY());
current = tmp.getEllipse();
if(newnode) {
parent.redrawEditNodeArea(tmp,0,false);
} else {
parent.redrawEditNodeArea(tmp,0,true);
}
} else {
remove(tmp);
}
}
/**
* This function removes the current item from the Network. The
* node will then no longer exist in the list.
*
* @param tmp the node to be deleted
*/
private void remove(Item tmp) {
// Remove the pointers to the children.
for(int i=0;i<tmp.numParents();i++) {
int loc;
// Check to make sure this item is in the list
if((loc=img.indexOf(tmp.getParent(i))) != -1) {
((Item) img.get(loc)).deleteChild(tmp);
}
}
for(int i=0;i<tmp.numChild();i++) {
int loc;
// Check to make sure this item is in the list
if((loc=img.indexOf(tmp.getChild(i))) != -1) {
((Item) img.get(loc)).deleteParent(tmp);
}
}
// Now that all of the parents and children have been
// taken care of, we can delete the node.
img.remove(tmp);
}
/**
* This function returns a the current Bayesian Network that needs
* to be saved to the disk.
*
* @return img - the current Bayesian Network that is acting as the
* active network
*/
public LinkedList getBayNet() {
return img;
}
/**
* This function takes a network from off the disk and makes it to be
* the active network that the user can modify and change as they
* see fit.
*
* @param llist - a LinkedList representing the new network
*/
public void setBayNet(LinkedList llist) {
img = llist;
if(img.size() > 0) {
active = (Item) img.getFirst();
current = active.getEllipse();
parent.redrawEditNodeArea(active, 0, false);
}
repaint();
}
/**
* This function is responsible for drawing the arcs on the Bayesian
* Network graph. It does all of the calculations that are necessary
* to preform this function.
*
* @param x1 the parents x coordinate
* @param x2 the childs x coordinate
* @param y1 the parents y coordinate
* @param y2 the childs y coordinate
* @param g the Graphics2D that is responsible for drawing the arcs
* onto the palette.
*/
private void drawArc(double x1, double x2, double y1, double y2, Graphics2D g) {
double x;
double y;
double a;
double b;
double d;
double alpha;
double r;
double m;
double n;
double s;
double t;
double e;
double f;
x = x2 - x1;
y = y2 - y1;
a = width / 2;
b = height / 2;
d = 10.0;
alpha = Math.atan(y/x);
r = Math.sqrt((Math.pow(b, 2) * Math.pow(a, 2)) / ((Math.pow(b, 2) * Math.pow(Math.cos(alpha), 2)) + (Math.pow(a, 2) * Math.pow(Math.sin(alpha), 2))));
if(x >= 0) {
m = r * Math.cos(alpha);
n = r * Math.sin(alpha);
s = d * Math.cos(alpha);
t = d * Math.sin(alpha);
e = d / 2.0 * Math.sin(Math.PI - alpha);
f = d / 2.0 * Math.cos(Math.PI - alpha);
} else {
m = -r * Math.cos(alpha);
n = -r * Math.sin(alpha);
s = -d * Math.cos(alpha);
t = -d * Math.sin(alpha);
e = -d / 2.0 * Math.sin(Math.PI - alpha);
f = -d / 2.0 * Math.cos(Math.PI - alpha);
}
g.setStroke(new BasicStroke(2.0f));
g.drawLine((int) (x1 + m), (int) (y1 + n), (int) (x2 - m), (int) (y2 - n));
//drawArrow((int) (x2 - m - s - e), (int) (y2 - n - t - f), (int) (x2 - m - s), (int) (y2 - n - t), (int) (x2 - m), (int) (y2 - n), g);
drawArrow((int) (x2 - m - s - e), (int) (y2 - n - t - f), (int) (x2 - m - s + e), (int) (y2 - n - t + f), (int) (x2 - m), (int) (y2 - n), g);
}
/**
* This method in is charge of the tip of the arrow for the
* nodes in the program. It takes in three coordinate pairs
* and will draw the triangle that is represented by these verticies.
*
* @param x1 the x coordinate of the 1st coordinate pair
* @param x2 the y coordinate of the 1st coordinate pair
* @param y1 the x coordinate of the 2nd coordinate pair
* @param y2 the y coordinate of the 2nd coordinate pair
* @param z1 the x coordinate of the 3rd coordinate pair
* @param z2 the y coordinate of the 3rd coorditate pair
* @param g the graphics that will draw the triangle on the panel
*/
private void drawArrow(int x1, int x2, int y1, int y2, int z1, int z2, Graphics2D g) {
int a1[] = { x1, y1, z1, x1 };
int b1[] = { x2, y2, z2, x2 };
g.fillPolygon(a1, b1, 4);
}
/**
* This function returns whether the file has been modifed since
* it was last saved. If it is still in it's saved state the
* function will return true, otherwise the function will return
* false.
*
* @return saved - a boolean stating whether the current Bayesian
* Network is saved or not
*/
public boolean isSaved() {
return saved;
}
/**
* Sets the Bayesian network to a saved state or not.
*
* @param saved a boolean stating whether the network is saved or not
*/
public void setSaved(boolean saved) {
this.saved = saved;
}
}