package bluej.pkgmgr.target;

import bluej.pkgmgr.Package;
import bluej.pkgmgr.PackageEditor;
import bluej.utility.javafx.JavaFXUtil;

import java.util.Properties;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Labeled;
import javafx.scene.Cursor;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.Node;

import threadchecker.OnThread;
import threadchecker.Tag;


| A general target in a package | | @author Michael Cahill | public abstract class Target implements Comparable<Target>{ static final int DEF_WIDTH = 80; static final int DEF_HEIGHT = 50; static final int ARR_HORIZ_DIST = 5; static final int ARR_VERT_DIST = 10; static final int HANDLE_SIZE = 20; static final int TEXT_HEIGHT = 16; static final int TEXT_BORDER = 4; static final int SHAD_SIZE = 4; private static final double SHADOW_RADIUS = 3.0; protected static final double RESIZE_CORNER_SIZE = 16; @OnThread(Tag.FXPlatform) private int preMoveX; @OnThread(Tag.FXPlatform) private int preMoveY; @OnThread(Tag.FXPlatform) private int preResizeWidth; @OnThread(Tag.FXPlatform) private int preResizeHeight; @OnThread(Tag.FXPlatform) private boolean pressIsResize; @OnThread(Tag.FXPlatform) private double pressDeltaX; @OnThread(Tag.FXPlatform) private double pressDeltaY; @OnThread(Tag.FXPlatform) private ContextMenu showingContextMenu; @OnThread(value = Tag.Any, requireSynchronized = true) private String identifierName; private String displayName; @OnThread(value = Tag.Any, requireSynchronized = true) private Package pkg; @OnThread(Tag.Any) protected boolean selected; protected boolean queued; @OnThread(Tag.FXPlatform) protected BorderPane pane = JavaFXUtil.initFX(BorderPane::new); @OnThread(Tag.FX) private boolean resizable = true;
| Create a new target with default size. | @OnThread(Tag.FXPlatform) public Target(Package pkg, String identifierName) { pane.setPrefWidth(calculateWidth(new Label(), identifierName)); pane.setPrefHeight(DEF_HEIGHT); pane.setCursor(Cursor.HAND); JavaFXUtil.addStyleClass(pane, "target"); pane.setEffect(new DropShadow(SHADOW_RADIUS, SHADOW_RADIUS/2.0, SHADOW_RADIUS/2.0, javafx.scene.paint.Color.GRAY)); pane.setFocusTraversable(true); JavaFXUtil.addFocusListener(pane, hasFocus -> { PackageEditor pkgEditor = pkg.getEditor(); if (pkgEditor == null) { return; } if (hasFocus && !isSelected()) { pkgEditor.selectOnly(this); } if (!hasFocus) { pkgEditor.checkForLossOfFocus(); } }); pane.setOnMouseClicked(e -> { if (e.getClickCount() > 1 && e.getButton() == MouseButton.PRIMARY && !e.isPopupTrigger() && e.isStillSincePress()) { doubleClick(e.isShiftDown()); } else if (e.getClickCount() == 1 && e.getButton() == MouseButton.PRIMARY && !e.isPopupTrigger() && e.isStillSincePress()) { if (!pkg.getEditor().clickForExtends(this, e.getSceneX(), e.getSceneY())) { if (e.isShiftDown() || e.isShortcutDown()) { pkg.getEditor().toggleSelection(this); } else { pkg.getEditor().selectOnly(this); } } updateCursor(e, false); if (isSelected()) pane.requestFocus(); } e.consume(); }); pane.setOnMouseMoved(e -> { updateCursor(e, false); pkg.getEditor().setMouseIn(this); }); pane.setOnMouseExited(e -> { pkg.getEditor().setMouseLeft(this); }); pane.setOnMousePressed(e -> { if (e.getButton() == MouseButton.PRIMARY) { showingMenu(null); pressDeltaX = e.getX(); pressDeltaY = e.getY(); pressIsResize = isSelected() && cursorAtResizeCorner(e); if (pressIsResize && isResizable()) pkg.getEditor().startedResize(); else{ pkg.getEditor().startedMove(); } } e.consume(); }); pane.setOnMouseDragged(e -> { if (e.getButton() == MouseButton.PRIMARY && !pkg.getEditor().isCreatingExtends()) { if (pressIsResize && isResizable()) { int newWidth = pkg.getEditor().snapToGrid((int) (e.getX() + (preResizeWidth - pressDeltaX))); int newHeight = pkg.getEditor().snapToGrid((int) (e.getY() + (preResizeHeight - pressDeltaY))); pkg.getEditor().resizeBy(newWidth - preResizeWidth, newHeight - preResizeHeight); } else if (isMoveable()) { if (!isSelected()) { pkg.getEditor().selectOnly(this); pkg.getEditor().startedMove(); } Point2D p = pkg.getEditor().sceneToLocal(e.getSceneX(), e.getSceneY()); int newX = pkg.getEditor().snapToGrid((int) (p.getX() - pressDeltaX)); int newY = pkg.getEditor().snapToGrid((int) (p.getY() - pressDeltaY)); pkg.getEditor().moveBy(newX - preMoveX, newY - preMoveY); updateCursor(e, true); } } e.consume(); }); pane.setOnMouseReleased(e -> { pkg.getEditor().endResize(); }); pane.setOnKeyTyped(e -> { if (e.getCharacter().length() > 0 && "+-".contains(e.getCharacter()) && isResizable()) { pkg.getEditor().startedResize(); int delta = e.getCharacter().equals("+") ? PackageEditor.GRID_SIZE : -PackageEditor.GRID_SIZE; pkg.getEditor().resizeBy(delta, delta); pkg.getEditor().endResize(); e.consume(); } }); pane.setOnKeyPressed(e -> { if (isArrowKey(e)) { if (e.isControlDown()) { if (isResizable()) { pkg.getEditor().startedResize(); int d = PackageEditor.GRID_SIZE; pkg.getEditor().resizeBy( e.getCode() == KeyCode.LEFT ? -d : (e.getCode() == KeyCode.RIGHT ? d : 0), e.getCode() == KeyCode.UP ? -d : (e.getCode() == KeyCode.DOWN ? d : 0)); pkg.getEditor().endResize(); } } else if (e.isShiftDown()) { if (isMoveable()) { pkg.getEditor().startedMove(); int d = PackageEditor.GRID_SIZE; pkg.getEditor().moveBy( e.getCode() == KeyCode.LEFT ? -d : (e.getCode() == KeyCode.RIGHT ? d : 0), e.getCode() == KeyCode.UP ? -d : (e.getCode() == KeyCode.DOWN ? d : 0)); } } else { pkg.getEditor().navigate(e); } e.consume(); } else if (e.getCode() == KeyCode.ESCAPE) { if (pkg.getEditor()!= null) { pkg.getEditor().selectOnly(this); } e.consume(); } else if (e.getCode() == KeyCode.A && !e.isAltDown()) pkg.getEditor().selectAll(); e.consume(); } }); JavaFXUtil.listenForContextMenu(pane, (x, y) -> { pkg.getEditor().selectOnly(this); popupMenu(x.intValue(), y.intValue(), pkg.getEditor()); return true; }, KeyCode.SPACE, KeyCode.ENTER); if (pkg == null) throw new NullPointerException(); this.pkg = pkg; this.identifierName = identifierName; this.displayName = identifierName; } @OnThread(Tag.FXPlatform) private void updateCursor(MouseEvent e, boolean moving) { if (moving) { pane.setCursor(Cursor.MOVE); } else if (isSelected() && isResizable() && cursorAtResizeCorner(e)) { pane.setCursor(Cursor.SE_RESIZE); } else { pane.setCursor(Cursor.HAND); } } @OnThread(Tag.FXPlatform) protected boolean cursorAtResizeCorner(MouseEvent e) { return e.getX() + e.getY() >= getWidth() + getHeight() - RESIZE_CORNER_SIZE; }
| Calculate the width of a target depending on the length of its name and | the font used for displaying the name. The size returned is a multiple of | 10 (to fit the interactive resizing behaviour). | | @param name | the name of the target (may be null). | @return the width the target should have to fully display its name. | @OnThread(Tag.FX) protected static int calculateWidth(Labeled node, String name) { int width = 0; if (name != null) width = (int)JavaFXUtil.measureString(node, name); if ((width + 20) <= DEF_WIDTH) return DEF_WIDTH; else{ return (width + 29) / PackageEditor.GRID_SIZE * PackageEditor.GRID_SIZE; } }
| This target has been removed from its package. | public synchronized void setRemoved() { pkg = null; }
| Load this target's properties from a properties file. The prefix is an | internal name used for this target to identify its properties in a | properties file used by multiple targets. | public void load(Properties props, String prefix) throws NumberFormatException { int xpos = 0; int ypos = 0; int width = 20; int height = 10; try { xpos = Math.max(Integer.parseInt(props.getProperty(prefix + ".x")), 0); ypos = Math.max(Integer.parseInt(props.getProperty(prefix + ".y")), 0); width = Math.max(Integer.parseInt(props.getProperty(prefix + ".width")), 1); height = Math.max(Integer.parseInt(props.getProperty(prefix + ".height")), 1); } catch (NumberFormatException nfe) { } setPos(xpos, ypos); setSize(width, height); }
| Save the target's properties to 'props'. | @OnThread(Tag.FXPlatform) public void save(Properties props, String prefix) { props.put(prefix + ".x", String.valueOf(getX())); props.put(prefix + ".y", String.valueOf(getY())); props.put(prefix + ".width", String.valueOf(getWidth())); props.put(prefix + ".height", String.valueOf(getHeight())); props.put(prefix + ".name", getIdentifierName()); }
| Return this target's package (ie the package that this target is | currently shown in) | @OnThread(Tag.Any) public synchronized Package getPackage() { return pkg; }
| Change the text which the target displays for its label | public void setDisplayName(String name) { displayName = name; }
| Returns the text which the target is displaying as its label | public String getDisplayName() { return displayName; } @OnThread(Tag.Any) public synchronized String getIdentifierName() { return identifierName; } public synchronized void setIdentifierName(String newName) { identifierName = newName; }
| | Sets the selected status of this target. Do not call directly | to select us; instead call SelectionController/SelectionSet's methods, | which will call this after updating the selected set. | @OnThread(Tag.FXPlatform) public void setSelected(boolean selected) { this.selected = selected; JavaFXUtil.setPseudoclass("bj-selected", selected, pane); redraw(); } @OnThread(Tag.FXPlatform) protected void redraw() { }
| | (non-Javadoc) | | @see bluej.graph.Selectable#isSelected() | @OnThread(Tag.Any) public boolean isSelected() { return selected; } @OnThread(Tag.FXPlatform) public int getX() { Double leftAnchor = AnchorPane.getLeftAnchor(pane); if (leftAnchor == null) return 0; else{ return (int)(double)leftAnchor; } } @OnThread(Tag.FXPlatform) public int getY() { Double topAnchor = AnchorPane.getTopAnchor(pane); if (topAnchor == null) return 0; else{ return (int)(double)topAnchor; } } @OnThread(Tag.FXPlatform) public int getWidth() { return (int)pane.getPrefWidth(); } @OnThread(Tag.FXPlatform) public int getHeight() { return (int)pane.getPrefHeight(); } public boolean isQueued() { return queued; } public void setQueued(boolean queued) { this.queued = queued; } @OnThread(Tag.FX) public boolean isResizable() { return resizable; } @OnThread(Tag.FXPlatform) public void setResizable(boolean resizable) { this.resizable = resizable; } @OnThread(Tag.Any) public boolean isSaveable() { return true; } public boolean isSelectable() { return true; } @OnThread(Tag.FXPlatform) public void repaint() { Package thePkg = getPackage(); if (thePkg != null) thePkg.repaint(); }
| We have a notion of equality that relates solely to the identifierName. | If the identifierNames's are equal then the Target's are equal. | @OnThread(Tag.Any) public boolean equals(Object o) { if (o instanceof Target) { Target t = (Target) o; return this.identifierName.equals(t.identifierName); } return false; } @OnThread(Tag.Any) public synchronized int hashCode() { return identifierName.hashCode(); } @OnThread(value = Tag.FXPlatform, ignoreParent = true) public int compareTo(Target t) { if (equals(t)) return 0; if (this.getY() < t.getY()) return -1; else if (this.getY() > t.getY()) return 1; if (this.getX() < t.getX()) return -1; else if (this.getX() > t.getX()) return 1; return this.identifierName.compareTo(t.getIdentifierName()); } public String toString() { return getDisplayName(); } @OnThread(Tag.FXPlatform) public void setPos(int x, int y) { AnchorPane.setTopAnchor(pane, (double)y); AnchorPane.setLeftAnchor(pane, (double)x); repaint(); } @OnThread(Tag.FXPlatform) public void setSize(int width, int height) { pane.setPrefWidth(width); pane.setPrefHeight(height); repaint(); } @OnThread(Tag.FXPlatform) public abstract void doubleClick(boolean openInNewWindow); @OnThread(Tag.FXPlatform) public abstract void popupMenu(int x, int y, PackageEditor editor); public abstract void remove(); @OnThread(Tag.FXPlatform) public Node getNode() { return pane; } @OnThread(Tag.FXPlatform) public boolean isMoveable() { return false; } @OnThread(Tag.FXPlatform) public void setIsMoveable(boolean b) { }
| Is this a front class (true; most of them) or back (false; | unit-test classes which always appear behind others) | @OnThread(Tag.FXPlatform) public boolean isFront() { return true; }
| Gets the bounds relative to the package editor. | @return | @OnThread(Tag.FXPlatform) public Bounds getBoundsInEditor() { return pane.getBoundsInParent(); }
| Save the current position so that we later know | how much to move by (i.e. the delta) when dragging. | @OnThread(Tag.FXPlatform) public void savePreMovePosition() { preMoveX = getX(); preMoveY = getY(); }
| The X position before we started the move | @OnThread(Tag.FXPlatform) public int getPreMoveX() { return preMoveX; }
| The Y position before we started the move | @OnThread(Tag.FXPlatform) public int getPreMoveY() { return preMoveY; }
| Save the current size so that we later know | how much to resize by (i.e. the delta). | @OnThread(Tag.FXPlatform) public void savePreResize() { preResizeWidth = getWidth(); preResizeHeight = getHeight(); } @OnThread(Tag.FXPlatform) public int getPreResizeWidth() { return preResizeWidth; } @OnThread(Tag.FXPlatform) public int getPreResizeHeight() { return preResizeHeight; } @OnThread(Tag.FXPlatform) protected final void showingMenu(ContextMenu newMenu) { if (newMenu != null) { requestFocus(); } if (showingContextMenu != null) { showingContextMenu.hide(); } showingContextMenu = newMenu; } @OnThread(Tag.FXPlatform) private static boolean isArrowKey(KeyEvent evt) { return evt.getCode() == KeyCode.UP || evt.getCode() == KeyCode.DOWN || evt.getCode() == KeyCode.LEFT || evt.getCode() == KeyCode.RIGHT; } @OnThread(Tag.FXPlatform) public boolean isFocused() { return pane.isFocused(); } @OnThread(Tag.FXPlatform) public void requestFocus() { pane.requestFocus(); } @OnThread(Tag.FXPlatform) public void setCreatingExtends(boolean drawingExtends) { pane.setEffect(drawingExtends ? new ColorAdjust(0, 0, -0.2, 0): null); } }

.   Target
.   updateCursor
.   cursorAtResizeCorner
.   calculateWidth
.   setRemoved
.   load
.   save
.   getPackage
.   setDisplayName
.   getDisplayName
.   getIdentifierName
.   setIdentifierName
.   setSelected
.   redraw
.   isSelected
.   getX
.   getY
.   getWidth
.   getHeight
.   isQueued
.   setQueued
.   isResizable
.   setResizable
.   isSaveable
.   isSelectable
.   repaint
.   equals
.   hashCode
.   compareTo
.   toString
.   setPos
.   setSize
.   doubleClick
.   popupMenu
.   remove
.   getNode
.   isMoveable
.   setIsMoveable
.   isFront
.   getBoundsInEditor
.   savePreMovePosition
.   getPreMoveX
.   getPreMoveY
.   savePreResize
.   getPreResizeWidth
.   getPreResizeHeight
.   showingMenu
.   isArrowKey
.   isFocused
.   requestFocus
.   setCreatingExtends




825 neLoCode + 37 LoComm