MainPane.java
/**
* Copyright (c) 2016 European Organisation for Nuclear Research (CERN), All Rights Reserved.
*/
package org.minifx.workbench.components;
import static java.util.Collections.singletonList;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toList;
import static org.minifx.workbench.css.MiniFxCssConstants.PERSPECTIVE_BUTTON_CLASS;
import static org.minifx.workbench.css.MiniFxCssConstants.TOOLBAR_BUTTON_CLASS;
import static org.minifx.workbench.util.MiniFxComponents.containerPaneFrom;
import static org.minifx.workbench.util.MiniFxComponents.createPerspective;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import org.minifx.workbench.css.MiniFxCssConstants;
import org.minifx.workbench.domain.definition.DisplayProperties;
import org.minifx.workbench.domain.definition.FooterDefinition;
import org.minifx.workbench.domain.definition.PerspectiveDefinition;
import org.minifx.workbench.domain.definition.ToolbarItemDefinition;
import org.minifx.workbench.spring.AbstractPerspectiveEvent;
import org.minifx.workbench.spring.ActivatePerspectiveCommand;
import org.minifx.workbench.spring.ChangePerspectiveButtonStyleCommand;
import org.minifx.workbench.spring.PerspectiveActivatedEvent;
import org.minifx.workbench.util.MiniFxComponents;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
/**
* Main JavaFX Pane for a MiniFx application. It has a toolbar for perspective switching at the top. The active
* perspective is integrated in the center.
*/
public class MainPane extends BorderPane {
private static final HBox DEFAULT_FILLER = new HBox();
private final Map<Object, Node> perspectives = new HashMap<>();
private final Map<Object, ToggleButton> perspectivesToButtons = new HashMap<>();
private final ApplicationEventPublisher publisher;
private HBox perspectiveButtonsBox;
private ToggleGroup perspectiveButtonToggleGroup = new ToggleGroup();
public MainPane(Collection<ToolbarItemDefinition> toolbarItems, ApplicationEventPublisher publisher) {
this(toolbarItems, DEFAULT_FILLER, publisher);
}
public MainPane(Collection<ToolbarItemDefinition> toolbarItems, Node filler, ApplicationEventPublisher publisher) {
this.publisher = publisher;
List<Node> toolbarNodes = toolbarItems.stream()//
.sorted(comparing(ToolbarItemDefinition::order))//
.map(ToolbarItemDefinition::node)//
.collect(toList());
HBox toolbarBox = horizontalBoxOf(Pos.TOP_LEFT);
HBox centralBox = horizontalBoxOf(Pos.TOP_CENTER);
perspectiveButtonsBox = horizontalBoxOf(Pos.TOP_RIGHT);
addToolbarButtonsTo(toolbarNodes, toolbarBox);
addToolbarButtonsTo(singletonList(filler), centralBox);
HBox.setHgrow(centralBox, Priority.ALWAYS);
setTop(new ToolBar(toolbarBox, centralBox, perspectiveButtonsBox));
}
/**
* Set the {@link FooterDefinition}s that will be added to the pane. NOTE: this method can be called only once
* in the current implementation!
*/
public void setFooters(Collection<FooterDefinition> footers) {
if (getBottom() != null) {
/* A refactor of MiniFxComponents#containerPaneFrom would allow dynamic insertions of footer items */
throw new IllegalStateException("Footer already initialized!");
}
Platform.runLater(() -> containerPaneFrom(footers).map(MiniFxComponents::configureSingleNodeStyle)
.ifPresent(this::setBottom));
}
/**
* Add {@link PerspectiveDefinition}s to the pane. NOTE: this method can be called only once in the current
* implementation!
*/
public void setPerspectives(Collection<PerspectiveDefinition> definitions) {
if (definitions.isEmpty()) {
return;
}
if (!perspectives.isEmpty()) {
throw new IllegalStateException("Perspectives already initialized!");
}
definitions.forEach(definition -> perspectives.put(definition.perspective(), createPerspective(definition)));
List<ToggleButton> orderedPerspectiveButtons = definitions.stream() //
.sorted(Comparator.comparingInt(p -> p.displayProperties().order())) //
.map(this::createPerspectiveButton) //
.collect(Collectors.toList());
addToolbarButtonsTo(orderedPerspectiveButtons, perspectiveButtonsBox);
}
private static HBox horizontalBoxOf(Pos alignment) {
HBox box = new HBox();
box.setSpacing(3);
box.setAlignment(alignment);
box.setFillHeight(true);
return box;
}
private static void addToolbarButtonsTo(List<? extends Node> nodes, HBox box) {
nodes.forEach(node -> node.getStyleClass().add(TOOLBAR_BUTTON_CLASS));
box.getChildren().addAll(nodes);
}
private ToggleButton createPerspectiveButton(PerspectiveDefinition perspective) {
DisplayProperties displayProperties = perspective.displayProperties();
ToggleButton button = new ToggleButton(displayProperties.name(), displayProperties.graphic().orElse(null));
button.setGraphicTextGap(10);
button.setOnAction(evt -> setActive(perspective.perspective()));
button.getStyleClass().addAll(PERSPECTIVE_BUTTON_CLASS, TOOLBAR_BUTTON_CLASS);
perspectivesToButtons.put(perspective.perspective(), button);
perspectiveButtonToggleGroup.getToggles().add(button);
return button;
}
private void setActive(Object perspectiveDefinition) {
Node perspective = this.perspectives.get(perspectiveDefinition);
perspective.getStyleClass().add(MiniFxCssConstants.MAIN_PANE_CLASS);
setCenter(perspective);
publisher.publishEvent(new PerspectiveActivatedEvent(perspectiveDefinition));
}
private Optional<ToggleButton> perspectiveButton(AbstractPerspectiveEvent command) {
return Optional.of(this.perspectivesToButtons.get(command.perspective()));
}
@EventListener
public void activatePerspective(ActivatePerspectiveCommand command) {
Platform.runLater(() -> perspectiveButton(command).ifPresent(ToggleButton::fire));
}
@EventListener
public void colorPerspectiveButton(ChangePerspectiveButtonStyleCommand command) {
Platform.runLater(() -> perspectiveButton(command).map(b -> b.getStyleClass()).ifPresent(command::apply));
}
}