simplify task selection code

This commit is contained in:
Sebastian Stenzel
2021-06-29 14:04:32 +02:00
parent a96115ecd6
commit c7b9735f13
5 changed files with 50 additions and 95 deletions

View File

@@ -20,68 +20,29 @@ import javafx.util.Callback;
class CheckListCell extends ListCell<HealthCheckTask> {
private final FontAwesome5IconView stateIcon = new FontAwesome5IconView();
private final Callback<HealthCheckTask, BooleanProperty> selectedGetter;
private final ObjectProperty<State> stateProperty;
private CheckBox checkBox = new CheckBox();
private BooleanProperty selectedProperty;
CheckListCell(Callback<HealthCheckTask, BooleanProperty> selectedGetter, ObservableValue<Boolean> switchIndicator) {
this.selectedGetter = selectedGetter;
this.stateProperty = new SimpleObjectProperty<>(State.SELECTION);
switchIndicator.addListener(this::changeState);
CheckListCell() {
setPadding(new Insets(6));
setAlignment(Pos.CENTER_LEFT);
setContentDisplay(ContentDisplay.LEFT);
getStyleClass().add("label");
}
private void changeState(ObservableValue<? extends Boolean> observableValue, boolean oldValue, boolean newValue) {
if (newValue) {
stateProperty.set(State.RUN);
} else {
stateProperty.set(State.SELECTION);
}
}
@Override
protected void updateItem(HealthCheckTask item, boolean empty) {
super.updateItem(item, empty);
if (item != null) {
setText(item.getTitle());
}
switch (stateProperty.get()) {
case SELECTION -> updateItemSelection(item, empty);
case RUN -> updateItemRun(item, empty);
}
}
private void updateItemSelection(HealthCheckTask item, boolean empty) {
if (!empty) {
setGraphic(checkBox);
if (selectedProperty != null) {
checkBox.selectedProperty().unbindBidirectional(selectedProperty);
}
selectedProperty = selectedGetter.call(item);
if (selectedProperty != null) {
checkBox.selectedProperty().bindBidirectional(selectedProperty);
}
} else {
setGraphic(null);
setText(null);
}
}
private void updateItemRun(HealthCheckTask item, boolean empty) {
if (item != null) {
item.stateProperty().addListener(this::stateChanged);
graphicProperty().bind(Bindings.createObjectBinding(() -> graphicForState(item.getState()), item.stateProperty()));
stateIcon.setGlyph(glyphForState(item.getState()));
checkBox.selectedProperty().bindBidirectional(item.chosenForExecutionProperty());
} else {
graphicProperty().unbind();
setGraphic(null);
setText(null);
checkBox.selectedProperty().unbind();
}
}
@@ -92,7 +53,7 @@ class CheckListCell extends ListCell<HealthCheckTask> {
private Node graphicForState(Worker.State state) {
return switch (state) {
case READY -> null;
case READY -> checkBox;
case SCHEDULED, RUNNING, FAILED, CANCELLED, SUCCEEDED -> stateIcon;
};
}
@@ -108,8 +69,4 @@ class CheckListCell extends ListCell<HealthCheckTask> {
};
}
private enum State {
SELECTION,
RUN;
}
}

View File

@@ -1,6 +1,7 @@
package org.cryptomator.ui.health;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.tobiasdiez.easybind.EasyBind;
import dagger.Lazy;
import org.cryptomator.ui.common.ErrorComponent;
@@ -10,28 +11,24 @@ import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import javafx.beans.binding.Binding;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.concurrent.Worker;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.stage.Stage;
import javafx.util.StringConverter;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
@@ -43,6 +40,7 @@ public class CheckListController implements FxController {
private final Stage window;
private final ObservableList<HealthCheckTask> tasks;
private final FilteredList<HealthCheckTask> chosenTasks;
private final ReportWriter reportWriter;
private final ExecutorService executorService;
private final ObjectProperty<HealthCheckTask> selectedTask;
@@ -50,19 +48,18 @@ public class CheckListController implements FxController {
private final SimpleObjectProperty<Worker<?>> runningTask;
private final Binding<Boolean> running;
private final Binding<Boolean> finished;
private final Map<HealthCheckTask, BooleanProperty> listPickIndicators;
private final IntegerProperty numberOfPickedChecks;
private final IntegerBinding chosenTaskCount;
private final BooleanBinding anyCheckSelected;
private final BooleanProperty showResultScreen;
/* FXML */
public ListView<HealthCheckTask> checksListView;
@Inject
public CheckListController(@HealthCheckWindow Stage window, Lazy<Collection<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
public CheckListController(@HealthCheckWindow Stage window, Lazy<List<HealthCheckTask>> tasks, ReportWriter reportWriteTask, ObjectProperty<HealthCheckTask> selectedTask, ExecutorService executorService, Lazy<ErrorComponent.Builder> errorComponentBuilder) {
this.window = window;
this.tasks = FXCollections.observableArrayList(tasks.get());
this.tasks = FXCollections.observableList(tasks.get(), HealthCheckTask::observables);
this.chosenTasks = this.tasks.filtered(HealthCheckTask::isChosenForExecution);
this.reportWriter = reportWriteTask;
this.executorService = executorService;
this.selectedTask = selectedTask;
@@ -70,13 +67,7 @@ public class CheckListController implements FxController {
this.runningTask = new SimpleObjectProperty<>();
this.running = EasyBind.wrapNullable(runningTask).mapObservable(Worker::runningProperty).orElse(false);
this.finished = EasyBind.wrapNullable(runningTask).mapObservable(Worker::stateProperty).map(END_STATES::contains).orElse(false);
this.listPickIndicators = new HashMap<>();
this.numberOfPickedChecks = new SimpleIntegerProperty(0);
this.tasks.forEach(task -> {
var entrySelectedProp = new SimpleBooleanProperty(false);
entrySelectedProp.addListener((observable, oldValue, newValue) -> numberOfPickedChecks.set(numberOfPickedChecks.get() + (newValue ? 1 : -1)));
listPickIndicators.put(task, entrySelectedProp);
});
this.chosenTaskCount = Bindings.size(this.chosenTasks);
this.anyCheckSelected = selectedTask.isNotNull();
this.showResultScreen = new SimpleBooleanProperty(false);
}
@@ -84,27 +75,31 @@ public class CheckListController implements FxController {
@FXML
public void initialize() {
checksListView.setItems(tasks);
checksListView.setCellFactory(view -> new CheckListCell(listPickIndicators::get, showResultScreen));
checksListView.setCellFactory(view -> new CheckListCell());
selectedTask.bind(checksListView.getSelectionModel().selectedItemProperty());
}
@FXML
public void toggleSelectAll(ActionEvent event) {
if (event.getSource() instanceof CheckBox c) {
listPickIndicators.forEach( (task, pickProperty) -> pickProperty.set(c.isSelected()));
tasks.forEach(t -> t.chosenForExecutionProperty().set(c.isSelected()));
}
}
@FXML
public void runSelectedChecks() {
Preconditions.checkState(runningTask.get() == null);
var batch = checksListView.getItems().filtered(item -> listPickIndicators.get(item).get());
var batchService = new BatchService(batch);
// prevent further interaction by cancelling non-chosen tasks:
tasks.filtered(Predicates.not(chosenTasks::contains)).forEach(HealthCheckTask::cancel);
// run chosen tasks:
var batchService = new BatchService(chosenTasks);
batchService.setExecutor(executorService);
batchService.start();
runningTask.set(batchService);
showResultScreen.set(true);
checksListView.getSelectionModel().select(batch.get(0));
checksListView.getSelectionModel().select(chosenTasks.get(0));
checksListView.refresh();
window.sizeToScene();
}
@@ -158,13 +153,12 @@ public class CheckListController implements FxController {
return showResultScreen;
}
public int getNumberOfPickedChecks() {
return numberOfPickedChecks.get();
public int getChosenTaskCount() {
return chosenTaskCount.getValue();
}
public IntegerProperty numberOfPickedChecksProperty() {
return numberOfPickedChecks;
public IntegerBinding chosenTaskCountProperty() {
return chosenTaskCount;
}
}

View File

@@ -19,8 +19,6 @@ import org.cryptomator.ui.keyloading.KeyLoadingComponent;
import org.cryptomator.ui.keyloading.KeyLoadingStrategy;
import org.cryptomator.ui.mainwindow.MainWindow;
import javax.annotation.Nullable;
import javax.inject.Named;
import javax.inject.Provider;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
@@ -30,6 +28,7 @@ import javafx.stage.Modality;
import javafx.stage.Stage;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
@@ -65,7 +64,7 @@ abstract class HealthCheckModule {
/* Only inject with Lazy-Wrapper!*/
@Provides
@HealthCheckScoped
static Collection<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) {
static List<HealthCheckTask> provideAvailableHealthCheckTasks(Collection<HealthCheck> availableHealthChecks, @HealthCheckWindow Vault vault, AtomicReference<Masterkey> masterkeyRef, AtomicReference<VaultConfig> vaultConfigRef, SecureRandom csprng, ResourceBundle resourceBundle) {
return availableHealthChecks.stream().map(check -> new HealthCheckTask(vault.getPath(), vaultConfigRef.get(), masterkeyRef.get(), csprng, check, resourceBundle)).toList();
}

View File

@@ -9,7 +9,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
@@ -34,6 +37,7 @@ class HealthCheckTask extends Task<Void> {
private final HealthCheck check;
private final ObservableList<DiagnosticResult> results;
private final LongProperty durationInMillis;
private final BooleanProperty chosenForExecution;
public HealthCheckTask(Path vaultPath, VaultConfig vaultConfig, Masterkey masterkey, SecureRandom csprng, HealthCheck check, ResourceBundle resourceBundle) {
this.vaultPath = Objects.requireNonNull(vaultPath);
@@ -49,6 +53,7 @@ class HealthCheckTask extends Task<Void> {
updateTitle(check.identifier());
}
this.durationInMillis = new SimpleLongProperty(-1);
this.chosenForExecution = new SimpleBooleanProperty();
}
@Override
@@ -60,21 +65,10 @@ class HealthCheckTask extends Task<Void> {
if (isCancelled()) {
throw new CancellationException();
}
// FIXME: slowdown for demonstration purposes only:
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
if (isCancelled()) {
return;
} else {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
Platform.runLater(() -> results.add(result));
});
}
Platform.runLater(() ->durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
Platform.runLater(() -> durationInMillis.set(Duration.between(start, Instant.now()).toMillis()));
return null;
}
@@ -90,6 +84,10 @@ class HealthCheckTask extends Task<Void> {
/* Getter */
Observable[] observables() {
return new Observable[]{results, chosenForExecution};
}
public ObservableList<DiagnosticResult> results() {
return results;
}
@@ -106,4 +104,11 @@ class HealthCheckTask extends Task<Void> {
return durationInMillis.get();
}
public BooleanProperty chosenForExecutionProperty() {
return chosenForExecution;
}
public boolean isChosenForExecution() {
return chosenForExecution.get();
}
}