package com.drawbridge.vl;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import com.drawbridge.Activity;
import com.drawbridge.correspondance.RadialSpotlight;
import com.drawbridge.correspondance.Spotlight;
import com.drawbridge.correspondance.Spotlightable;
import com.drawbridge.dm.DMSimplePanel;
import com.drawbridge.jsengine.JsEngine;
import com.drawbridge.jsengine.ast.DBParser;
import com.drawbridge.jsengine.ast.Evaluator;
import com.drawbridge.jsengine.ast.NumberLiteralEvaluator;
import com.drawbridge.jsengine.jsobjects.JSNumber;
import com.drawbridge.jsengine.jsobjects.JSType;
import com.drawbridge.text.DocumentError;
import com.drawbridge.text.TextPanel;
import com.drawbridge.utils.Utils;
import com.drawbridge.vl.PopupDiallerPrimitive.DiallerListener;
import com.drawbridge.vl.PopupSelectorOperator.PopupSelectorListener;
import com.drawbridge.vl.blocks.Block;
import com.drawbridge.vl.blocks.BlockFunctionCall;
import com.drawbridge.vl.blocks.BlockFunctionDefinition;
import com.drawbridge.vl.blocks.BlockIdentifier;
import com.drawbridge.vl.blocks.BlockOperator;
import com.drawbridge.vl.blocks.BlockPrimitive;
public class VLCanvas extends JPanel implements DiallerListener, Spotlightable, PopupSelectorListener
{
public Grid mViewGrid = new Grid();
public DocumentError mDocumentError = null;
public HashMap<Integer, Integer> mIndentedLines;
public static final int LINE_SEPERATOR = 10;
public static final int LINE_HEIGHT = 55;
public static final int BORDER_LEFT = 10;
public static final int BORDER_INDENT = 40;
public final boolean mDrawLines = true;
public Dimension mPreferredSize = new Dimension(500, 500);
private VLModel mModel;
private PopupDiallerPrimitive mPrimitiveDialler = null;
private Double mInitialDiallerValue;
private BlockPrimitive mDiallerBlock = null;
private BlockOperator mOperatorBlock = null;
private PopupSelectorOperator mPopupSelectorOp = null;
private PopupInfoMessage mPopupInfoMessage = null;
private static final long serialVersionUID = 1L;
private int mHighlightedLine = -1;
private int mHighlightedXPos = -10;
public int mCaretLine = -1;
private HashSet<RadialSpotlight> mSpotlights = new HashSet<RadialSpotlight>();
private boolean mDimBackground = false;
private LinkedList<Block> mBlocksToHighlight = null;
private Lock gridLock = new ReentrantLock();
public VLCanvas() {
mIndentedLines = new HashMap<Integer, Integer>();
synchronized (mViewGrid)
{
while (mViewGrid.size() <= 10)
{
mViewGrid.add(new LinkedList<Block>());
}
}
mModel = new VLModel(this);
this.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent me)
{
VLCanvas.this.requestFocus();
}
});
this.addMouseMotionListener(new MouseAdapter()
{
@Override
public void mouseMoved(MouseEvent me)
{
// Make Dialler Invisible
MouseEvent diallerEvent = SwingUtilities.convertMouseEvent(me.getComponent(), me, mPrimitiveDialler);
if (mPrimitiveDialler != null && mPrimitiveDialler.getParent() != null && !mPrimitiveDialler.contains(diallerEvent.getX(), diallerEvent.getY()))
{
if (mDiallerBlock != null && !mDiallerBlock.contains(me.getX(), me.getY()))
mPrimitiveDialler.setVisible(false);
}
MouseEvent popupSelectorEvent = SwingUtilities.convertMouseEvent(me.getComponent(), me, mPopupSelectorOp);
if(mPopupSelectorOp != null && mPopupSelectorOp.getParent() != null && !mPopupSelectorOp.contains(popupSelectorEvent.getX(), popupSelectorEvent.getY()))
{
if(mOperatorBlock != null && !mOperatorBlock.contains(me.getX(), me.getY()))
mPopupSelectorOp.setVisible(false);
}
// Do error popup on hover
if (mDocumentError != null)
{
int line = mDocumentError.lineNumber;
int width = VLCanvas.this.getWidth() - 4;
int y = LINE_SEPERATOR + (line * (LINE_HEIGHT + LINE_SEPERATOR));
if (line < mIndentedLines.size()) {
Rectangle errorRect = new Rectangle(BORDER_LEFT + (BORDER_INDENT * (int) mIndentedLines.get(line)), y, width, LINE_HEIGHT);
if (errorRect.contains(me.getX(), me.getY()))
{
// Show the Error Popup
Point activityPoint = SwingUtilities.convertPoint(me.getComponent(), me.getX(), me.getY(), Activity.getInstance());
Activity.getInstance().showErrorPopup(mDocumentError.errorTitle, mDocumentError.errorDescription, activityPoint);
}
else
{
Activity.getInstance().hideTipWindow();
}
}
}
else
{
// Hide the Error Popup
Activity.getInstance().hideTipWindow();
}
}
});
this.setLayout(null);
mPrimitiveDialler = new PopupDiallerPrimitive(this);
add(mPrimitiveDialler);
mPopupSelectorOp = new PopupSelectorOperator(this);
add(mPopupSelectorOp);
mPopupInfoMessage = new PopupInfoMessage();
add(mPopupInfoMessage);
mPopupSelectorOp.setVisible(false);
mPopupSelectorOp.setSize(mPopupSelectorOp.getPreferredSize());
mPrimitiveDialler.setVisible(false);
mPrimitiveDialler.setSize(mPrimitiveDialler.getPreferredSize());
addComponentListener(mCanvasComponentListener);
displayInfoMessage("Testing");
}
ComponentListener mCanvasComponentListener = new ComponentAdapter(){
@Override
public void componentResized(ComponentEvent e)
{
mPopupInfoMessage.setLocation(5, 5);
mPopupInfoMessage.setSize(e.getComponent().getWidth() - 10, 40);
}
};
public void addBlockToCanvasAndGrid(Block block, int xPos, int yPos)
{
int lineIndex = 0;
synchronized (mViewGrid)
{
int closestIndex = 0; //remove this when removing trys
try{
add(block);
block.addComponentListener(new ComponentAdapter()
{
@Override
public void componentResized(ComponentEvent arg0)
{
VLCanvas.this.validateGridLayout();
}
});
lineIndex = (yPos - LINE_SEPERATOR) / (LINE_HEIGHT + LINE_SEPERATOR);
// Make sure the grid location exists
while (mViewGrid.size() <= lineIndex)
{
mViewGrid.add(new LinkedList<Block>());
}
// Find Nearest X Location & Add to End of List
closestIndex = getNearestXBlockLocation(lineIndex, xPos);
}
catch(NullPointerException e){
Utils.out.println(getClass(), "Caught NullPointerException in first Catch!");
}
try
{
LinkedList<Block> line = mViewGrid.get(lineIndex);
line.add(closestIndex, block);
mModel.onGridUpdate(mViewGrid);
validateGridLayout();
} catch (NullPointerException e)
{
Utils.out.println(getClass(), "Caught NullPointerException in second Catch!");
e.printStackTrace();
Utils.out.println("Attempted to access:" + lineIndex);
Utils.out.println("Size was:" + mViewGrid.size());
Utils.out.println("ClosestIndex:" + closestIndex);
Utils.out.println("Block:" + ((block == null) ? "null" : block.toString()));
Object[] objs = mViewGrid.toArray();
for (Object obj : objs)
{
String answer = ((obj == null) ? "null" : obj.toString());
Utils.out.println(answer);
}
}
}
// Set Corresponding Caret Position in TextPanel
if (TextPanel.hasInstance())
{
Point nearestChar = TextPanel.getInstance().getDocument().getDocumentModel().getCharPosFromMouseClick(xPos, lineIndex);
TextPanel.getInstance().getDocument().setCaretPosition(nearestChar.x, nearestChar.y);
TextPanel.getInstance().getDocument().repaint();
this.mCaretLine = lineIndex;
}
}
public void updateGrid()
{
synchronized (mViewGrid)
{
mModel.onGridUpdate(mViewGrid);
}
}
public VLModel getModel()
{
return mModel;
}
public int getNearestXBlockLocation(int lineIndex, int xPos)
{
synchronized (mViewGrid)
{
if (mViewGrid.size() > lineIndex && lineIndex >= 0)
{
LinkedList<Block> line = mViewGrid.get(lineIndex);
int closestIndex = 0;
if (line != null)
{
for (int i = 0; i <= line.size() && line.size() > 0; i++)
{
int diff;
if (i < line.size())
diff = Math.abs(line.get(i).getX() - xPos);
else
diff = Math.abs(line.get(i - 1).getX() + line.get(i - 1).getWidth() - xPos);
int diffClosest = Math.abs(line.get(closestIndex).getX() - xPos);
if (diff < diffClosest)
closestIndex = i;
}
}
return closestIndex;
} else
return mViewGrid.size() - 1;
}
}
@Override
public Dimension getPreferredSize()
{
return mPreferredSize;
}
@Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Draw gray line markers
if (mDrawLines)
{
int width = getWidth() - 4;
int noOfLines = (getHeight() - LINE_SEPERATOR) / LINE_HEIGHT;
for (int i = 0; i < noOfLines; i++)
{
if (mDocumentError != null && mDocumentError.lineNumber == i)
{
g2.setColor(Color.decode("#FF0000"));
}
else if (i == mCaretLine)
{
g2.setColor(Color.decode("#E8F2FF"));
}
else if (i == mHighlightedLine)
{
g2.setColor(Color.decode("#EEEEEE"));
}
else
g2.setColor(Color.decode("#F7F6E8"));
int y = LINE_SEPERATOR + (i * (LINE_HEIGHT + LINE_SEPERATOR));
if (mIndentedLines.get(i) != null)
{
g2.fillRoundRect((BORDER_LEFT + (BORDER_INDENT * (int) mIndentedLines.get(i))), y, width, LINE_HEIGHT, 30, 30);
}
else
g2.fillRoundRect(BORDER_LEFT, y, width, LINE_HEIGHT, 30, 30);
}
}
if (mDimBackground)
{
g2.setColor(new Color(220, 220, 220));
g2.fillRect(0, 0, getWidth() - 1, getHeight() - 1);
if (mSpotlights != null && mSpotlights.size() > 0)
{
for (RadialSpotlight p : mSpotlights)
{
p.paintRadialSpotlight(g);
}
}
}
// Add highlighter to show where block is being dropped
if (mHighlightedXPos > 0 && mHighlightedLine > -1)
{
g2.setColor(Color.yellow);
if (mIndentedLines.get(mHighlightedLine) != null)
mHighlightedXPos = Math.max(mHighlightedXPos - 5, BORDER_LEFT + (BORDER_INDENT * (int) mIndentedLines.get(mHighlightedLine)));
g2.fillRect(mHighlightedXPos, LINE_SEPERATOR + (mHighlightedLine * (LINE_HEIGHT + LINE_SEPERATOR)), 10, LINE_HEIGHT);
}
}
public void removeFromGrid(Block block)
{
synchronized (mViewGrid)
{
for (LinkedList<Block> line : mViewGrid)
{
if (line != null && line.contains(block))
{
line.remove(block);
}
}
mModel.onGridUpdate(mViewGrid);
}
}
public void resetDragPosition()
{
mHighlightedLine = -1;
}
// Allows VD to update line highlighting.
public void notifyOfDragPosition(int x, int y)
{
synchronized (mViewGrid)
{
int index = -1;
if (this.contains(x, y))
{
index = (y - VLCanvas.LINE_SEPERATOR) / (VLCanvas.LINE_HEIGHT + VLCanvas.LINE_SEPERATOR);
}
if (mViewGrid.size() > index && index >= 0)
{
mHighlightedLine = index;
int highlightedIndex = getNearestXBlockLocation(index, x);
if (mViewGrid.get(index).size() > 0 && highlightedIndex < mViewGrid.get(index).size())
mHighlightedXPos = mViewGrid.get(index).get(highlightedIndex).getX();
else if (highlightedIndex == mViewGrid.get(index).size() && mViewGrid.get(index).size() != 0)
mHighlightedXPos = mViewGrid.get(index).getLast().getX() + mViewGrid.get(index).getLast().getWidth();
else if (highlightedIndex == 0)
mHighlightedXPos = 10;
SwingUtilities.invokeLater(new Runnable()
{
@Override
public void run()
{
VLPanel.getInstance().repaint();
}
});
}
}
}
public void validateGridLayout()
{
mIndentedLines.clear();
int noOfBlocksInGrid = 0;
synchronized (mViewGrid)
{
int lastIndentValue = 0;
int i = 0;
for (LinkedList<Block> line : mViewGrid)
{
noOfBlocksInGrid += line.size();
int blockYPos = LINE_SEPERATOR + ((LINE_HEIGHT + LINE_SEPERATOR) * i);
// Set X Position
int blockXPos = BORDER_LEFT + (lastIndentValue * BORDER_INDENT);
mIndentedLines.put(i, lastIndentValue);
if (line.size() == 0 && lastIndentValue > 0)
{
// Reduce indent for next line.
lastIndentValue--;
}
else if (Utils.containsClassType(line, BlockFunctionDefinition.class))
{
lastIndentValue++;
}
for (Block block : line)
{
int verticalCenterOffset = (LINE_HEIGHT - block.getHeight()) / 2;
block.setLocation(blockXPos, blockYPos + verticalCenterOffset);
blockXPos += block.getWidth();
}
i++;
}
// Remove unwanted extra lines
int lastEmptyLineIndex = -1;
for (int p = 0; p < mViewGrid.size(); p++) {
if (mViewGrid.get(p).size() == 0 && p > (lastEmptyLineIndex + 1))
lastEmptyLineIndex = p;
}
// Make Height Reflect Grid
int requiredHeight = LINE_SEPERATOR + (mViewGrid.size() * (LINE_HEIGHT + LINE_SEPERATOR)) + LINE_HEIGHT;
if (getPreferredSize().height < requiredHeight)
{
mPreferredSize = new Dimension(getWidth(), requiredHeight);
this.setSize(mPreferredSize);
}
else if (getPreferredSize().height > requiredHeight) {
mPreferredSize = new Dimension(getWidth(), requiredHeight);
this.setSize(mPreferredSize);
}
// Make Width Reflect Grid
int largestWidth = 0;
for (LinkedList<Block> line : mViewGrid)
{
int width = 0;
for (Block b : line)
{
width += b.getWidth();
}
if (width > largestWidth)
largestWidth = width;
}
largestWidth += 20;
mPreferredSize = new Dimension(largestWidth, getHeight());
// printNumberOfBlocks(mViewGrid);
new Thread(new Runnable(){
@Override
public void run()
{
addSyntaxAnnotation();
}
}).start();
repaint();
}
// Verify there hasn't been a block overflow
if (this.getComponentCount() != (noOfBlocksInGrid + 3)) {
Utils.err.println(getClass(), "Block count in VLJCanvas is incorrect (" + this.getComponentCount() + " instead of " + (noOfBlocksInGrid + 2) + ")");
}
}
public synchronized void addSyntaxAnnotation()
{
for(LinkedList<Block> row : mViewGrid){
for(int i = 0; i < row.size(); i++){
Block currentBlock = row.get(i);
//Reset annotation
currentBlock.dotOnLeft = false;
currentBlock.dotOnRight = false;
currentBlock.leftParen = false;
currentBlock.rightParen = false;
currentBlock.rightComma = false;
currentBlock.rightSemiColon = false;
Utils.out.println(getClass(), "objects in row:" + row.size());
if(i > 0){
Block previousBlock = row.get(i-1);
//Annotate Function Call Dots
if(row.get(i) instanceof BlockFunctionCall &&
(row.get(i-1) instanceof BlockFunctionCall || row.get(i-1) instanceof BlockIdentifier)){
currentBlock.dotOnLeft = true;
previousBlock.dotOnRight = true;
}
//Annotate Start and End Parentheses
//Start - after function call, before literal/identifier
if(previousBlock instanceof BlockFunctionCall && (currentBlock instanceof BlockPrimitive || currentBlock instanceof BlockIdentifier)){
currentBlock.leftParen = true;
}
//End - after literal/identifier, last in line
if(i == (row.size() -1) && (currentBlock instanceof BlockPrimitive || currentBlock instanceof BlockIdentifier) &&
(previousBlock instanceof BlockPrimitive || previousBlock instanceof BlockIdentifier || previousBlock instanceof BlockFunctionCall) ) {
currentBlock.rightParen = true;
}
else if(i == (row.size() -1) && !(currentBlock instanceof BlockFunctionDefinition)){
currentBlock.rightSemiColon = true;
}
}
//Annotate Parameter Commas
if(i < (row.size() - 1) && (currentBlock instanceof BlockPrimitive || currentBlock instanceof BlockIdentifier)){
Block nextBlock = row.get(i+1);
if(nextBlock instanceof BlockPrimitive || nextBlock instanceof BlockIdentifier){
currentBlock.rightComma = true;
}
}
}
}
}
public void printNumberOfBlocks(Grid grid)
{
int count = 0;
for (LinkedList<Block> line : grid)
{
count += line.size();
}
Utils.out.println(this.getClass(), "Total controlled block count:" + count);
Utils.out.println(this.getClass(), "ComponentCount:" + this.getComponentCount());
}
@Override
public <T>void onDialValueChange()
{
boolean shoudValidateGridLayout = false;
try{
HashSet<RadialSpotlight> diallerBlockSpotlight = new HashSet<RadialSpotlight>();
int radius = mDiallerBlock.getWidth() / 2;
diallerBlockSpotlight.add(new RadialSpotlight(mDiallerBlock.getX() + radius, mDiallerBlock.getY() + (mDiallerBlock.getHeight() / 2), radius, 50));
this.setSpotlights(diallerBlockSpotlight);
// Get new value
int x = mPrimitiveDialler.getPercentageValue();
int y = (int) (x + (0.01 * x * x * x));
// Finally, update Parse Tree.
Evaluator eval = mDiallerBlock.getEvaluatorNode();
if (eval instanceof NumberLiteralEvaluator)
{
// Utils.err.println(getClass(), " Evaluator Found!");
// Replace block value with new value
Double replacement = new Double(mInitialDiallerValue + y);
NumberLiteralEvaluator<T> modEval = (NumberLiteralEvaluator<T>) eval;
// Modify integer, then somehow let everyone else know!
// Get Closest Valid Replacement
JSType oldValue = modEval.evaluate();
JSNumber oldNumber = new JSNumber(0);
if(oldValue != null && oldValue instanceof JSNumber){
oldNumber = (JSNumber) oldValue;
}
//Check if we need to repaint block
if(("" + oldNumber.intValue()).length() != ("" + replacement).length()){
shoudValidateGridLayout = true;
}
if(modEval.isValidValue(replacement)){
String warningMessage = "The number cannot be ";
if(modEval.hasCustomMax() && modEval.hasCustomMin())
warningMessage += "less than " + modEval.getMinValue() + " or more than " + modEval.getMaxValue();
else if(modEval.hasCustomMin())
warningMessage += "less than " + modEval.getMinValue();
else
warningMessage += "more than " + modEval.getMaxValue();
displayInfoMessage(warningMessage + "!");
// Update Text Box
mDiallerBlock.mRoundedText.setText("" + replacement);
}
else{
// Update Text Box
mDiallerBlock.mRoundedText.setText("" + oldNumber.intValue());
}
// Set the Snapshot
String newSnapshot = this.getModel().generateCodeFromBlocks(mViewGrid);
DBParser.getInstance().setSnapshot(newSnapshot);
// Modify the AST
modEval.modifyValue(getModel(), (T) replacement, false);
if(DBParser.getInstance().containsEvaluator(modEval)){
HashSet<Evaluator> changedEvals = new HashSet<Evaluator>();
changedEvals.add(modEval);
DBParser.getInstance().mRecentlyChangedEvaluators = changedEvals;
}
// else if(eval instanceof )
else
DBParser.getInstance().mRecentlyChangedEvaluators = null;
if (!DBParser.getInstance().containsEvaluator(modEval))
{
// Utils.err.println(getClass(),
// "Parser does not contain evaluator!");
}
if (DMSimplePanel.hasInstance())
{
if (DMSimplePanel.getInstance().isStoppedAndEmpty())
{
JsEngine.getInstance().requestExecutionOfModifiedAST();
}
}
mDiallerBlock.repaint();
}
else
{
Utils.err.println(getClass(), "No Evaluator Node Found!");
// Parse fully so we have some AST to work with
if(TextPanel.hasInstance())
JsEngine.getInstance().executeCode(null, TextPanel.getInstance().getDocument().getText(), false);
else{
Utils.err.println(getClass(), "WARNING - executing from VLCanvas");
JsEngine.getInstance().executeCode(null, getModel().generateCodeFromBlocks(mViewGrid), false);
}
}
if(shoudValidateGridLayout)
validateGridLayout();
}
catch(Exception e){
e.printStackTrace();
}
}
public void displayDialler(BlockPrimitive b, String initialValue, Point blockPosition)
{
mDiallerBlock = b;
mInitialDiallerValue = Double.parseDouble(initialValue);
if (mPrimitiveDialler != null)
{
if (mPrimitiveDialler.getParent() == null)
add(mPrimitiveDialler);
mPopupSelectorOp.setVisible(false);
mPrimitiveDialler.reset();
mPrimitiveDialler.setVisible(true);
int diallerX = (b.getX() + (b.getWidth() / 2)) - (mPrimitiveDialler.getWidth() / 2);
mPrimitiveDialler.setLocation(new Point(diallerX, blockPosition.y - PopupDiallerPrimitive.mSliderHeight + 4));
mPrimitiveDialler.setSize(mPrimitiveDialler.getPreferredSize());
mPrimitiveDialler.getParent().setComponentZOrder(mPrimitiveDialler, 0);
}
}
public void displaySelectorOp(BlockOperator b, Point blockPosition){
mOperatorBlock = b;
if(mPopupSelectorOp != null)
{
if(mPopupSelectorOp.getParent() == null)
add(mPopupSelectorOp);
mPrimitiveDialler.setVisible(false);
mPopupSelectorOp.setVisible(true);
int diallerX = (b.getX() + (b.getWidth() / 2)) - (mPopupSelectorOp.getWidth() / 2);
mPopupSelectorOp.setLocation(new Point(diallerX, blockPosition.y - Popup.mSliderHeight - 4));
mPopupSelectorOp.setPreferredSize( new Dimension(250, 170));
mPopupSelectorOp.getParent().setComponentZOrder(mPopupSelectorOp, 0);
}
}
public void displayInfoMessage(String message){
mPopupInfoMessage.show(message, 2000);
}
public void hideInfoMessage(){
mPopupInfoMessage.setVisible(false);
}
public void hideSelectorOp(){
mPopupSelectorOp.setVisible(false);
}
public void hideDisplayDialler(){
mDiallerBlock.setVisible(false);
}
@SuppressWarnings("unchecked")
@Override
public void setSpotlights(HashSet<? extends Spotlight> spotlights)
{
mSpotlights = (HashSet<RadialSpotlight>) spotlights;
}
@Override
public void setDimBackground(boolean b)
{
mDimBackground = b;
}
public synchronized void clear()
{
mViewGrid.clear();
}
public void setBlocksToHighlight(LinkedList<Block> spotlightBlocks)
{
mBlocksToHighlight = spotlightBlocks;
}
@Override
public void onPopupSelectorChange(BlockOperator.SupportedOperationType opType)
{
mOperatorBlock.setType(opType);
}
}