package bluej.pkgmgr;
import java.awt.Rectangle;
import java.awt.geom.Area;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.swing.SwingUtilities;
import bluej.Config;
import bluej.compiler.CompileReason;
import bluej.compiler.CompileType;
import bluej.extmgr.FXMenuManager;
import bluej.pkgmgr.target.ClassTarget;
import bluej.utility.DialogManager;
import bluej.utility.Utility;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.Window;
import bluej.debugger.DebuggerObject;
import bluej.debugger.gentype.GenTypeClass;
import bluej.extensions.BDependency;
import bluej.extmgr.ExtensionsManager;
import bluej.extmgr.PackageExtensionMenu;
import bluej.graph.SelectionController;
import bluej.pkgmgr.dependency.Dependency;
import bluej.pkgmgr.dependency.UsesDependency;
import bluej.pkgmgr.target.DependentTarget;
import bluej.pkgmgr.target.Target;
import bluej.testmgr.record.InvokerRecord;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.ResizableCanvas;
import bluej.views.CallableView;
import threadchecker.OnThread;
import threadchecker.Tag;
| The main class diagram in the BlueJ package manager frame, supporting
| dragging/resizing of classes (incl multi-select), and drawing/creation of relations.
|
| The PackageEditor is a StackPane with five child panes. The back one is a Canvas,
| on which we draw the arrows (this way, the arrows always appear behind the classes).
| The second is a layer which contains classes which appear underneath other classes:
| currently, this is just test classes (which always appear behind the class they
| are testing). The third is a layer for all other targets. The fourth is a pane
| on which we draw the class selection rectangle, so that it always appears on top of
| the classes. The fifth is just a label to show a message when the current package is empty.
|
@OnThread(Tag.FXPlatform)
public final class PackageEditor
extends StackPane
implements MouseTrackingOverlayPane.MousePositionListener, PkgMgrFrame.PkgMgrPane, PackageListener,
PackageUI{
private static final int RIGHT_PLACEMENT_MIN = 300;
private static final int WHITESPACE_SIZE = 10;
| The grid resolution for graph layout.
|
public static final int GRID_SIZE = 10;
private final PkgMgrFrame pmf;
private final Package pkg;
private final SelectionController selectionController;
private final BooleanProperty showExtends;
private final BooleanProperty showUses;
private final AnchorPane frontClassLayer = new AnchorPaneExtraSpacing();
private final AnchorPane backClassLayer = new AnchorPane();
private final Pane selectionLayer = new Pane();
protected Label noClassesExistedMessage;
private final Canvas arrowLayer = new ResizableCanvas();
private boolean aboutToRepaint = false;
@OnThread(Tag.FXPlatform)
private ContextMenu showingContextMenu;
@OnThread(Tag.FXPlatform)
private MouseTrackingOverlayPane overlay;
@OnThread(Tag.FXPlatform)
private Label arrowCreationTip;
@OnThread(Tag.FXPlatform)
private boolean creatingExtends = false;
@OnThread(Tag.FXPlatform)
private ClassTarget extendsSubClass;
@OnThread(Tag.FXPlatform)
private ClassTarget extendsSuperClassHover;
@OnThread(Tag.FXPlatform)
private double newExtendsDestX;
@OnThread(Tag.FXPlatform)
private double newExtendsDestY;
| Construct a package editor for the given package.
|
@OnThread(Tag.FXPlatform)
public PackageEditor(PkgMgrFrame pmf, Package pkg, BooleanProperty showUses, BooleanProperty showInherits, MouseTrackingOverlayPane overlay)
{
this.pmf = pmf;
this.pkg = pkg;
this.selectionController = new SelectionController(this);
this.showUses = showUses;
this.showExtends = showInherits;
this.overlay = overlay;
this.selectionController.addSelectionListener(sel -> pmf.notifySelectionChanged(sel));
JavaFXUtil.addStyleClass(this, "class-diagram");
frontClassLayer.setBackground(null);
backClassLayer.setBackground(null);
frontClassLayer.setPickOnBounds(false);
JavaFXUtil.addChangeListenerPlatform(arrowLayer.widthProperty(), s -> repaint());
JavaFXUtil.addChangeListenerPlatform(arrowLayer.heightProperty(), s -> repaint());
selectionLayer.setMouseTransparent(true);
javafx.scene.shape.Rectangle rect = selectionController.getMarquee().getRectangle();
JavaFXUtil.addStyleClass(rect, "marquee");
selectionLayer.getChildren().add(rect);
noClassesExistedMessage = new Label(Config.getString("pkgmgr.noClassesExisted.message"));
noClassesExistedMessage.setVisible(false);
JavaFXUtil.addStyleClass(noClassesExistedMessage, "pmf-no-classes-msg");
getChildren().addAll(arrowLayer, backClassLayer, frontClassLayer, selectionLayer, noClassesExistedMessage);
JavaFXUtil.addChangeListener(showUses, e -> JavaFXUtil.runNowOrLater(this::repaint));
JavaFXUtil.addChangeListener(showExtends, e -> JavaFXUtil.runNowOrLater(this::repaint));
addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (e.getCode() == KeyCode.ESCAPE && creatingExtends)
stopNewInherits();
});
setPrefHeight(400.0);
}
@OnThread(Tag.FXPlatform)
public void benchToFixture(ClassTarget t)
{
pmf.objectBenchToTestFixture(t);
}
@OnThread(Tag.FXPlatform)
public void fixtureToBench(ClassTarget t)
{
pmf.testFixtureToObjectBench(t);
}
@OnThread(Tag.FXPlatform)
public void makeTestCase(ClassTarget t)
{
pmf.makeTestCase(t);
}
@OnThread(Tag.FXPlatform)
public void runTest(ClassTarget ct, String name)
{
ct.getRole().run(pmf, ct, name);
}
@OnThread(Tag.FXPlatform)
public void openPackage(String packageName)
{
pmf.openPackageTarget(packageName);
}
| Raise an event to notify that an object should be placed on the oject bench.
| @param src The source of the event
| @param obj The object to be put on the event
| @param iType The "interface" type of the object (declared type, used as a
|* fallback if the runtime type is not accessible)
* @param ir The invoker record for the invocation used to create this object
* @param animateFromScenePoint
@OnThread(Tag.FXPlatform)
public void raisePutOnBenchEvent(Window src, DebuggerObject obj, GenTypeClass iType, InvokerRecord ir, boolean askForName, Optional<Point2D> animateFromScenePoint)
{
pmf.putObjectOnBench(src, obj, iType, ir, askForName, animateFromScenePoint);
}
| Notify of some interaction.
|
@OnThread(Tag.FXPlatform)
public void recordInteraction(InvokerRecord ir)
{
pmf.recordInteraction(ir);
}
private boolean popupMenu(double screenX, double screenY)
{
ContextMenu menu = new ContextMenu();
Point2D graphLoc = screenToLocal(screenX, screenY);
for (Dependency d : getVisibleEdges())
{
if (d.isRemovable() && d.contains((int)graphLoc.getX(), (int)graphLoc.getY()))
{
MenuItem removeEdge = new MenuItem(Config.getString("menu.edit.remove"));
removeEdge.setOnAction(e -> {
d.remove();
});
JavaFXUtil.addStyleClass(removeEdge, "class-action-inbuilt");
menu.setOnShowing(e -> {
d.setSelected(true);
});
menu.setOnHiding(e -> {
d.setSelected(false);
});
menu.getItems().add(removeEdge);
showingMenu(menu);
menu.show(this, screenX, screenY);
return true;
}
}
MenuItem newClass = new MenuItem(Config.getString("menu.edit.newClass"));
newClass.setOnAction(e -> {
pmf.menuCall();
pmf.doCreateNewClass(graphLoc.getX(), graphLoc.getY());
});
JavaFXUtil.addStyleClass(newClass, "class-action-inbuilt");
MenuItem newPackage = new MenuItem(Config.getString("menu.edit.newPackage"));
newPackage.setOnAction(e -> {
pmf.menuCall();
pmf.doCreateNewPackage(graphLoc.getX(), graphLoc.getY());
});
JavaFXUtil.addStyleClass(newPackage, "class-action-inbuilt");
MenuItem newCSS = new MenuItem(Config.getString("menu.edit.newCSS"));
newCSS.setOnAction(e -> {
pmf.menuCall();
pmf.doCreateNewCSS(graphLoc.getX(), graphLoc.getY());
});
JavaFXUtil.addStyleClass(newCSS, "class-action-inbuilt");
MenuItem addClassFromFile = new MenuItem(Config.getString("menu.edit.addClass"));
addClassFromFile.setOnAction(e -> {
pmf.menuCall();
pmf.doAddFromFile();
});
JavaFXUtil.addStyleClass(addClassFromFile, "class-action-inbuilt");
menu.getItems().addAll(newClass, newPackage, newCSS, addClassFromFile);
SwingUtilities.invokeLater(() -> {
ExtensionsManager extMgr = ExtensionsManager.getInstance();
PackageExtensionMenu menuGenerator = new PackageExtensionMenu(pkg);
Platform.runLater(() -> {
FXMenuManager menuManager = new FXMenuManager(menu, extMgr, menuGenerator);
SwingUtilities.invokeLater(() -> {
menuManager.addExtensionMenu(pkg.getProject());
Platform.runLater(() -> {
showingMenu(menu);
menu.show(this, screenX, screenY);
});
});
});
});
return true;
}
private void showingMenu(ContextMenu menu)
{
if (showingContextMenu != null)
{
showingContextMenu.hide();
}
showingContextMenu = menu;
}
@OnThread(Tag.FXPlatform)
public Window getFXWindow()
{
return pmf.getFXWindow();
}
| Position the given vertex nicely in the graph. Thsi usually means that it
| will be placed somewhere near the top where it does not overlap with
| existing vertices.
|
| @param t
| The vertex to place.
|
public void findSpaceForVertex(Target t)
{
Area a = new Area();
for (Target vertex : pkg.getVertices())
{
if (vertex != t) {
Rectangle vr = new Rectangle(vertex.getX(), vertex.getY(), (int)vertex.getWidth(), (int)vertex.getHeight());
a.add(new Area(vr));
}
}
double minWidth = 300;
double minHeight = 200;
if (RIGHT_PLACEMENT_MIN > minWidth)
minWidth = RIGHT_PLACEMENT_MIN;
Rectangle targetRect = new Rectangle((int)t.getWidth() + WHITESPACE_SIZE * 2, (int)t.getHeight() + WHITESPACE_SIZE * 2);
for (int y = 0; y < (2 * minHeight); y += 10) {
for (int x = 0; x < (minWidth - t.getWidth() - 2 * WHITESPACE_SIZE); x += 10) {
targetRect.setLocation(x, y);
if (!a.intersects(targetRect)) {
t.setPos(x + 10, y + 10);
return;
}
}
}
t.setPos(10, (int)minHeight + 10);
}
@Override
@OnThread(value = Tag.FXPlatform)
public void graphChanged()
{
HashMap<Node, Boolean> keep = new HashMap<>();
for (Node c : frontClassLayer.getChildren())
{
keep.put(c, false);
}
for (Node c : backClassLayer.getChildren())
{
keep.put(c, false);
}
for (Target v : pkg.getVertices())
{
if (!keep.containsKey(v.getNode()))
{
(v.isFront() ? frontClassLayer : backClassLayer).getChildren().add(v.getNode());
if (v.isSelected()) {
selectionController.addToSelection(v);
}
}
keep.put(v.getNode(), true);
}
for (Map.Entry<Node, Boolean> e : keep.entrySet())
{
if (e.getValue().booleanValue() == false)
{
frontClassLayer.getChildren().remove(e.getKey());
backClassLayer.getChildren().remove(e.getKey());
}
}
pmf.graphChanged();
repaint();
}
public void graphClosed()
{
}
private static final int ARROW_SIZE = 18;
private static final double ARROW_ANGLE = Math.PI / 6;
private static final double DASHES[] = {5.0f, 2.0f
};
| Schedules a repaint. The repaint is done with a runLater,
| but using this method avoids a double repaint in common cases,
| e.g. we have a listener on X and Y or width and height, and both change
| in one go; we want to redraw once, not twice.
|
public void repaint()
{
if (!aboutToRepaint)
{
aboutToRepaint = true;
JavaFXUtil.runAfterCurrent(this::actualRepaint);
}
}
| Records that the mouse is now hovering over the given target
|
public void setMouseIn(Target target)
{
if (creatingExtends && extendsSubClass != null && target instanceof ClassTarget)
extendsSuperClassHover = (ClassTarget)target;
}
| Records that the mouse has stopped hovering over the given target.
|
public void setMouseLeft(Target target)
{
if (extendsSuperClassHover == target)
extendsSuperClassHover = null;
}
| Check whether the focus is still on one of the vertices in the graph.
| If not, clears the selection.
|
public void checkForLossOfFocus()
{
JavaFXUtil.runAfterCurrent(() -> {
if (!targetHasFocus() && getFXWindow().isFocused())
{
selectionController.clearSelection();
}
});
}
| Does one of the targets in the class diagram have focus?
| @return
|
public boolean targetHasFocus()
{
return pkg.getVertices().stream().anyMatch(Target::isFocused);
}
| Tries to focus a target in the class diagram. If there is
| already a selection, we focus one of the selected items.
| If not, we select an arbitrart target.
| @return true if we found something to focus, false if there was nothing to focus.
|
public boolean focusSelectedOrArbitrary()
{
if (selectionController.getSelection().isEmpty())
{
if (pkg.getVertices().isEmpty())
return false;
else
{
pkg.getVertices().get(0).requestFocus();
return true;
}
}
else
{
selectionController.getSelection().get(0).requestFocus();
return true;
}
}
public boolean isCreatingExtends()
{
return creatingExtends;
}
| A class caching the vital details needed to draw an extends dependency line,
| which could be either real and finished, or in-progress of being created.
|
@OnThread(Tag.FXPlatform)
private static class ExtendsDepInfo
{
private final Dependency.Line line;
private final boolean selected;
private final boolean creating;
private BDependency.Type type;
public ExtendsDepInfo(Dependency d)
{
this.line = d.computeLine();
this.selected = d.isSelected();
this.creating = false;
this.type=d.getType();
}
public ExtendsDepInfo(DependentTarget from, double toX, double toY)
{
Point2D pFrom = new Point2D(from.getX() + from.getWidth() / 2, from.getY() + from.getHeight() / 2);
Point2D pTo = new Point2D(toX, toY);
double angle = Math.atan2(-(pFrom.getY() - pTo.getY()), pFrom.getX() - pTo.getX());
pFrom = from.getAttachment(angle + Math.PI);
line = new Dependency.Line(pFrom, pTo, angle);
selected = false;
creating = true;
}
public ExtendsDepInfo(DependentTarget from, DependentTarget to)
{
Point2D pFrom = new Point2D(from.getX() + from.getWidth() / 2, from.getY() + from.getHeight() / 2);
Point2D pTo = new Point2D(to.getX() + to.getWidth() / 2, to.getY() + to.getHeight() / 2);
double angle = Math.atan2(-(pFrom.getY() - pTo.getY()), pFrom.getX() - pTo.getX());
pFrom = from.getAttachment(angle + Math.PI);
pTo = to.getAttachment(angle);
line = new Dependency.Line(pFrom, pTo, angle);
selected = false;
creating = true;
}
}
| Does the actual repaint of the arrowLayer (do not call directly;
| see repaint method).
|
private void actualRepaint()
{
aboutToRepaint = false;
List<Dependency> extendsDeps = isShowExtends() ? new ArrayList<>(pkg.getExtendsArrows()) : Collections.emptyList();;
List<UsesDependency> usesDeps = isShowUses() ? new ArrayList<>(pkg.getUsesArrows()) : Collections.emptyList();
List<ExtendsDepInfo> extendsLines = new ArrayList<>(Utility.mapList(extendsDeps, ExtendsDepInfo::new));
if (extendsSubClass != null)
{
if (extendsSuperClassHover != null)
{
extendsLines.add(new ExtendsDepInfo(extendsSubClass, extendsSuperClassHover));
}
else
{
Point2D p = arrowLayer.sceneToLocal(newExtendsDestX, newExtendsDestY);
extendsLines.add(new ExtendsDepInfo(extendsSubClass, p.getX(), p.getY()));
}
}
GraphicsContext g = arrowLayer.getGraphicsContext2D();
g.clearRect(0, 0, arrowLayer.getWidth(), arrowLayer.getHeight());
for (ExtendsDepInfo d : extendsLines)
{
g.setStroke(d.creating ? Color.BLUE : Color.BLACK);
g.setLineWidth(d.selected ? 3.0 : 1.0);
Dependency.Line line = d.line;
double fromY = line.from.getY();
double fromX = line.from.getX();
double toY = line.to.getY();
double toX = line.to.getX();
double angle = Math.atan2(-(fromY - toY), fromX - toX);
double arrowJoinX = toX + ((ARROW_SIZE - 2) * Math.cos(angle));
double arrowJoinY = toY - ((ARROW_SIZE - 2) * Math.sin(angle));
double[] xPoints = {toX, toX + ((ARROW_SIZE) * Math.cos(angle + ARROW_ANGLE)),
toX + (ARROW_SIZE * Math.cos(angle - ARROW_ANGLE))
};
double[] yPoints = {toY, toY - ((ARROW_SIZE) * Math.sin(angle + ARROW_ANGLE)),
toY - (ARROW_SIZE * Math.sin(angle - ARROW_ANGLE))
};
g.setLineDashes();
g.strokePolygon(xPoints, yPoints, 3);
if (d.type==BDependency.Type.IMPLEMENTS)
{
g.setLineDashes(DASHES);
}
else
{
g.setLineDashes();
}
g.strokeLine(fromX, fromY, arrowJoinX, arrowJoinY);
}
for (UsesDependency d : usesDeps)
{
g.setLineWidth(1.0);
g.setLineDashes(DASHES);
double src_x = d.getSourceX();
double src_y = d.getSourceY();
double dst_x = d.getDestX();
double dst_y = d.getDestY();
g.setStroke(Color.BLACK);
int delta_x = d.isEndLeft() ? -10 : 10;
g.strokeLine(dst_x, dst_y, dst_x + delta_x, dst_y + 4);
g.strokeLine(dst_x, dst_y, dst_x + delta_x, dst_y - 4);
g.setLineDashes(DASHES);
double corner_y = src_y + (d.isStartTop() ? -15 : 15);
g.strokeLine(src_x, corner_y, src_x, src_y);
src_y = corner_y;
double corner_x = dst_x + (d.isEndLeft() ? -15 : 15);
g.strokeLine(corner_x, dst_y, dst_x, dst_y);
dst_x = corner_x;
if ((src_y != dst_y) && (d.isStartTop() == (src_y < dst_y))) {
corner_x = Utility.roundHalf(((src_x + dst_x) / 2) + (d.isEndLeft() ? 15 : -15));
corner_x = (d.isEndLeft() ? Math.min(dst_x, corner_x) : Math.max(dst_x, corner_x));
g.strokeLine(src_x, src_y, corner_x, src_y);
src_x = corner_x;
}
if ((src_x != dst_x) && (d.isEndLeft() == (src_x > dst_x))) {
corner_y = Utility.roundHalf(((src_y + dst_y) / 2) + (d.isStartTop() ? 15 : -15));
corner_y = (d.isStartTop() ? Math.min(src_y, corner_y) : Math.max(src_y, corner_y));
g.strokeLine(dst_x, corner_y, dst_x, dst_y);
dst_y = corner_y;
}
g.strokeLine(src_x, src_y, src_x, dst_y);
g.strokeLine(src_x, dst_y, dst_x, dst_y);
}
}
| Clear the set of selected classes. (Nothing will be selected after this.)
|
public void clearSelection()
{
selectionController.clearSelection();
}
| Add to the current selection
| @param element the element to add
|
public void addToSelection(Target element)
{
selectionController.addToSelection(element);
}
| Toggles whether the given element is selected or not.
|
public void toggleSelection(Target element)
{
if (!element.isSelected())
selectionController.addToSelection(element);
else{ selectionController.removeFromSelection(element);
}
}
@OnThread(Tag.Any)
public Package getPackage()
{
return pkg;
}
| Start our mouse listener. This is not done in the constructor, because we want
| to give others (the PkgMgrFrame) the chance to listen first.
|
| NB: I'm not sure this is true now that we are in JavaFX.
|
public void startMouseListening()
{
addEventFilter(MouseEvent.MOUSE_PRESSED, e -> showingMenu(null));
addEventHandler(MouseEvent.MOUSE_DRAGGED, selectionController::mouseDragged);
addEventHandler(MouseEvent.MOUSE_CLICKED, selectionController::mouseClicked);
addEventHandler(MouseEvent.MOUSE_PRESSED, selectionController::mousePressed);
addEventHandler(MouseEvent.MOUSE_RELEASED, selectionController::mouseReleased);
JavaFXUtil.listenForContextMenu(this, this::popupMenu);
}
| Gets all the currently visible edges (depending on showUses/showExtends settings).
|
@OnThread(Tag.FXPlatform)
public synchronized Collection getVisibleEdges()
{
List<Dependency> deps = new ArrayList<>();
if (isShowUses())
deps.addAll(pkg.getUsesArrows());
if (isShowExtends())
deps.addAll(pkg.getExtendsArrows());
return deps;
}
| Returns the {}link Dependency} with the specified <code>origin</code>,
| <code>target</code> and <code>type</code> or <code>null</code> if there
| is no such dependency.
|
| @param origin
| The origin of the dependency.
| @param target
| The target of the dependency.
| @param type
| The type of the dependency (there may be more than one
| dependencies with the same origin and target but different
| types).
| @return The {}link Dependency} with the specified <code>origin</code>,
| <code>target</code> and <code>type</code> or <code>null</code> if
| there is no such dependency.
|
@OnThread(Tag.SwingIsFX)
public synchronized Dependency getDependency(DependentTarget origin, DependentTarget target, BDependency.Type type)
{
List<? extends Dependency> dependencies;
switch (type) {
case USES :
dependencies = pkg.getUsesArrows();
break;
case IMPLEMENTS :
case EXTENDS :
dependencies = pkg.getExtendsArrows();
break;
default :
return null;
}
for (Dependency dependency : dependencies) {
DependentTarget from = dependency.getFrom();
DependentTarget to = dependency.getTo();
if (from.equals(origin) && to.equals(target)) {
return dependency;
}
}
return null;
}
public void setShowUses(boolean state)
{
showUses.set(state);
}
public void setShowExtends(boolean state)
{
showExtends.set(state);
}
public boolean isShowUses()
{
return showUses.get();
}
public boolean isShowExtends()
{
return showExtends.get();
}
| Clears the state: stops creating a new extends arrow.
|
public void clearState()
{
if (creatingExtends)
stopNewInherits();
}
| Called to start creating a new inherits arrow.
|
public void doNewInherits()
{
if (!creatingExtends) {
arrowCreationTip = new Label(Config.getString("pkgmgr.chooseInhFrom"));
JavaFXUtil.addStyleClass(arrowCreationTip, "pmf-create-extends-tip");
overlay.addMouseTrackingOverlay(arrowCreationTip, false, new ReadOnlyDoubleWrapper(5.0), arrowCreationTip.heightProperty().negate().add(-5.0));
creatingExtends = true;
extendsSubClass = null;
JavaFXUtil.setPseudoclass("bj-drawing-extends", true, this);
for (Target t : pkg.getVertices())
t.setCreatingExtends(true);
repaint();
}
}
| Stops creating a new inherits arrow.
|
private void stopNewInherits()
{
overlay.removeMouseListener(this);
overlay.remove(arrowCreationTip);
arrowCreationTip = null;
creatingExtends = false;
extendsSubClass = null;
extendsSuperClassHover = null;
JavaFXUtil.setPseudoclass("bj-drawing-extends", false, this);
for (Target t : pkg.getVertices())
t.setCreatingExtends(false);
repaint();
}
public boolean doRemoveDependency()
{
return false;
}
| Selects the given single target, and no others.
|
public void selectOnly(Target target)
{
selectionController.selectOnly(target);
}
| Modify the given point to be one of the defined grid points.
|
| @param x The original point's x coordinate.
| @return The x coordinate of a point close to the original which is on the grid.
|
public int snapToGrid(int x)
{
int steps = (int)Math.round((double)x / PackageEditor.GRID_SIZE);
int new_x = steps * PackageEditor.GRID_SIZE;
return new_x;
}
| Started a move operation on the selection
|
public void startedMove()
{
for (Target element : selectionController.getSelection())
{
if (element.isMoveable())
element.savePreMovePosition();
}
}
| Performing a move operation: move by the given amounts
| from the last time we called startedMove
|
public void moveBy(int deltaX, int deltaY)
{
for (Target element : selectionController.getSelection())
{
if (element.isMoveable())
element.setPos(Math.max(0, element.getPreMoveX() + deltaX), Math.max(0, element.getPreMoveY() + deltaY));
}
}
| Started a move operation on the selection
|
public void startedResize()
{
for (Target element : selectionController.getSelection())
{
if (element.isResizable())
{
element.savePreResize();
JavaFXUtil.setPseudoclass("bj-resizing", true, element.getNode());
}
}
}
| Performing a resize operation: resize by the given amounts
| from the last time we called startedResize
|
public void resizeBy(int deltaWidth, int deltaHeight)
{
for (Target element : selectionController.getSelection())
{
if (element.isResizable())
element.setSize(Math.max(40, element.getPreResizeWidth() + deltaWidth), Math.max(20, element.getPreResizeHeight() + deltaHeight));
}
}
| Finishes a resize operation.
|
public void endResize()
{
for (Target element : selectionController.getSelection())
{
if (element.isResizable())
JavaFXUtil.setPseudoclass("bj-resizing", false, element.getNode());
}
}
| Navigates the diagram according to the given key press.
|
public void navigate(KeyEvent event)
{
selectionController.navigate(event);
}
| Select everything in the diagram.
|
public void selectAll()
{
selectionController.selectAll();
}
| The given target has been clicked. If we are creating a new extends
| arrow then select this target and return true. If we are not creating
| a new extends arrow, return false.
|
public boolean clickForExtends(Target target, double sceneX, double sceneY)
{
if (creatingExtends && target instanceof ClassTarget)
{
if (extendsSubClass == null)
{
extendsSubClass = (ClassTarget)target;
arrowCreationTip.setText(Config.getString("pkgmgr.chooseInhTo"));
overlay.addMouseListener(this);
newExtendsDestX = sceneX;
newExtendsDestY = sceneY;
repaint();
}
else
{
ClassTarget subClassFinal = this.extendsSubClass;
if (!subClassFinal.hasSourceCode())
{
DialogManager.showErrorFX(pmf.getFXWindow(),
"no-extends-arrow-from-no-source-class");
clearState();
return false;
}
ClassTarget superClass = (ClassTarget)target;
if (subClassFinal.isInterface())
{
if (superClass.isInterface())
pkg.userAddExtendsInterfaceDependency(subClassFinal, superClass);
}
else
{
if (superClass.isInterface())
pkg.userAddImplementsClassDependency(subClassFinal, superClass);
else{ pkg.userAddExtendsClassDependency(subClassFinal, superClass);
}
}
pkg.compile(subClassFinal, CompileReason.MODIFIED_EXTENDS, CompileType.INDIRECT_USER_COMPILE);
stopNewInherits();
}
}
return false;
}
@Override
public void mouseMoved(double localX, double localY)
{
newExtendsDestX = localX;
newExtendsDestY = localY;
repaint();
}
@Override
public Stage getStage()
{
return pmf.getFXWindow();
}
@Override
public void callStaticMethodOrConstructor(CallableView view)
{
pmf.callStaticMethodOrConstructor(view);
}
@Override
public void highlightObject(DebuggerObject currentObject)
{
pmf.getObjectBench().highlightObject(currentObject);
}
| An AnchorPane with extra space at the right and bottom.
| There's no API/CSS for this, so we override the size computations
| and add the spacing to the parent's return value.
|
@OnThread(Tag.FX)
private static class AnchorPaneExtraSpacing
extends AnchorPane
{
public static final double EXTRA_SPACE = 20.0;
@Override
protected double computePrefWidth(double height)
{
return super.computePrefWidth(height) + EXTRA_SPACE;
}
@Override
protected double computePrefHeight(double width)
{
return super.computePrefHeight(width) + EXTRA_SPACE;
}
@Override
protected double computeMinHeight(double width)
{
return super.computeMinHeight(width) + EXTRA_SPACE;
}
@Override
protected double computeMinWidth(double height)
{
return super.computeMinWidth(height) + EXTRA_SPACE;
}
}
}
. - PackageEditor
. PackageEditor
. benchToFixture
. fixtureToBench
. makeTestCase
. runTest
. openPackage
. raisePutOnBenchEvent
. recordInteraction
. popupMenu
. showingMenu
. getFXWindow
. findSpaceForVertex
. graphChanged
. graphClosed
. repaint
. setMouseIn
. setMouseLeft
. checkForLossOfFocus
. targetHasFocus
. focusSelectedOrArbitrary
. isCreatingExtends
top,
use,
map,
class ExtendsDepInfo
. ExtendsDepInfo
. ExtendsDepInfo
. ExtendsDepInfo
. actualRepaint
. clearSelection
. addToSelection
. toggleSelection
. getPackage
. startMouseListening
. getVisibleEdges
. getDependency
. setShowUses
. setShowExtends
. isShowUses
. isShowExtends
. clearState
. doNewInherits
. stopNewInherits
. doRemoveDependency
. selectOnly
. snapToGrid
. startedMove
. moveBy
. startedResize
. resizeBy
. endResize
. navigate
. selectAll
. clickForExtends
. mouseMoved
. getStage
. callStaticMethodOrConstructor
. highlightObject
top,
use,
map,
class AnchorPaneExtraSpacing
. computePrefWidth
. computePrefHeight
. computeMinHeight
. computeMinWidth
1216 neLoCode
+ 83 LoComm