/*
* Copyright [2014] [Christian Loehnert, krampenschiesser@gmail.com]
* 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 de.ks.idnadrev.task.view;
import de.ks.BaseController;
import de.ks.activity.ActivityHint;
import de.ks.file.FileStore;
import de.ks.i18n.Localized;
import de.ks.idnadrev.entity.Task;
import de.ks.idnadrev.entity.TaskState;
import de.ks.idnadrev.task.create.CreateTaskActivity;
import de.ks.idnadrev.task.finish.FinishTaskActivity;
import de.ks.idnadrev.task.work.WorkOnTaskActivity;
import de.ks.persistence.PersistentWork;
import de.ks.text.view.AsciiDocContent;
import de.ks.text.view.AsciiDocViewer;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.fxml.FXML;
import javafx.geometry.NodeOrientation;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.net.URL;
import java.time.Duration;
import java.util.List;
import java.util.ResourceBundle;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class ViewTasks extends BaseController<List<Task>> {
private static final Logger log = LoggerFactory.getLogger(ViewTasks.class);
public static final String NEGATIVE_FUN_FACTOR = "negativeFunFactor";
public static final String RECOVERING_EFFORT = "recoveringEffortFactor";
@FXML
protected ViewTasksMaster viewController;
@FXML
protected Label name;
@FXML
protected Label context;
@FXML
protected Label estimatedTime;
@FXML
protected Label spentTime;
@FXML
protected Label state;
@FXML
protected Hyperlink parentProject;
@FXML
protected ProgressBar physicalEffort;
@FXML
protected ProgressBar mentalEffort;
@FXML
protected ProgressBar funFactor;
@FXML
protected FlowPane tagPane;
@FXML
protected StackPane description;
@FXML
protected Button start;
@FXML
protected Button finish;
@FXML
protected Button edit;
@FXML
protected Button createSubtaskBtn;
@FXML
protected Button show;
@FXML
protected Button delete;
@FXML
protected Button later;
@FXML
protected Button asap;
@Inject
FileStore fileStore;
private AsciiDocViewer asciiDocViewer;
private final SimpleBooleanProperty disable = new SimpleBooleanProperty(false);
@Override
public void initialize(URL location, ResourceBundle resources) {
TreeTableView<Task> tasksView = viewController.getTasksView();
activityInitialization.loadAdditionalControllerWithFuture(AsciiDocViewer.class).thenAcceptAsync(l -> {
asciiDocViewer = l.getController();
tasksView.getSelectionModel().selectedItemProperty().addListener((p, o, n) -> {
if (n == null) {
asciiDocViewer.reset();
} else {
asciiDocViewer.show(new AsciiDocContent(n.getValue().getName(), n.getValue().getDescription()));
}
});
description.getChildren().add(l.getView());
}, controller.getJavaFXExecutor());
viewController.getTasksView().setOnMouseClicked(e -> {
if (e.getClickCount() > 1 && !start.isDisabled()) {
startWork();
}
});
ReadOnlyObjectProperty<TreeItem<Task>> selectedItemProperty = tasksView.getSelectionModel().selectedItemProperty();
selectedItemProperty.addListener((p, o, n) -> applyTask(n));
start.disableProperty().bind(disable);
finish.disableProperty().bind(disable);
edit.disableProperty().bind(disable);
show.disableProperty().bind(disable);
delete.disableProperty().bind(disable);
later.disableProperty().bind(disable);
asap.disableProperty().bind(disable);
createSubtaskBtn.disableProperty().bind(disable);
}
protected void applyTask(TreeItem<Task> taskTreeItem) {
disable.set(true);
clear();
if (taskTreeItem != null) {
Task task = taskTreeItem.getValue();
log.info("Applying task {}", task);
if (task.getId() >= 0) {
disable.set(false);
}
name.setText(task.getName());
context.setText(task.getContext() != null ? task.getContext().getName() : "");
estimatedTime.setText(parseDuration(task.isProject() ? task.getTotalEstimatedTime() : task.getEstimatedTime(), true));
spentTime.setText(parseDuration(task.getTotalWorkDuration(), true));
parentProject.setText(task.getParent() != null ? task.getParent().getName() : null);
state.setText(task.getState().name());
setEffortProgress(task.getPhysicalEffort().getAmount(), physicalEffort, RECOVERING_EFFORT);
setEffortProgress(task.getMentalEffort().getAmount(), mentalEffort, RECOVERING_EFFORT);
setEffortProgress(task.getFunFactor().getAmount(), funFactor, NEGATIVE_FUN_FACTOR);
task.getTags().forEach((tag) -> tagPane.getChildren().add(new Label(tag.getName())));
asciiDocViewer.show(new AsciiDocContent(task.getName(), task.getDescription()));
}
}
private void setEffortProgress(int amount, ProgressBar temp, String styleClass) {
temp.setProgress(Math.abs(amount / 5D));
if (amount < 0) {
temp.setNodeOrientation(NodeOrientation.RIGHT_TO_LEFT);
temp.getStyleClass().add(styleClass);
} else {
temp.setNodeOrientation(NodeOrientation.LEFT_TO_RIGHT);
temp.getStyleClass().remove(styleClass);
}
}
private String parseDuration(Duration duration, boolean useShortFormat) {
if (duration == null) {
return null;
} else {
long hours = duration.toHours();
if (hours == 0 && useShortFormat) {
return duration.toMinutes() + Localized.get("duration.minutes");
} else {
long remainingMinutes = duration.minus(Duration.ofHours(hours)).toMinutes();
return String.format("%02d", hours) + ":" + String.format("%02d", remainingMinutes) + Localized.get("duration.hours.short");
}
}
}
private void clear() {
name.setText(null);
spentTime.setText(null);
asciiDocViewer.reset();
parentProject.setText(null);
context.setText(null);
physicalEffort.setProgress(0);
mentalEffort.setProgress(0);
funFactor.setProgress(0);
tagPane.getChildren().clear();
}
@FXML
void onTableKeyReleased(KeyEvent event) {
if (event.getCode().equals(KeyCode.ENTER)) {
startWork();
}
}
@FXML
void selectParentProject() {
TreeTableView<Task> tasksView = viewController.getTasksView();
Task parent = tasksView.getSelectionModel().getSelectedItem().getValue().getParent();
TreeItem<Task> nextSelection = viewController.getTreeItem(parent);
tasksView.getSelectionModel().select(nextSelection);
}
@FXML
void showTimeUnits() {
Stage stage = new Stage();
stage.initModality(Modality.APPLICATION_MODAL);
stage.initOwner(this.show.getScene().getWindow());
stage.setTitle(Localized.get("workunits"));
WorkUnitController workUnitController = controller.getControllerInstance(WorkUnitController.class);
Task value = viewController.getTasksView().getSelectionModel().getSelectedItem().getValue();
workUnitController.setTask(value);
GridPane root = workUnitController.getRoot();
stage.setScene(new Scene(root));
stage.show();
stage.setOnHidden(e -> {
stage.getScene().setRoot(new StackPane());
});
}
@FXML
void editTask() {
TreeTableView<Task> tasksView = viewController.getTasksView();
ActivityHint hint = new ActivityHint(CreateTaskActivity.class);
hint.setReturnToActivity(controller.getCurrentActivityId());
Supplier supplier = () -> tasksView.getSelectionModel().getSelectedItem().getValue();
hint.setReturnToDatasourceHint(supplier);
hint.setDataSourceHint(supplier);
controller.startOrResume(hint);
}
@FXML
void createSubtask() {
TreeTableView<Task> tasksView = viewController.getTasksView();
ActivityHint hint = new ActivityHint(CreateTaskActivity.class);
hint.setReturnToActivity(controller.getCurrentActivityId());
Supplier supplier = () -> {
Task parent = tasksView.getSelectionModel().getSelectedItem().getValue();
Task child = new Task("");
child.setParent(parent);
child.setContext(parent.getContext());
return child;
};
hint.setReturnToDatasourceHint(() -> tasksView.getSelectionModel().getSelectedItem().getValue());
hint.setDataSourceHint(supplier);
controller.startOrResume(hint);
}
@FXML
void startWork() {
TreeTableView<Task> tasksView = viewController.getTasksView();
Supplier currentSelection = () -> tasksView.getSelectionModel().getSelectedItem().getValue();
ActivityHint activityHint = new ActivityHint(WorkOnTaskActivity.class, controller.getCurrentActivityId());
activityHint.setDataSourceHint(currentSelection);
activityHint.setReturnToDatasourceHint(currentSelection);
controller.startOrResume(activityHint);
}
@FXML
void finishTask() {
TreeTableView<Task> tasksView = viewController.getTasksView();
ActivityHint activityHint = new ActivityHint(FinishTaskActivity.class, controller.getCurrentActivityId());
activityHint.setDataSourceHint(() -> tasksView.getSelectionModel().getSelectedItem().getValue());
controller.startOrResume(activityHint);
}
@FXML
void deleteTask() {
TreeTableView<Task> tasksView = viewController.getTasksView();
PersistentWork.run(em -> {
Task task = tasksView.getSelectionModel().getSelectedItem().getValue();
em.remove(PersistentWork.reload(task));
});
controller.reload();
}
@FXML
public void scheduleAsap() {
TreeTableView<Task> tasksView = viewController.getTasksView();
PersistentWork.run(em -> {
Task task = tasksView.getSelectionModel().getSelectedItem().getValue();
PersistentWork.reload(task).setState(TaskState.ASAP);
});
controller.reload();
}
@FXML
public void scheduleLater() {
TreeTableView<Task> tasksView = viewController.getTasksView();
PersistentWork.run(em -> {
Task task = tasksView.getSelectionModel().getSelectedItem().getValue();
PersistentWork.reload(task).setState(TaskState.LATER);
});
controller.reload();
}
@Override
protected void onRefresh(List<Task> loaded) {
List<AsciiDocContent> asciiDocContents = loaded.stream().map(t -> new AsciiDocContent(t.getName(), t.getDescription())).collect(Collectors.toList());
this.asciiDocViewer.preload(asciiDocContents);
}
}