/* * 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.work; import de.ks.BaseController; import de.ks.activity.ActivityHint; import de.ks.activity.executor.ActivityExecutor; import de.ks.i18n.Localized; import de.ks.idnadrev.IdnadrevWindow; import de.ks.idnadrev.entity.Task; import de.ks.idnadrev.entity.WorkUnit; import de.ks.idnadrev.task.finish.FinishTaskActivity; import de.ks.idnadrev.thought.add.AddThoughtActivity; import de.ks.persistence.PersistentWork; import de.ks.text.AsciiDocEditor; import javafx.beans.property.SimpleStringProperty; import javafx.beans.property.StringProperty; import javafx.fxml.FXML; import javafx.scene.control.Label; import javafx.scene.control.ProgressBar; import javafx.scene.control.Tooltip; import javafx.scene.layout.StackPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import java.net.URL; import java.time.Duration; import java.util.ResourceBundle; import java.util.concurrent.TimeUnit; public class WorkOnTask extends BaseController<Task> { private static final Logger log = LoggerFactory.getLogger(WorkOnTask.class); public static final String OVERTIME_STYLE_CLASS = "negativeFunFactor"; @FXML protected Label estimatedTime; @FXML protected ProgressBar estimatedTimeBar; @FXML protected Label name; @FXML protected Label overTime; @FXML protected StackPane descriptionView; protected AsciiDocEditor description; @Inject IdnadrevWindow window; protected final SimpleStringProperty tookString = new SimpleStringProperty(""); @Override public void initialize(URL location, ResourceBundle resources) { AsciiDocEditor.load(descriptionView.getChildren()::add, ade -> this.description = ade); StringProperty nameBinding = store.getBinding().getStringProperty(Task.class, t -> t.getName()); name.textProperty().bind(nameBinding); description.hideActionBar(); StringProperty descriptionBinding = store.getBinding().getStringProperty(Task.class, t -> t.getDescription()); descriptionBinding.bind(description.textProperty()); overTime.textProperty().isNotEmpty().addListener((p, o, n) -> { if (n) { log.info("Switching to negative style class"); estimatedTimeBar.getStyleClass().add(OVERTIME_STYLE_CLASS); } else { log.info("Switching to positive style class"); estimatedTimeBar.getStyleClass().remove(OVERTIME_STYLE_CLASS); } }); store.getBinding().registerClearOnRefresh(overTime); controller.getJavaFXExecutor().submit(() -> { Tooltip tooltip = new Tooltip(); tooltip.textProperty().bind(tookString); estimatedTimeBar.setTooltip(tooltip); }); } @Override public void onSuspend() { controller.save(); } @FXML void stopWork() { store.executeCustomRunnable(this::finishWorkUnit); controller.save(); controller.stopCurrent(); } protected void finishWorkUnit() { PersistentWork.wrap(() -> { Task reload = PersistentWork.reload(store.getModel()); WorkUnit last = reload.getWorkUnits().last(); last.stop(); }); controller.getJavaFXExecutor().submit(() -> window.getWorkingOnTaskLink().setCurrentTask(null)); } @FXML void createThought() { ActivityHint hint = new ActivityHint(AddThoughtActivity.class, controller.getCurrentActivityId()); controller.startOrResume(hint); } @FXML void finishTask() { store.executeCustomRunnable(this::finishWorkUnit); controller.save(); ActivityHint currentHint = controller.getCurrentActivity().getActivityHint(); String returnToActivity = controller.getCurrentActivityId(); if (currentHint != null) { returnToActivity = currentHint.getReturnToActivity(); } ActivityHint nextHint = new ActivityHint(FinishTaskActivity.class, returnToActivity); nextHint.setModelHint(PersistentWork::reload); controller.startOrResume(nextHint); } @Override protected void onRefresh(Task task) { String descriptionContent = task.getDescription(); description.setText(descriptionContent); ActivityExecutor executorService = controller.getExecutorService(); PersistentWork.runAsync(em -> { Task reloaded = PersistentWork.reload(task); WorkUnit last = reloaded.getWorkUnits().isEmpty() ? null : reloaded.getWorkUnits().last(); if (last == null || last.isFinished()) { WorkUnit workUnit = new WorkUnit(reloaded); em.persist(workUnit); } }, executorService); Duration time = task.getEstimatedTime(); if (time == null || time.toMillis() == 0) { estimatedTimeBar.setProgress(-0.1D); } else { increaseProgress(); long refreshRate = time.toMillis() / 100; refreshRate = Math.min(60 * 1000, refreshRate); log.debug("Triggering progress updates at a rate of {}ms", refreshRate); executorService.scheduleAtFixedRate(this::increaseProgress, 100, refreshRate, TimeUnit.MILLISECONDS); estimatedTime.setText(getHourMinutesString(time)); } if (!description.getText().isEmpty()) { description.selectPreview(); } Task lastWorkedOnTask = window.getWorkingOnTaskLink().getCurrentTask(); if (lastWorkedOnTask != null && !lastWorkedOnTask.equals(task)) { PersistentWork.runAsync(em -> { Task old = PersistentWork.reload(lastWorkedOnTask); if (old.getWorkUnits().size() > 0 && !old.getWorkUnits().last().isFinished()) { old.getWorkUnits().last().stop(); } }, executorService); } window.getWorkingOnTaskLink().setCurrentTask(task); } @Override public void duringSave(Task model) { model.setDescription(description.getText()); } public String getHourMinutesString(Duration duration) { long hours = duration.toHours(); if (hours == 0) { return duration.toMinutes() + Localized.get("duration.minutes"); } else { long remainingMinutes = duration.minus(Duration.ofHours(hours)).toMinutes(); return hours + ":" + String.format("%02d", remainingMinutes) + Localized.get("duration.hours.short"); } } private void increaseProgress() { Task task = store.getModel(); Duration estimatedTime = task.getEstimatedTime(); if (estimatedTime == null || estimatedTime.toMillis() == 0) { controller.getJavaFXExecutor().submit(() -> estimatedTimeBar.setProgress(-0.1D)); } else { Task reloaded = PersistentWork.wrap(() -> { Task reload = PersistentWork.reload(task); reload.getWorkUnits().forEach(u -> u.getDuration()); return reload; }); Duration took = reloaded.getTotalWorkDuration(); controller.getJavaFXExecutor().submit(() -> tookString.set(getHourMinutesString(took))); if (took.compareTo(estimatedTime) < 0) { double progress = 100D / Math.max(estimatedTime.toMillis(), 1) * took.toMillis() / 100D; controller.getJavaFXExecutor().submit(() -> estimatedTimeBar.setProgress(progress)); } else { Duration over = took.minus(estimatedTime); controller.getJavaFXExecutor().submit(() -> estimatedTimeBar.setProgress(1.0)); controller.getJavaFXExecutor().submit(() -> overTime.setText(getHourMinutesString(over))); } } } }