/*
* Copyright 2013-2014 Odysseus Software GmbH
*
* 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 org.musicmount.fx;
import java.io.File;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.ColumnConstraintsBuilder;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import javafx.stage.DirectoryChooser;
import org.musicmount.builder.MusicMountBuilder;
public class FXBuildController {
private static final String STATUS_NO_RELATIVE_MUSIC_PATH = "Cannot calculate relative music path, custom path path required";
private Pane pane;
private TextField musicFolderTextField;
private Button musicFolderChooseButton;
private TextField mountFolderTextField;
private Button mountFolderChooseButton;
private TextField musicPathTextField;
private ChoiceBox<String> musicPathChoiceBox;
private CheckBox retinaCheckBox;
private CheckBox groupingCheckBox;
private CheckBox fullCheckBox;
private CheckBox noTrackIndexCheckBox;
private CheckBox noVariousArtistsCheckBox;
private CheckBox unknownGenreCheckBox;
private ProgressIndicator progressIndicator;
private Button runButton;
private Text statusText;
private final FXCommandModel model;
private final MusicMountBuilder builder;
private final Service<Object> service = new Service<Object>() {
@Override
protected Task<Object> createTask() {
return new Task<Object>() {
@Override
protected Object call() throws Exception {
builder.build(model.getMusicFolder(), model.getMountFolder(), model.getMusicPath());
return null;
}
};
}
};
public FXBuildController(final FXCommandModel model) {
this.model = model;
this.builder = new MusicMountBuilder(model.getBuildConfig());
this.pane = createView();
builder.setProgressHandler(new FXProgressHandler(statusText, progressIndicator, builder.getProgressHandler()));
musicFolderTextField.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
model.setMusicFolder(model.toFolder(newValue));
if (model.getCustomMusicPath() == null) {
updateMusicPath();
}
updateRunButton();
}
});
musicFolderChooseButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Music Folder");
File directory = directoryChooser.showDialog(musicFolderChooseButton.getScene().getWindow());
if (directory != null) {
musicFolderTextField.setText(directory.getAbsolutePath());
}
}
});
mountFolderTextField.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
model.setMountFolder(model.toFolder(newValue));
if (model.getCustomMusicPath() == null) {
updateMusicPath();
}
updateRunButton();
}
});
mountFolderChooseButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
DirectoryChooser directoryChooser = new DirectoryChooser();
directoryChooser.setTitle("Site Folder");
File directory = directoryChooser.showDialog(mountFolderChooseButton.getScene().getWindow());
if (directory != null) {
mountFolderTextField.setText(directory.getAbsolutePath());
}
}
});
musicPathTextField.textProperty().addListener(new ChangeListener<String>() {
@Override
public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
if (model.getCustomMusicPath() != null) {
model.setCustomMusicPath(newValue);
}
updateRunButton();
}
});
musicPathChoiceBox.getSelectionModel().selectedIndexProperty().addListener(new ChangeListener<Number>() {
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
if(newValue.intValue() == 0) { // auto
model.setCustomMusicPath(null);
} else if (model.getCustomMusicPath() == null) { // custom
model.setCustomMusicPath(FXCommandModel.DEFAULT_CUSTOM_MUSIC_PATH);
}
updateMusicPath();
updateRunButton();
}
});
retinaCheckBox.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
builder.getConfig().setRetina(retinaCheckBox.isSelected());
}
});
groupingCheckBox.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
builder.getConfig().setGrouping(groupingCheckBox.isSelected());
}
});
fullCheckBox.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
builder.getConfig().setFull(fullCheckBox.isSelected());
}
});
noTrackIndexCheckBox.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
builder.getConfig().setNoTrackIndex(noTrackIndexCheckBox.isSelected());
}
});
noVariousArtistsCheckBox.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
builder.getConfig().setNoVariousArtists(noVariousArtistsCheckBox.isSelected());
}
});
unknownGenreCheckBox.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
builder.getConfig().setUnknownGenre(unknownGenreCheckBox.isSelected());
}
});
service.setOnRunning(new EventHandler<WorkerStateEvent>() {
public void handle(WorkerStateEvent event) {
statusText.setText(null);
disableControls(true);
}
});
service.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
public void handle(WorkerStateEvent event) {
statusText.setText("Site generation succeeded");
disableControls(false);
}
});
service.setOnFailed(new EventHandler<WorkerStateEvent>() {
public void handle(WorkerStateEvent event) {
builder.getProgressHandler().endTask();
statusText.setText("Site generation failed");
if (service.getException() != null) {
service.getException().printStackTrace();
}
disableControls(false);
}
});
runButton.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
service.reset();
service.start();
}
});
updateAll();
}
void disableControls(boolean disable) {
musicFolderTextField.setDisable(disable);
musicFolderChooseButton.setDisable(disable);
mountFolderTextField.setDisable(disable);
mountFolderChooseButton.setDisable(disable);
musicFolderTextField.setDisable(disable);
musicPathChoiceBox.setDisable(disable);
musicPathTextField.setDisable(disable);
retinaCheckBox.setDisable(disable);
groupingCheckBox.setDisable(disable);
fullCheckBox.setDisable(disable);
noTrackIndexCheckBox.setDisable(disable);
noVariousArtistsCheckBox.setDisable(disable);
unknownGenreCheckBox.setDisable(disable);
runButton.setDisable(disable || !model.isValidBuildModel());
}
Pane createView() {
GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(5);
grid.setVgap(10);
// grid.setGridLinesVisible(true);
grid.getColumnConstraints().add(0, ColumnConstraintsBuilder.create().hgrow(Priority.NEVER).build());
grid.getColumnConstraints().add(1, ColumnConstraintsBuilder.create().hgrow(Priority.ALWAYS).percentWidth(33).build());
grid.getColumnConstraints().add(2, ColumnConstraintsBuilder.create().hgrow(Priority.ALWAYS).build());
grid.getColumnConstraints().add(3, ColumnConstraintsBuilder.create().hgrow(Priority.ALWAYS).percentWidth(33).build());
grid.getColumnConstraints().add(4, ColumnConstraintsBuilder.create().hgrow(Priority.NEVER).build());
/*
* music folder
*/
Label musicFolderLabel = new Label("Music Folder");
musicFolderTextField = new TextField();
musicFolderTextField.setPromptText("Input directory, containing your music collection");
musicFolderChooseButton = new Button("...");
grid.add(musicFolderLabel, 0, 1);
GridPane.setHalignment(musicFolderLabel, HPos.RIGHT);
grid.add(musicFolderTextField, 1, 1, 3, 1);
grid.add(musicFolderChooseButton, 4, 1);
/*
* mount folder
*/
Label mountFolderLabel = new Label("Site Folder");
mountFolderTextField = new TextField();
mountFolderTextField.setPromptText("Output directory, containing your generated site");
mountFolderChooseButton = new Button("...");
grid.add(mountFolderLabel, 0, 2);
GridPane.setHalignment(mountFolderLabel, HPos.RIGHT);
grid.add(mountFolderTextField, 1, 2, 3, 1);
grid.add(mountFolderChooseButton, 4, 2);
/*
* music path
*/
Label musicPathLabel = new Label("Music Path");
musicPathTextField = new TextField();
musicPathTextField.setPromptText("Web path to music, absolute or relative to site URL");
musicPathChoiceBox = new ChoiceBox<String>(FXCollections.observableArrayList("Auto", "Custom"));
grid.add(musicPathLabel, 0, 3);
GridPane.setHalignment(musicPathLabel, HPos.RIGHT);
HBox.setHgrow(musicPathTextField, Priority.ALWAYS);
HBox musicPathHBox = new HBox(10);
musicPathHBox.getChildren().addAll(musicPathChoiceBox, musicPathTextField);
grid.add(musicPathHBox, 1, 3, 3, 1);
/*
* options
*/
Label optionsLabel = new Label("Options");
grid.add(optionsLabel, 0, 4);
GridPane.setHalignment(optionsLabel, HPos.RIGHT);
retinaCheckBox = new CheckBox("Retina Images");
retinaCheckBox.setTooltip(new Tooltip("Double image resolution, better for tables"));
grid.add(retinaCheckBox, 1, 4);
groupingCheckBox = new CheckBox("Track Grouping");
groupingCheckBox.setTooltip(new Tooltip("Use grouping tag to group album tracks"));
grid.add(groupingCheckBox, 1, 5);
fullCheckBox = new CheckBox("Full Parse/Build");
fullCheckBox.setTooltip(new Tooltip("Force full parse/build, don't use asset store"));
grid.add(fullCheckBox, 1, 6);
noTrackIndexCheckBox = new CheckBox("No Track Index");
noTrackIndexCheckBox.setTooltip(new Tooltip("Do not generate a track index"));
grid.add(noTrackIndexCheckBox, 3, 4);
noVariousArtistsCheckBox = new CheckBox("No 'Various Artists' Item");
noVariousArtistsCheckBox.setTooltip(new Tooltip("Exclude 'Various Artists' from album artist index"));
grid.add(noVariousArtistsCheckBox, 3, 5);
unknownGenreCheckBox = new CheckBox("Add 'Unknown' Genre");
unknownGenreCheckBox.setTooltip(new Tooltip("Report missing genre as 'Unknown'"));
grid.add(unknownGenreCheckBox, 3, 6);
/*
* progress
*/
progressIndicator = new ProgressIndicator();
progressIndicator.setPrefWidth(30);
progressIndicator.setVisible(false);
VBox progressBox = new VBox();
progressBox.setFillWidth(false);
progressBox.setAlignment(Pos.BOTTOM_CENTER);
progressBox.getChildren().add(progressIndicator);
grid.add(progressBox, 0, 5, 1, 3);
/*
* run button
*/
runButton = new Button("Build Site");
runButton.setId("build-button");
runButton.getStyleClass().add("run-button");
HBox runButtonHBox = new HBox(10);
runButtonHBox.setAlignment(Pos.BOTTOM_RIGHT);
runButtonHBox.getChildren().add(runButton);
grid.add(runButtonHBox, 3, 7, 2, 1);
GridPane.setVgrow(runButtonHBox, Priority.ALWAYS);
BorderPane borderPane = new BorderPane();
borderPane.setCenter(grid);
BorderPane.setMargin(grid, new Insets(10));
Text titleText = new Text("Generate MusicMount Site");
titleText.setId("build-title");
titleText.getStyleClass().add("tool-title");
borderPane.setTop(titleText);
BorderPane.setMargin(titleText, new Insets(15, 10, 0, 10));
statusText = new Text();
statusText.setId("build-status");
statusText.getStyleClass().add("status-text");
borderPane.setBottom(statusText);
BorderPane.setMargin(statusText, new Insets(5, 10, 10, 10));
return borderPane;
}
void updateAll() {
updateMusicFolder();
updateMountFolder();
updateMusicPath();
updateOptions();
updateRunButton();
}
void updateMusicFolder() {
musicFolderTextField.setText(model.getMusicFolder() != null ? model.getMusicFolder().toString() : null);
}
void updateMountFolder() {
mountFolderTextField.setText(model.getMountFolder() != null ? model.getMountFolder().toString() : null);
}
void updateMusicPath() {
musicPathChoiceBox.getSelectionModel().select(model.getCustomMusicPath() == null ? 0 : 1);
musicPathTextField.setEditable(model.getCustomMusicPath() != null);
musicPathTextField.setText(model.getMusicPath());
if (model.getCustomMusicPath() == null && model.getMusicFolder() != null && model.getMountFolder() != null
&& !model.getMusicFolder().equals(model.getMountFolder()) && model.getMusicPath() == null) { // no relative path
statusText.setText(STATUS_NO_RELATIVE_MUSIC_PATH);
} else if (STATUS_NO_RELATIVE_MUSIC_PATH.equals(statusText.getText())) {
statusText.setText(null);
}
}
void updateOptions() {
fullCheckBox.setSelected(builder.getConfig().isFull());
groupingCheckBox.setSelected(builder.getConfig().isGrouping());
noTrackIndexCheckBox.setSelected(builder.getConfig().isNoTrackIndex());
noVariousArtistsCheckBox.setSelected(builder.getConfig().isNoVariousArtists());
retinaCheckBox.setSelected(builder.getConfig().isRetina());
unknownGenreCheckBox.setSelected(builder.getConfig().isUnknownGenre());
}
void updateRunButton() {
runButton.setDisable(service.isRunning() || !model.isValidBuildModel());
}
public Pane getPane() {
return pane;
}
public Service<Object> getService() {
return service;
}
}