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