<commit_before>package se.mju.stackanalyzer.ui;

import static java.lang.Math.max;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import se.mju.stackanalyzer.StackTraceStatistics;
import se.mju.stackanalyzer.StackTracesStatistics;
import se.mju.stackanalyzer.model.StackTrace;

public class StacksPane extends Pane {
	
	private static final double BOX_HEIGHT = 20;
	private static final double INVISIBLE_HEIGHT = BOX_HEIGHT*3;
	public static final double BOX_MARGIN = 0;
	private StackTrace stackTrace;
	private Rectangle rect;
	private Rectangle invisibleChildren;
	private float invokationsComparedToParent;
	private Label label;
	private Label invokations;
	private StackPane levelComponents = new StackPane();
	private Color color;
	private boolean childrenCreated = false;
	private StackTracesStatistics stats;

	public StacksPane(final StackTraceStatistics traceStats, final StackTracesStatistics stats) {
		this.stackTrace = traceStats.geftStacktace();
		this.stats = stats;
		setPrefSize(600, 400);
		
        rect = new Rectangle(0,BOX_HEIGHT);
        
        invisibleChildren = new Rectangle(0, INVISIBLE_HEIGHT);
        invisibleChildren.setFill(createGradient(Color.GRAY, INVISIBLE_HEIGHT*1.5));

        if (stackTrace != null) {
        	label = new Label(stackTrace.getMethod());
        	//label.setTooltip(new Tooltip(stackTrace.getFullyQualifiedName()));
        	
        	invokations = new Label("" + stats.getInvokations(stackTrace));
        } else {
        	label = new Label("");
        	invokations = new Label("");
        }
        
        getChildren().add(rect);
        getChildren().add(invisibleChildren);
        getChildren().add(levelComponents);
        
        levelComponents.getChildren().add(invokations);
        StackPane.setAlignment(invokations, Pos.TOP_LEFT);
        invokations.setTextFill(Color.GRAY);
        //invokations.setStyle("-fx-font-size: 80%;"); // Gives out of mem when processing css
        
        levelComponents.getChildren().add(label);
        StackPane.setAlignment(label, Pos.BOTTOM_CENTER);
        //levelComponents.setStyle("-fx-border-color: green;");
        if (stackTrace != null) {
        	Tooltip tooltip = new Tooltip();
        	tooltip.activatedProperty().addListener(
        			(ov,  old_val,  new_val) -> {
        					tooltip.setText(stackTrace.getFullyQualifiedName() + "\n" 
        					+ Math.round(traceStats.getInvakationsComparedToParent() * 100) + "% of parent\n"
        					+ Math.round(traceStats.getInvakationsComparedToRoot() * 100) + "% of root");
        			}
        	);
			Tooltip.install(this, tooltip);
        }
        
        this.invokationsComparedToParent = traceStats.getInvakationsComparedToParent();
        color = StackpaneController.getColor(stackTrace);
	}

	private void createChildernIfNeeded(StackTrace stackTrace, final StackTracesStatistics stats) {
		if (childrenCreated) {
			return;
		}
		childrenCreated = true;
		for (StackTraceStatistics x: stats.getStatForChildrens(stackTrace)) {
        	StacksPane childPane = new StacksPane(x, stats);
        	getChildren().add(childPane);
        	this.addMouseListener(levelComponents.getOnMouseClicked());
        }
	}
	
	public void addMouseListener(EventHandler<? super MouseEvent> eventHandler) {
		levelComponents.setOnMouseClicked(eventHandler);
		for (Node child : getChildren()) {
			if (child instanceof StacksPane) {
				StacksPane stackPane = (StacksPane) child;
				stackPane.addMouseListener(eventHandler);
			}
		}
	}
	
	@Override
	public void resize(double width, double height) {
		resizeAndReturnRectHeight(width, height);
	}

	private double resizeAndReturnRectHeight(double width, double height) {
		super.resize(width, height);
		createChildernIfNeeded(stackTrace, stats);
		if (stackTrace == null) {
			height += BOX_HEIGHT; // Hide parts of the artificial root box
		}
		levelComponents.resize(width, BOX_HEIGHT);
		levelComponents.setLayoutY(height - BOX_HEIGHT);
		invisibleChildren.setLayoutY(height - BOX_HEIGHT - INVISIBLE_HEIGHT);
		
		if (width < 15) {
			levelComponents.setVisible(false);
		} else {
			levelComponents.setVisible(true);
		}
		

		double x = 0;
		List<StacksPane> schildren = getChildStacksPanes();
		int childCount = schildren.size();
		double childHeight = height- BOX_HEIGHT - BOX_MARGIN;
		double maxChildRextHeight = 0;
		double invisibleChildrensWidth = 0;
		for (StacksPane childPane : schildren) {
			double childWidth = width * childPane.invokationsComparedToParent - BOX_MARGIN * (childCount - 1) / childCount;
			if (childWidth > 3 && childHeight > -BOX_HEIGHT) {
				childPane.setLayoutX(x);
				double childRectHeight = childPane.resizeAndReturnRectHeight(childWidth, childHeight);
				maxChildRextHeight = max(maxChildRextHeight, childRectHeight);
				
				x += childWidth + BOX_MARGIN;
				childPane.setVisible(true);
			} else {
				invisibleChildrensWidth += childWidth;
				childPane.setVisible(false);
			}
		}

		invisibleChildren.setLayoutX(x);
		invisibleChildren.setWidth(invisibleChildrensWidth);
		invisibleChildren.setVisible(invisibleChildrensWidth > 1);
		
		double rectHeight = maxChildRextHeight + BOX_HEIGHT + BOX_MARGIN;
		rect.setWidth(width);
		rect.setHeight(rectHeight);
		rect.setLayoutY(height - rectHeight);
		
		LinearGradient lg1 = createGradient(color, rectHeight);
		rect.setFill(lg1);
		
		return rectHeight;
	}

