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