package greenfoot.guifx.classes;
import greenfoot.guifx.GreenfootStage;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
import threadchecker.OnThread;
import threadchecker.Tag;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
| A hierarchical tree display of classes. There can be zero-to-unlimited root classes,
| each of which can have zero-to-unlimited subclasses, and each of those can
| have zero-unlimited subclasses, and so on, all the way down.
|
| Inheritance arrows are drawn for each subclass relation. Classes are sorted
| alphabetically at each level in the hierarchy.
|
@OnThread(Tag.FXPlatform)
public class ClassGroup extends Pane implements ChangeListener<Number>{
public static final int VERTICAL_SPACING = 8;
private final List<GClassNode> topLevel = new ArrayList<>();
private final GreenfootStage greenfootStage;
public ClassGroup(GreenfootStage greenfootStage)
{
this.greenfootStage = greenfootStage;
getStyleClass().add("class-group");
setMinHeight(USE_PREF_SIZE);
setMinWidth(USE_PREF_SIZE);
setMaxWidth(Double.MAX_VALUE);
setMaxHeight(Double.MAX_VALUE);
}
| Sets the top-level classes for this class group.
|
public void setClasses(List<GClassNode> topLevel)
{
for (Node child : getChildren())
{
if (child instanceof ClassDisplay)
{
((ClassDisplay) child).widthProperty().removeListener(this);
((ClassDisplay) child).heightProperty().removeListener(this);
}
}
getChildren().clear();
for (GClassNode classInfo : this.topLevel)
{
classInfo.tidyup();
}
this.topLevel.clear();
this.topLevel.addAll(topLevel);
updateAfterAdd();
}
| Gets the live list of top-level classes in this group. This should only be used for adding, not for
| removal. If you add a class anywhere within, you should then call updateAfterAdd().
| Note: only gets top-level classes, does not get subclasses.
|
public List getLiveTopLevelClasses()
{
return topLevel;
}
| Gets a stream containing all the classes in this group, both top-level
| and subclasses.
|
public Stream streamAllClasses()
{
return topLevel.stream().flatMap(c -> streamInclSubclasses(c));
}
| Helper method to get a stream containing the given class and all its subclasses
| (all the way down to the bottom: subsubclasses, etc)
|
private static Stream streamInclSubclasses(GClassNode item)
{
return Stream.concat(Stream.of(item),
item.getSubClasses().stream().flatMap(c -> streamInclSubclasses(c)));
}
| Refreshes display after a class has been added to the diagram.
|
public void updateAfterAdd()
{
Collections.sort(this.topLevel, Comparator.comparing(GClassNode::getDisplayName));
redisplay();
}
private void redisplay()
{
int leftIndent = VERTICAL_SPACING;
redisplay(null, topLevel, leftIndent, 0);
requestLayout();
}
| Lay out the list of classes vertically, at the same indent.
| Also lay out any subclasses.
|
| @param arrowToSuper Either null (no superclass) or a vertical inherit arrow to update
| the position of, once we've laid out all classes in the stratum.
| @param stratum The list of classes to layout (in list order)
| @param x The current X position for all the classes
| @param y The Y position for the top class.
| @return The resulting Y position after doing the layout.
|
private int redisplay(InheritArrow arrowToSuper, List<GClassNode> stratum, int x, int y)
{
final int startY = y;
List<Double> arrowArms = new ArrayList<>();
for (GClassNode classInfo : stratum)
{
y += VERTICAL_SPACING;
ClassDisplay classDisplay = classInfo.getDisplay(greenfootStage);
if (!getChildren().contains(classDisplay))
{
getChildren().add(classDisplay);
classDisplay.widthProperty().addListener(this);
classDisplay.heightProperty().addListener(this);
}
double halfHeight = Math.floor(classDisplay.getHeight() / 2.0);
arrowArms.add(y + halfHeight - startY);
classDisplay.setLayoutX(x);
classDisplay.setLayoutY(y);
y += classDisplay.getHeight();
if (!classInfo.getSubClasses().isEmpty())
{
if (!getChildren().contains(classInfo.getArrowFromSub()))
{
getChildren().add(classInfo.getArrowFromSub());
}
classInfo.getArrowFromSub().setLayoutX(x + 5 + 0.5);
classInfo.getArrowFromSub().setLayoutY(y + 0.5);
y = redisplay(classInfo.getArrowFromSub(), classInfo.getSubClasses(), x + 20, y);
}
else
{
getChildren().remove(classInfo.getArrowFromSub());
}
}
if (arrowToSuper != null)
{
arrowToSuper.setArmLocations(15.0, arrowArms);
}
return y;
}
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
protected double computePrefHeight(double width)
{
return getChildren().stream()
.filter(c -> c instanceof ClassDisplay)
.mapToDouble(c -> VERTICAL_SPACING + c.prefHeight(width))
.sum();
}
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
protected double computePrefWidth(double height)
{
return getChildren().stream()
.filter(c -> c instanceof ClassDisplay)
.mapToDouble(c -> c.getLayoutX() + c.prefWidth(-1))
.max().orElse(0.0)
+ VERTICAL_SPACING;
}
| Listener for when width or height changes on any of the classes.
| All we want to do in that case is request a layout. Note we don't want to
| just use a lambda, because we also want to remove the listener later.
|
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue)
{
redisplay();
}
| Save image selections to a properties file.
|
public void saveImageSelections(Properties p)
{
for (GClassNode gcn : topLevel)
{
saveImageSelections(gcn, p);
}
}
| Save the image selections for a particular class node, and its subclasses, to a properties
| file.
|
private void saveImageSelections(GClassNode gcn, Properties p)
{
String imageName = gcn.getImageFilename();
if (imageName != null) {
p.put("class." + gcn.getQualifiedName() + ".image", imageName);
}
for (GClassNode gcnSubclass : gcn.getSubClasses())
{
saveImageSelections(gcnSubclass, p);
}
}
}
. ClassGroup
. setClasses
. getLiveTopLevelClasses
. streamAllClasses
. streamInclSubclasses
. updateAfterAdd
. redisplay
. redisplay
. computePrefHeight
. computePrefWidth
. changed
. saveImageSelections
. saveImageSelections
261 neLoCode
+ 28 LoComm