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); } }