Skip to content

Commit

Permalink
Refactor ApplicationPanel with MVC in mind (#1587)
Browse files Browse the repository at this point in the history
- refactor the ApplicationPanel class with MVC in mind
- change ApplicationsView to use ApplicationDetailsPanel
- change the ApplicationDetailsPanel to be a singelton
- add a base class for the dialog panels
- remove the now unused ApplicationPanel class
  • Loading branch information
madoar committed Jan 2, 2019
1 parent 16232ba commit 2149516
Show file tree
Hide file tree
Showing 11 changed files with 562 additions and 300 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package org.phoenicis.javafx.components.application.control;

import javafx.beans.property.*;
import javafx.scene.web.WebView;
import org.phoenicis.javafx.components.application.skin.ApplicationDetailsPanelSkin;
import org.phoenicis.javafx.components.common.control.DetailsPanelBase;
import org.phoenicis.javafx.views.mainwindow.apps.ApplicationFilter;
import org.phoenicis.repository.dto.ApplicationDTO;
import org.phoenicis.scripts.interpreter.ScriptInterpreter;

/**
* A details panel for the applications tab used to show the details for a selected application
*/
public class ApplicationDetailsPanel extends DetailsPanelBase<ApplicationDetailsPanel, ApplicationDetailsPanelSkin> {
/**
* The script interpreter to execute installation scripts
*/
private final ScriptInterpreter scriptInterpreter;

/**
* The filter settings to be used to filter the installation scripts
*/
private final ApplicationFilter filter;

/**
* The shown application
*/
private final ObjectProperty<ApplicationDTO> application;

/**
* Boolean flag to decide whether the script sources should be shown
*/
private final BooleanProperty showScriptSource;

/**
* The stylesheet for the {@link WebView}
*/
private final StringProperty webEngineStylesheet;

/**
* Constructor
*
* @param scriptInterpreter The script interpreter to execute installation scripts
* @param filter The filter settings to be used to filter the installation scripts
* @param application The shown application
* @param showScriptSource Boolean flag to decide whether the script sources should be shown
* @param webEngineStylesheet The stylesheet for the {@link WebView}
* @param onClose The callback for close button clicks
*/
public ApplicationDetailsPanel(ScriptInterpreter scriptInterpreter, ApplicationFilter filter,
ObjectProperty<ApplicationDTO> application, BooleanProperty showScriptSource,
StringProperty webEngineStylesheet, ObjectProperty<Runnable> onClose) {
super(onClose);

this.scriptInterpreter = scriptInterpreter;
this.filter = filter;
this.application = application;
this.showScriptSource = showScriptSource;
this.webEngineStylesheet = webEngineStylesheet;
}

/**
* Constructor
*
* @param scriptInterpreter The script interpreter to execute installation scripts
* @param filter The filter settings to be used to filter the installation scripts
* @param application The shown application
*/
public ApplicationDetailsPanel(ScriptInterpreter scriptInterpreter, ApplicationFilter filter,
ObjectProperty<ApplicationDTO> application) {
this(scriptInterpreter, filter, application, new SimpleBooleanProperty(), new SimpleStringProperty(),
new SimpleObjectProperty<>());
}

/**
* {@inheritDoc}
*/
@Override
public ApplicationDetailsPanelSkin createSkin() {
return new ApplicationDetailsPanelSkin(this);
}

public ScriptInterpreter getScriptInterpreter() {
return scriptInterpreter;
}

public ApplicationFilter getFilter() {
return filter;
}

public ApplicationDTO getApplication() {
return application.get();
}

public ObjectProperty<ApplicationDTO> applicationProperty() {
return application;
}

public void setApplication(ApplicationDTO application) {
this.application.set(application);
}

public boolean isShowScriptSource() {
return showScriptSource.get();
}

public BooleanProperty showScriptSourceProperty() {
return showScriptSource;
}

public void setShowScriptSource(boolean showScriptSource) {
this.showScriptSource.set(showScriptSource);
}

public String getWebEngineStylesheet() {
return webEngineStylesheet.get();
}

public StringProperty webEngineStylesheetProperty() {
return webEngineStylesheet;
}

public void setWebEngineStylesheet(String webEngineStylesheet) {
this.webEngineStylesheet.set(webEngineStylesheet);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package org.phoenicis.javafx.components.application.skin;

import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.*;
import javafx.scene.web.WebView;
import org.phoenicis.javafx.collections.MappedList;
import org.phoenicis.javafx.components.application.control.ApplicationDetailsPanel;
import org.phoenicis.javafx.components.common.skin.DetailsPanelBaseSkin;
import org.phoenicis.javafx.dialogs.ErrorDialog;
import org.phoenicis.javafx.views.mainwindow.apps.ApplicationFilter;
import org.phoenicis.repository.dto.ApplicationDTO;
import org.phoenicis.repository.dto.ScriptDTO;

import java.net.URI;

import static org.phoenicis.configuration.localisation.Localisation.tr;

/**
* The skin for the {@link ApplicationDetailsPanel} component
*/
public class ApplicationDetailsPanelSkin
extends DetailsPanelBaseSkin<ApplicationDetailsPanel, ApplicationDetailsPanelSkin> {
/**
* The preferred height for the application miniature images
*/
private final DoubleProperty miniatureHeight;

/**
* A list of all scripts for the shown application
*/
private final ObservableList<ScriptDTO> scripts;

/**
* A filtered version of <code>scripts</code>
*/
private final ObservableList<ScriptDTO> filteredScripts;

/**
* A list containing the {@link URI}s for the miniatures for the shown application
*/
private final ObservableList<URI> miniatureUris;

/**
* A mapped list between the <code>miniatureUris</code> and a {@link Region} component
*/
private final ObservableList<Region> miniatures;

/**
* Constructor
*
* @param control The control belonging to the skin
*/
public ApplicationDetailsPanelSkin(ApplicationDetailsPanel control) {
super(control);

this.miniatureHeight = new SimpleDoubleProperty();

this.scripts = FXCollections.observableArrayList();
this.miniatureUris = FXCollections.observableArrayList();

this.filteredScripts = createFilteredScripts();
this.miniatures = createMiniatures();
}

/**
* Creates a filtered version of <code>scripts</code>
*
* @return A filtered version of the scripts list
*/
private FilteredList<ScriptDTO> createFilteredScripts() {
final ApplicationFilter filter = getControl().getFilter();

final FilteredList<ScriptDTO> filteredScripts = scripts.filtered(filter::filter);

filteredScripts.predicateProperty().bind(
Bindings.createObjectBinding(() -> filter::filter,
filter.containAllOSCompatibleApplicationsProperty(),
filter.containCommercialApplicationsProperty(),
filter.containRequiresPatchApplicationsProperty(),
filter.containTestingApplicationsProperty()));

return filteredScripts;
}

/**
* Creates a mapped list between the <code>miniatureUris</code> and a {@link Region} component
*
* @return A mapped list between miniatureUris and a Region component
*/
private ObservableList<Region> createMiniatures() {
return new MappedList<>(miniatureUris, miniatureUri -> {
final Region image = new Region();
image.getStyleClass().add("appMiniature");
image.setStyle(String.format("-fx-background-image: url(\"%s\");", miniatureUri.toString()));

image.prefHeightProperty().bind(miniatureHeight);
image.prefWidthProperty().bind(miniatureHeight.multiply(1.5));

return image;
});
}

/**
* {@inheritDoc}
*/
@Override
public void initialise() {
super.initialise();

// ensure that the content of the details panel changes when the to be shown application changes
getControl().applicationProperty().addListener((Observable invalidation) -> updateApplication());
// initialise the content of the details panel correct
updateApplication();
}

/**
* {@inheritDoc}
*/
@Override
protected Node createContent() {
final WebView appDescription = new WebView();
appDescription.getEngine().userStyleSheetLocationProperty().bind(getControl().webEngineStylesheetProperty());
VBox.setVgrow(appDescription, Priority.ALWAYS);

getControl().applicationProperty().addListener((Observable invalidation) -> updateDescription(appDescription));
updateDescription(appDescription);

final Label installers = new Label(tr("Installers"));
installers.getStyleClass().add("descriptionTitle");

final GridPane scriptGrid = new GridPane();

filteredScripts.addListener((InvalidationListener) change -> updateScripts(scriptGrid));
getControl().showScriptSourceProperty().addListener((Observable invalidation) -> updateScripts(scriptGrid));
updateScripts(scriptGrid);

final HBox miniaturesPane = new HBox();
miniaturesPane.getStyleClass().add("appPanelMiniaturesPane");

Bindings.bindContent(miniaturesPane.getChildren(), miniatures);

final ScrollPane miniaturesPaneWrapper = new ScrollPane(miniaturesPane);
miniaturesPaneWrapper.getStyleClass().add("appPanelMiniaturesPaneWrapper");

miniatureHeight.bind(miniaturesPaneWrapper.heightProperty().multiply(0.8));

return new VBox(appDescription, installers, scriptGrid, miniaturesPaneWrapper);
}

/**
* Update the content of the details panel when the to be shown application changes
*/
private void updateApplication() {
final ApplicationDTO application = getControl().getApplication();

if (application != null) {
title.setValue(application.getName());
scripts.setAll(application.getScripts());
miniatureUris.setAll(application.getMiniatures());
}
}

/**
* Refreshes the shown scripts.
* When this method is called it begins by clearing the <code>scriptGrid</code>.
* Afterwards this method refills it.
*/
private void updateScripts(final GridPane scriptGrid) {
scriptGrid.getChildren().clear();

for (int i = 0; i < filteredScripts.size(); i++) {
ScriptDTO script = filteredScripts.get(i);

final Label scriptName = new Label(script.getScriptName());
GridPane.setHgrow(scriptName, Priority.ALWAYS);

if (getControl().isShowScriptSource()) {
final Tooltip tooltip = new Tooltip(tr("Source: {0}", script.getScriptSource()));
Tooltip.install(scriptName, tooltip);
}

final Button installButton = new Button(tr("Install"));
installButton.setOnMouseClicked(evt -> {
try {
installScript(script);
} catch (IllegalArgumentException e) {
final ErrorDialog errorDialog = ErrorDialog.builder()
.withMessage(tr("Error while trying to download the installer"))
.withException(e)
.build();

errorDialog.showAndWait();
}
});

scriptGrid.addRow(i, scriptName, installButton);
}
}

/**
* Updates the application description when the application changes
*
* @param appDescription The web view containing the application description
*/
private void updateDescription(final WebView appDescription) {
final ApplicationDTO application = getControl().getApplication();

if (application != null) {
appDescription.getEngine().loadContent("<body>" + application.getDescription() + "</body>");
}
}

/**
* Install the given script
*
* @param script The script to be installed
*/
private void installScript(ScriptDTO script) {
final StringBuilder executeBuilder = new StringBuilder();
executeBuilder.append(String.format("TYPE_ID=\"%s\";\n", script.getTypeId()));
executeBuilder.append(String.format("CATEGORY_ID=\"%s\";\n", script.getCategoryId()));
executeBuilder.append(String.format("APPLICATION_ID=\"%s\";\n", script.getApplicationId()));
executeBuilder.append(String.format("SCRIPT_ID=\"%s\";\n", script.getId()));

executeBuilder.append(script.getScript());
executeBuilder.append("\n");

// TODO: use Java interface instead of String
executeBuilder.append("new Installer().run();");

// execute the script
getControl().getScriptInterpreter().runScript(executeBuilder.toString(), e -> Platform.runLater(() -> {
// no exception if installation is cancelled
if (!(e.getCause() instanceof InterruptedException)) {
final ErrorDialog errorDialog = ErrorDialog.builder()
.withMessage(tr("The script ended unexpectedly"))
.withException(e)
.build();

errorDialog.showAndWait();
}
}));
}
}
Loading

0 comments on commit 2149516

Please sign in to comment.