package com.drawbridge.vl.blocks; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontFormatException; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.Insets; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.geom.Path2D; import java.awt.geom.Path2D.Double; import java.io.IOException; import java.io.InputStream; import javax.swing.BorderFactory; import javax.swing.BoxLayout; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import com.drawbridge.Activity; import com.drawbridge.jsengine.ast.Evaluator; import com.drawbridge.utils.AnalyticUtils; import com.drawbridge.utils.Utils; import com.drawbridge.vl.PopupDiallerPrimitive; import com.drawbridge.vl.VLCanvas; import com.drawbridge.vl.VLPanel; import com.google.caja.lexer.FilePosition; /** * A block represents a piece of code and can exist in the VLJGrid. * * @author Alistair Stead * */ public abstract class Block extends JComponent { public abstract String generateCode(); private static final long serialVersionUID = 578913919325710081L; private Point mMouseDownOffset = null; private FilePosition mFilePosition; private Evaluator mEvaluator; protected Path2D.Double mPath; public JTextField mRoundedText; private int mRoundSize = 20; protected Color mColor; protected Stroke mStroke; private Insets mPadding = new Insets(0, 10, 0, 10); public boolean rightSemiColon = false; public boolean leftParen = false; public boolean rightParen = false; public boolean rightComma = false; public boolean dotOnLeft = false; public boolean dotOnRight = false; protected final int mInitialHeight; public BoxLayout mLayout; public Block(FilePosition filePosition, Evaluator evaluator, int height, Color color, Stroke stroke, String text) { mInitialHeight = height; mColor = color; mStroke = stroke; // Text Field mRoundedText = new JTextField(text); mRoundedText.setOpaque(false); mLayout = new BoxLayout(this, BoxLayout.X_AXIS); this.setLayout(mLayout); this.add(mRoundedText); initImmutableProperties(); mFilePosition = filePosition; mEvaluator = evaluator; this.addMouseListener(mMouseAdapter); this.addMouseMotionListener(mMouseAdapter); } public Block(Block block) { mFilePosition = block.getFilePosition(); mEvaluator = block.getEvaluatorNode(); this.setPreferredSize(block.getPreferredSize()); mInitialHeight = block.mInitialHeight; mRoundedText = new JTextField(block.getText()); mColor = block.mColor; mStroke = block.mStroke; mPath = block.mPath; mLayout = new BoxLayout(this, BoxLayout.X_AXIS); this.setLayout(mLayout); this.add(mRoundedText); initImmutableProperties(); this.addMouseListener(mMouseAdapter); this.addMouseMotionListener(mMouseAdapter); } protected void initImmutableProperties() { mRoundedText.setDragEnabled(false); mRoundedText.setTransferHandler(null); mRoundedText.setFocusTraversalPolicy(null); mRoundedText.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent ke) { // Tell the canvas about it SwingUtilities.invokeLater(new Runnable() { @Override public void run() { VLCanvas canvas = VLPanel.getInstance().mCanvas; if (canvas != null) { canvas.updateGrid(); } } }); } }); mPath = Block.getSimplePath(0, 0, getWidth(), getHeight()); this.addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { mPath = Block.getSimplePath(0, 0, getWidth(), getHeight()); repaint(); } }); this.setBorder(BorderFactory.createEmptyBorder(mPadding.top, mPadding.left, mPadding.bottom, mPadding.right)); // Font Properties GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); try { InputStream is = Activity.class.getResourceAsStream("/Assets/Fonts/Roboto-Regular.ttf"); Font customFont = Font.createFont(Font.TRUETYPE_FONT, is); is.close(); ge.registerFont(customFont); this.setFont(customFont); } catch (FontFormatException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } setSize(getPreferredSize()); // Text Properties mRoundedText.setMinimumSize(new Dimension(20, 20)); // mRoundedText.setBorder(BorderFactory.createLineBorder(Color.gray)); mRoundedText.setBorder(null); mRoundedText.setBackground(new Color(0, 0, 0, 0)); mRoundedText.setHorizontalAlignment(SwingConstants.LEFT); mRoundedText.setFocusable(false); mRoundedText.getDocument().addDocumentListener(mDocumentListener); // Make Text Editable mRoundedText.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent fe) { } @Override public void focusLost(FocusEvent fe) { mRoundedText.enableInputMethods(false); } }); this.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent me) { if (me.getClickCount() == 2) { mRoundedText.setFocusable(true); mRoundedText.selectAll(); mRoundedText.requestFocus(); mRoundedText.repaint(); } } }); mRoundedText.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent me) { if (me.getClickCount() == 2) { mRoundedText.setFocusable(true); mRoundedText.requestFocus(); mRoundedText.repaint(); } else { mRoundedText.setFocusable(false); me.setSource(Block.this); Block.this.dispatchEvent(me); } } @Override public void mousePressed(MouseEvent me) { mRoundedText.setFocusable(false); me.setSource(Block.this); Block.this.dispatchEvent(me); } @Override public void mouseReleased(MouseEvent me) { mRoundedText.setFocusable(false); me.setSource(Block.this); Block.this.dispatchEvent(me); } }); mRoundedText.addMouseMotionListener(new MouseMotionListener() { @Override public void mouseDragged(MouseEvent me) { mRoundedText.setFocusable(false); me.setSource(Block.this); Block.this.dispatchEvent(me); } @Override public void mouseMoved(MouseEvent me) { } }); validateForText(); } protected void validateForText() { FontMetrics metrics = mRoundedText.getFontMetrics(mRoundedText.getFont()); int textWidth = metrics.stringWidth(mRoundedText.getText()); int textHeight = metrics.getHeight(); // Set the Size of the TextField int size = Math.max(10, textWidth + mPadding.left + mPadding.right); mRoundedText.setSize(size, textHeight); mRoundedText.setPreferredSize(new Dimension(size, textHeight)); // Set the Size of the Block Dimension newBlockSize = new Dimension(size + mPadding.left + mPadding.right, mInitialHeight); setPreferredSize(newBlockSize); setSize(newBlockSize); validate(); repaint(); } public void setFilePosition(FilePosition filePosition) { mFilePosition = filePosition; } public FilePosition getFilePosition() { return mFilePosition; } /** * Only use this when updating old blocks using LCS * * @param evaluatorNode */ public void setEvaluatorNode(Evaluator evaluatorNode) { mEvaluator = evaluatorNode; } MouseAdapter mMouseAdapter = new MouseAdapter() { int dragCounter = 0; @Override public void mouseClicked(MouseEvent me) { Utils.out.println(getClass(), "Block mouseClicked"); if (me.getClickCount() == 1) { if (getPath().contains(me.getX(), me.getY())) { Utils.out.println(this.getClass(), "block click"); } } else if (me.getClickCount() == 2) { // ACCESS THE CONTENT HERE } } @Override public void mousePressed(MouseEvent me) { Utils.out.println(getClass(), "Block mousePressed"); if (getPath().contains(me.getX(), me.getY())) { dragCounter = 0; mMouseDownOffset = me.getPoint(); AnalyticUtils.recordVLBlockClick(); } } @Override public void mouseDragged(MouseEvent me) { if (mMouseDownOffset != null) { dragCounter++; if (dragCounter == 1) { Utils.playClick(); VLPanel.getInstance().mCanvas.removeFromGrid(Block.this); VLPanel.getInstance().setComponentZOrder(Block.this, 0); VLPanel.getInstance().mCanvas.remove(Block.this); VLPanel.getInstance().add(Block.this); } MouseEvent parentMouseEvent = SwingUtilities.convertMouseEvent(me.getComponent(), me, me.getComponent().getParent()); Block.this.setLocation(parentMouseEvent.getX() - mMouseDownOffset.x, parentMouseEvent.getY() - mMouseDownOffset.y); MouseEvent canvasMouseEvent = SwingUtilities.convertMouseEvent(me.getComponent(), me, VLPanel.getInstance().mCanvas); VLPanel.getInstance().mCanvas.notifyOfDragPosition(canvasMouseEvent.getX(), canvasMouseEvent.getY()); VLPanel.getInstance().setComponentZOrder(Block.this, 0); } } @Override public void mouseReleased(MouseEvent me) { Utils.out.println(getClass(), "Block mouseReleased"); if (dragCounter > 0) Utils.playClick(); dragCounter = 0; if (mMouseDownOffset != null) { final Container blockParent = Block.this.getParent(); Component[] parentComponents = blockParent.getComponents(); for (Component children : parentComponents) { if (!(children instanceof Block)) { MouseEvent meSibling = SwingUtilities.convertMouseEvent(Block.this, me, children); if (children.contains(meSibling.getX(), meSibling.getY())) { if (children instanceof JScrollPane) { // Return to // VisualDocument meSibling = SwingUtilities.convertMouseEvent(Block.this, me, VLPanel.getInstance().mCanvas); blockParent.remove(Block.this); VLPanel.getInstance().mCanvas.addBlockToCanvasAndGrid(Block.this, meSibling.getX(), meSibling.getY()); Block.this.setFilePosition(null); blockParent.repaint(); } else if (children instanceof PopupDiallerPrimitive) { // Do nothing } else { // Completely // Remove the // Block blockParent.remove(Block.this); blockParent.repaint(); VLPanel.getInstance().mCanvas.validateGridLayout(); VLPanel.getInstance().mCanvas.validate(); } } } } mMouseDownOffset = null; } VLPanel.getInstance().mCanvas.resetDragPosition(); } }; // Listens for changes in {@literal mRoundedText}} DocumentListener mDocumentListener = new DocumentListener() { @Override public void changedUpdate(DocumentEvent de) { validateForText(); } @Override public void insertUpdate(DocumentEvent de) { validateForText(); } @Override public void removeUpdate(DocumentEvent de) { validateForText(); } }; @Override public boolean contains(int x, int y) { if (getPath().contains(x, y)) return true; return false; } @Override public String toString() { return this.getClass().getSimpleName(); } public Evaluator getEvaluatorNode() { return mEvaluator; } public Color getColor() { return mColor; } public Double getPath() { return mPath; } public String getText() { return mRoundedText.getText(); } public void setText(String s) { if (mRoundedText != null) mRoundedText.setText(s); } public Insets getPadding() { return mPadding; } public void setPadding(Insets mPadding) { this.mPadding = mPadding; } public void setRoundSize(int newSize) { this.mRoundSize = newSize; } @Override public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // Anti-alias! RenderingHints.VALUE_ANTIALIAS_ON); g2.setColor(this.getColor()); g2.fillRoundRect(0, 0, getWidth(), getHeight(), mRoundSize, mRoundSize); g2.setColor(Color.black); if (mStroke != null) { g2.setStroke(mStroke); g2.drawRoundRect(0, 0, getWidth() - 1, getHeight() - 1, mRoundSize, mRoundSize); } g2.setColor(Color.black); g2.setFont(new Font(this.getFont().getFontName(), Font.BOLD, 30)); // Draw left parens if (leftParen && VLPanel.showSyntaxAnnotation) g2.drawString("(", 3, (getHeight() + g2.getFontMetrics().getLineMetrics("(", g2).getHeight() - g2.getFontMetrics().getDescent() - 8) / 2); // Draw right parens and semi-colon if(rightSemiColon && VLPanel.showSyntaxAnnotation) g2.drawString(";", getWidth() - g2.getFontMetrics().stringWidth(";"), (getHeight() + g2.getFontMetrics().getLineMetrics(";", g2).getHeight() - g2.getFontMetrics().getDescent() - 8) / 2); else if (rightParen && VLPanel.showSyntaxAnnotation) g2.drawString(");", getWidth() - g2.getFontMetrics().stringWidth(");"), (getHeight() + g2.getFontMetrics().getLineMetrics(");", g2).getHeight() - g2.getFontMetrics().getDescent() - 8) / 2); // Draw right comma if (rightComma && VLPanel.showSyntaxAnnotation) g2.drawString(",", getWidth() - g2.getFontMetrics().stringWidth(","), (getHeight() + g2.getFontMetrics().getLineMetrics(",", g2).getHeight() - g2.getFontMetrics().getDescent() - 8) / 2); g2.setColor(Color.black); int radius = 5; if (dotOnLeft && VLPanel.showSyntaxAnnotation) { g2.fillArc(0 - radius, ((getHeight() - radius) / 2), radius * 2, radius * 2, 0, 380); } if (dotOnRight && VLPanel.showSyntaxAnnotation) { g2.fillArc(getWidth() - 1 - radius, ((getHeight() - radius) / 2), radius * 2, radius * 2, 0, 360); } } public static Path2D.Double getSimplePath(int x, int y, int width, int height) { int roundInset = 15; int left = x; int right = x + width; int top = y; int bottom = y + height; Path2D.Double mPath = new Path2D.Double(); mPath.moveTo(left, top + roundInset); mPath.lineTo(left, bottom - roundInset); mPath.lineTo(right - roundInset, bottom); mPath.lineTo(right, top + roundInset); mPath.lineTo(left + roundInset, top); mPath.closePath(); return mPath; } // <= public static Path2D.Double getEqualityPath(int x, int y, int width, int height) { int left = x; int right = x + width; int top = y; int bottom = y + height; int topMargin = 6; int leftMargin = left + 20; int halfHeight = bottom / 2; Path2D.Double mPath = new Path2D.Double(); mPath.moveTo(leftMargin, top); mPath.lineTo(left, halfHeight); mPath.lineTo(leftMargin, bottom); mPath.lineTo(leftMargin, bottom - topMargin); mPath.lineTo(right, bottom - topMargin); mPath.lineTo(right, top + topMargin); mPath.lineTo(leftMargin, top + topMargin); mPath.lineTo(leftMargin, top); mPath.closePath(); return mPath; } }