/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); You may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.speedment.tool.core.internal.controller;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.generator.core.component.EventComponent;
import com.speedment.generator.core.event.ProjectLoaded;
import com.speedment.runtime.config.trait.HasEnabled;
import com.speedment.runtime.config.trait.HasMainInterface;
import com.speedment.runtime.config.util.DocumentUtil;
import com.speedment.tool.config.*;
import com.speedment.tool.config.trait.HasEnabledProperty;
import com.speedment.tool.config.trait.HasExpandedProperty;
import com.speedment.tool.config.trait.HasIconPath;
import com.speedment.tool.config.trait.HasNameProperty;
import com.speedment.tool.core.component.UserInterfaceComponent;
import com.speedment.tool.core.resource.SpeedmentIcon;
import java.net.URL;
import static java.util.Objects.requireNonNull;
import java.util.ResourceBundle;
import java.util.stream.Stream;
import static javafx.application.Platform.runLater;
import javafx.beans.binding.Bindings;
import javafx.beans.value.ChangeListener;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.MenuItem;
import static javafx.scene.control.SelectionMode.SINGLE;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
/**
*
* @author Emil Forslund
*/
public final class ProjectTreeController implements Initializable {
private @Inject UserInterfaceComponent ui;
private @Inject EventComponent events;
private @FXML TreeView<DocumentProperty> hierarchy;
@Override
public void initialize(URL location, ResourceBundle resources) {
ui.installContextMenu(ProjectProperty.class, this::createDefaultContextMenu);
ui.installContextMenu(DbmsProperty.class, this::createDefaultContextMenu);
ui.installContextMenu(SchemaProperty.class, this::createDefaultContextMenu);
ui.installContextMenu(TableProperty.class, this::createDefaultContextMenu);
ui.installContextMenu(IndexProperty.class, this::createDefaultContextMenu);
ui.installContextMenu(ForeignKeyProperty.class, this::createDefaultContextMenu);
runLater(() -> prepareTree(ui.projectProperty()));
}
private void prepareTree(ProjectProperty project) {
requireNonNull(project);
events.notify(new ProjectLoaded(project));
Bindings.bindContent(ui.getSelectedTreeItems(), hierarchy.getSelectionModel().getSelectedItems());
hierarchy.setCellFactory(view -> new DocumentPropertyCell(ui));
hierarchy.getSelectionModel().setSelectionMode(SINGLE);
populateTree(project);
}
private void populateTree(ProjectProperty project) {
requireNonNull(project);
final TreeItem<DocumentProperty> root = branch(project);
hierarchy.setRoot(root);
hierarchy.getSelectionModel().select(root);
}
private <P extends DocumentProperty & HasExpandedProperty> TreeItem<DocumentProperty> branch(P doc) {
requireNonNull(doc);
final TreeItem<DocumentProperty> branch = new TreeItem<>(doc);
branch.expandedProperty().bindBidirectional(doc.expandedProperty());
final ListChangeListener<? super DocumentProperty> onListChange = (ListChangeListener.Change<? extends DocumentProperty> change) -> {
while (change.next()) {
if (change.wasAdded()) {
change.getAddedSubList().stream()
.filter(HasExpandedProperty.class::isInstance)
.map(d -> (DocumentProperty & HasExpandedProperty) d)
.map(this::branch)
.forEachOrdered(branch.getChildren()::add);
}
if (change.wasRemoved()) {
change.getRemoved()
.forEach(val -> branch.getChildren()
.removeIf(item -> val.equals(item.getValue()))
);
}
}
};
// Create a branch for every child
doc.children()
.filter(HasExpandedProperty.class::isInstance)
.map(d -> (DocumentProperty & HasExpandedProperty) d)
.map(this::branch)
.forEachOrdered(branch.getChildren()::add);
// Listen to changes in the actual map
doc.childrenProperty().addListener((MapChangeListener.Change<? extends String, ? extends ObservableList<DocumentProperty>> change) -> {
if (change.wasAdded()) {
// Listen for changes in the added list
change.getValueAdded().addListener(onListChange);
// Create a branch for every child
change.getValueAdded().stream()
.filter(HasExpandedProperty.class::isInstance)
.map(d -> (DocumentProperty & HasExpandedProperty) d)
.map(this::branch)
.forEachOrdered(branch.getChildren()::add);
}
});
// Listen to changes in every list inside the map
doc.childrenProperty()
.values()
.forEach(list -> list.addListener(onListChange));
return branch;
}
private <DOC extends DocumentProperty> Stream<MenuItem> createDefaultContextMenu(TreeCell<DocumentProperty> treeCell, DOC node) {
final MenuItem expandAll = new MenuItem("Expand All", SpeedmentIcon.BOOK_OPEN.view());
final MenuItem collapseAll = new MenuItem("Collapse All", SpeedmentIcon.BOOK.view());
expandAll.setOnAction(ev -> {
DocumentUtil.traverseOver(node)
.filter(HasExpandedProperty.class::isInstance)
.forEach(doc -> ((HasExpandedProperty) doc).expandedProperty().setValue(true));
});
collapseAll.setOnAction(ev -> {
DocumentUtil.traverseOver(node)
.filter(HasExpandedProperty.class::isInstance)
.forEach(doc -> ((HasExpandedProperty) doc).expandedProperty().setValue(false));
});
return Stream.of(expandAll, collapseAll);
}
private final static class DocumentPropertyCell extends TreeCell<DocumentProperty> {
private final ChangeListener<Boolean> change = (ob, o, enabled) -> {
if (enabled) {
enable();
} else {
disable();
}
};
private final UserInterfaceComponent ui;
public DocumentPropertyCell(UserInterfaceComponent ui) {
this.ui = requireNonNull(ui);
// Listener should be initiated with a listener attached
// that removes enabled-listeners attached to the previous
// node when a new node is selected.
itemProperty().addListener((ob, o, n) -> {
if (o != null && o instanceof HasEnabledProperty) {
final HasEnabledProperty hasEnabled = (HasEnabledProperty) o;
hasEnabled.enabledProperty().removeListener(change);
}
if (n != null && n instanceof HasEnabledProperty) {
final HasEnabledProperty hasEnabled = (HasEnabledProperty) n;
hasEnabled.enabledProperty().addListener(change);
}
});
}
private void disable() {
getStyleClass().add("gui-disabled");
}
private void enable() {
while (getStyleClass().remove("gui-disabled")) {
}
}
@Override
protected void updateItem(DocumentProperty item, boolean empty) {
// item can be null
super.updateItem(item, requireNonNull(empty));
if (empty || item == null) {
textProperty().unbind();
setText(null);
setGraphic(null);
setContextMenu(null);
disable();
} else {
final ImageView icon;
if (item instanceof HasIconPath) {
final HasIconPath hasIcon = (HasIconPath) item;
icon = new ImageView(new Image(hasIcon.getIconPath()));
} else {
icon = SpeedmentIcon.forNode(item);
}
setGraphic(icon);
if (item instanceof HasNameProperty) {
@SuppressWarnings("unchecked")
final HasNameProperty withName = (HasNameProperty) item;
textProperty().bind(withName.nameProperty());
} else {
textProperty().unbind();
textProperty().setValue(null);
}
if (item instanceof HasMainInterface) {
ui.createContextMenu(this, item)
.ifPresent(this::setContextMenu);
}
if (HasEnabled.test(item)) {
enable();
} else {
disable();
}
}
}
}
}