-
Notifications
You must be signed in to change notification settings - Fork 73
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor ApplicationPanel with MVC in mind (#1587)
- 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
Showing
11 changed files
with
562 additions
and
300 deletions.
There are no files selected for viewing
126 changes: 126 additions & 0 deletions
126
...ain/java/org/phoenicis/javafx/components/application/control/ApplicationDetailsPanel.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
257 changes: 257 additions & 0 deletions
257
...in/java/org/phoenicis/javafx/components/application/skin/ApplicationDetailsPanelSkin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
})); | ||
} | ||
} |
Oops, something went wrong.