	private LinearGradient createGradient(Color color, double rectHeight) {
		Stop[] stops = new Stop[] {new Stop(0, color), new Stop(1, Color.WHITE)};
		return new LinearGradient(0, rectHeight, 0, rectHeight-4*BOX_HEIGHT, false, CycleMethod.NO_CYCLE, stops);
	}
	
	@Override
	protected void layoutChildren() {
		// The work is done in resize. Do not let default implementation mess that up
	}
	
	private List<StacksPane> getChildStacksPanes() {
		ObservableList<Node> children = getChildren();
		List<StacksPane> schildren = new ArrayList<>(children.size());
		for (Node child : children) {
			if (child instanceof StacksPane) {
				schildren.add((StacksPane) child);
			}
		}
		return schildren;
	}

	public StackTrace getStackTrace() {
		return stackTrace;
	}

	public Stream<StacksPane> asStream() {
		List<StacksPane> panes = new ArrayList<>();
		asStream(panes);
		return panes.stream();
	}

	private void asStream(List<StacksPane> panes) {
		panes.add(this);
		for (StacksPane child : getChildStacksPanes()) {
			panes.add(child);
		}
	}
	
	@Override
	public String toString() {
		return stackTrace.toString();
	}
}
<commit_msg>Fixed tooltip problem.<commit_after>package se.mju.stackanalyzer.ui;

import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import se.mju.stackanalyzer.StackTraceStatistics;
import se.mju.stackanalyzer.StackTracesStatistics;
import se.mju.stackanalyzer.model.StackTrace;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import static java.lang.Math.max;

public class StacksPane extends Pane {
	
	private static final double BOX_HEIGHT = 20;
	private static final double INVISIBLE_HEIGHT = BOX_HEIGHT*3;
	public static final double BOX_MARGIN = 0;
	private StackTrace stackTrace;
	private Rectangle rect;
	private Rectangle invisibleChildren;
	private float invokationsComparedToParent;
	private Label label;
	private Label invokations;
	private StackPane levelComponents = new StackPane();
	private Color color;
	private boolean childrenCreated = false;
	private StackTracesStatistics stats;

	public StacksPane(final StackTraceStatistics traceStats, final StackTracesStatistics stats) {
		this.stackTrace = traceStats.geftStacktace();
		this.stats = stats;
		setPrefSize(600, 400);
		
        rect = new Rectangle(0,BOX_HEIGHT);
        
        invisibleChildren = new Rectangle(0, INVISIBLE_HEIGHT);
        invisibleChildren.setFill(createGradient(Color.GRAY, INVISIBLE_HEIGHT*1.5));

        if (stackTrace != null) {
        	label = new Label(stackTrace.getMethod());
        	//label.setTooltip(new Tooltip(stackTrace.getFullyQualifiedName()));
        	
        	invokations = new Label("" + stats.getInvokations(stackTrace));
        } else {
        	label = new Label("");
        	invokations = new Label("");
        }
        
        getChildren().add(rect);
        getChildren().add(invisibleChildren);
        getChildren().add(levelComponents);
        
        levelComponents.getChildren().add(invokations);
        StackPane.setAlignment(invokations, Pos.TOP_LEFT);
        invokations.setTextFill(Color.GRAY);
        //invokations.setStyle("-fx-font-size: 80%;"); // Gives out of mem when processing css
        
        levelComponents.getChildren().add(label);
        StackPane.setAlignment(label, Pos.BOTTOM_CENTER);
        //levelComponents.setStyle("-fx-border-color: green;");
        if (stackTrace != null) {
        	Tooltip tooltip = new Tooltip();
        	tooltip.activatedProperty().addListener(
        			(ov,  old_val,  new_val) -> {
        					tooltip.setText(stackTrace.getFullyQualifiedName() + "\n" 
        					+ Math.round(traceStats.getInvakationsComparedToParent() * 100) + "% of parent\n"
        					+ Math.round(traceStats.getInvakationsComparedToRoot() * 100) + "% of root");
        			}
        	);
			Tooltip.install(label, tooltip);
        }
        
        this.invokationsComparedToParent = traceStats.getInvakationsComparedToParent();
        color = StackpaneController.getColor(stackTrace);
	}

	private void createChildernIfNeeded(StackTrace stackTrace, final StackTracesStatistics stats) {
		if (childrenCreated) {
			return;
		}
		childrenCreated = true;
		for (StackTraceStatistics x: stats.getStatForChildrens(stackTrace)) {
        	StacksPane childPane = new StacksPane(x, stats);
        	getChildren().add(childPane);
        	this.addMouseListener(levelComponents.getOnMouseClicked());
        }
	}
	
	public void addMouseListener(EventHandler<? super MouseEvent> eventHandler) {
		levelComponents.setOnMouseClicked(eventHandler);
		for (Node child : getChildren()) {
			if (child instanceof StacksPane) {
				StacksPane stackPane = (StacksPane) child;
				stackPane.addMouseListener(eventHandler);
			}
		}
	}
	
	@Override
	public void resize(double width, double height) {
		resizeAndReturnRectHeight(width, height);
	}

	private double resizeAndReturnRectHeight(double width, double height) {
		super.resize(width, height);
		createChildernIfNeeded(stackTrace, stats);
		if (stackTrace == null) {
			height += BOX_HEIGHT; // Hide parts of the artificial root box
		}
		levelComponents.resize(width, BOX_HEIGHT);
		levelComponents.setLayoutY(height - BOX_HEIGHT);
		invisibleChildren.setLayoutY(height - BOX_HEIGHT - INVISIBLE_HEIGHT);
		
		if (width < 15) {
			levelComponents.setVisible(false);
		} else {
			levelComponents.setVisible(true);
		}
		

		double x = 0;
		List<StacksPane> schildren = getChildStacksPanes();
		int childCount = schildren.size();
		double childHeight = height- BOX_HEIGHT - BOX_MARGIN;
		double maxChildRextHeight = 0;
		double invisibleChildrensWidth = 0;
		for (StacksPane childPane : schildren) {
			double childWidth = width * childPane.invokationsComparedToParent - BOX_MARGIN * (childCount - 1) / childCount;
			if (childWidth > 3 && childHeight > -BOX_HEIGHT) {
				childPane.setLayoutX(x);
				double childRectHeight = childPane.resizeAndReturnRectHeight(childWidth, childHeight);
				maxChildRextHeight = max(maxChildRextHeight, childRectHeight);
				
				x += childWidth + BOX_MARGIN;
				childPane.setVisible(true);
			} else {
				invisibleChildrensWidth += childWidth;
				childPane.setVisible(false);
			}
		}

		invisibleChildren.setLayoutX(x);
		invisibleChildren.setWidth(invisibleChildrensWidth);
		invisibleChildren.setVisible(invisibleChildrensWidth > 1);
		
		double rectHeight = maxChildRextHeight + BOX_HEIGHT + BOX_MARGIN;
		rect.setWidth(width);
		rect.setHeight(rectHeight);
		rect.setLayoutY(height - rectHeight);
		
		LinearGradient lg1 = createGradient(color, rectHeight);
		rect.setFill(lg1);
		
		return rectHeight;
	}

	private LinearGradient createGradient(Color color, double rectHeight) {
		Stop[] stops = new Stop[] {new Stop(0, color), new Stop(1, Color.WHITE)};
		return new LinearGradient(0, rectHeight, 0, rectHeight-4*BOX_HEIGHT, false, CycleMethod.NO_CYCLE, stops);
	}
	
	@Override
	protected void layoutChildren() {
		// The work is done in resize. Do not let default implementation mess that up
	}
	
	private List<StacksPane> getChildStacksPanes() {
		ObservableList<Node> children = getChildren();
		List<StacksPane> schildren = new ArrayList<>(children.size());
		for (Node child : children) {
			if (child instanceof StacksPane) {
				schildren.add((StacksPane) child);
			}
		}
		return schildren;
	}

	public StackTrace getStackTrace() {
		return stackTrace;
	}

	public Stream<StacksPane> asStream() {
		List<StacksPane> panes = new ArrayList<>();
		asStream(panes);
		return panes.stream();
	}

	private void asStream(List<StacksPane> panes) {
		panes.add(this);
		for (StacksPane child : getChildStacksPanes()) {
			panes.add(child);
		}
	}
	
	@Override
	public String toString() {
		return stackTrace.toString();
	}
}

{'fraction_non_alphanumeric': 0.10453579021970234, 'fraction_numerical': 0.005244507441530829, 'mean_word_length': 3.9826977401129944, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '967087', 'n_tokens_mistral': 4730, 'n_tokens_neox': 4327, 'n_words': 894}