001package conexp.fx.gui.task; 002 003import conexp.fx.core.math.Math3; 004import conexp.fx.gui.util.Platform2; 005 006/* 007 * #%L 008 * Concept Explorer FX 009 * %% 010 * Copyright (C) 2010 - 2023 Francesco Kriegel 011 * %% 012 * This program is free software: you can redistribute it and/or modify 013 * it under the terms of the GNU General Public License as 014 * published by the Free Software Foundation, either version 3 of the 015 * License, or (at your option) any later version. 016 * 017 * This program is distributed in the hope that it will be useful, 018 * but WITHOUT ANY WARRANTY; without even the implied warranty of 019 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 020 * GNU General Public License for more details. 021 * 022 * You should have received a copy of the GNU General Public 023 * License along with this program. If not, see 024 * <http://www.gnu.org/licenses/gpl-3.0.html>. 025 * #L% 026 */ 027 028import javafx.beans.binding.Bindings; 029import javafx.beans.value.ChangeListener; 030import javafx.beans.value.ObservableValue; 031import javafx.geometry.Insets; 032import javafx.geometry.Pos; 033import javafx.scene.Scene; 034import javafx.scene.control.Button; 035import javafx.scene.control.ButtonBuilder; 036import javafx.scene.control.Label; 037import javafx.scene.control.LabelBuilder; 038import javafx.scene.control.ListCell; 039import javafx.scene.control.ListView; 040import javafx.scene.control.ProgressBar; 041import javafx.scene.control.ProgressBarBuilder; 042import javafx.scene.control.ProgressIndicator; 043import javafx.scene.control.ProgressIndicatorBuilder; 044import javafx.scene.layout.AnchorPane; 045import javafx.scene.layout.BorderPane; 046import javafx.scene.layout.BorderPaneBuilder; 047import javafx.scene.layout.HBox; 048 049public final class ExecutorStatusBar { 050 051 private final class TaskItem { 052 053 private final ProgressBar currentProgressBar = ProgressBarBuilder 054 .create() 055 .minHeight(height - 2 * padding) 056 .maxHeight(height - 2 * padding) 057 .minWidth(100) 058 .maxWidth(100) 059 .build(); 060 private final Label currentStatusLabel = LabelBuilder 061 .create() 062 .minWidth(554) 063 .maxWidth(554) 064 .minHeight(height - 2 * padding) 065 .maxHeight(height - 2 * padding) 066 .build(); 067 private final Label timeLabel = LabelBuilder 068 .create() 069 .minWidth(100) 070 .maxWidth(100) 071 .minHeight(height - 2 * padding) 072 .maxHeight(height - 2 * padding) 073 .build(); 074 private final Button stopButton = 075 ButtonBuilder.create().style("-fx-base: red").maxHeight(12).maxWidth(12).minHeight(12).minWidth(12).build(); 076 private final BorderPane currentPane = new BorderPane(); 077 078 public TaskItem() { 079 currentPane.setLeft(currentStatusLabel); 080 currentPane.setCenter(new HBox(currentProgressBar, stopButton)); 081 currentPane.setRight(timeLabel); 082 currentProgressBar.setPadding(new Insets(0, 5, 0, 5)); 083 } 084 085 private final void bindTo(final TimeTask<?> task) { 086 currentStatusLabel.textProperty().bind(task.titleProperty()); 087 timeLabel.textProperty().bind( 088 Bindings.createStringBinding( 089 () -> task.isCancelled() ? "cancelled" : Math3.formatNanos(task.runTimeNanosProperty().get()), 090 task.runTimeNanosProperty(), 091 task.stateProperty())); 092 timeLabel.alignmentProperty().bind( 093 Bindings.createObjectBinding( 094 () -> task.isDone() || task.isRunning() ? Pos.BASELINE_RIGHT : Pos.BASELINE_LEFT, 095 task.stateProperty())); 096 currentProgressBar.progressProperty().bind(task.progressProperty()); 097 stopButton.setOnAction(e -> task.cancel(true)); 098 stopButton.visibleProperty().bind(Bindings.createObjectBinding(() -> !task.isDone(), task.stateProperty())); 099 } 100 } 101 102 private BlockingExecutor executor; 103 public final BorderPane statusBar; 104 private final int height = 20; 105 private final int padding = 2; 106 private final ProgressIndicator overallProgressIndicator = ProgressIndicatorBuilder 107 .create() 108 .minHeight(height - 2 * padding) 109 .maxHeight(height - 2 * padding) 110 .minWidth(height - 2 * padding) 111 .maxWidth(height - 2 * padding) 112 .build(); 113 private final ProgressBar overallProgressBar = ProgressBarBuilder 114 .create() 115 .minHeight(height - 2 * padding) 116 .maxHeight(height - 2 * padding) 117 .minWidth(200) 118 .maxWidth(200) 119 .build(); 120 private final Label overallStatusLabel = LabelBuilder 121 .create() 122 .graphic(overallProgressIndicator) 123 .minHeight(height - 2 * padding) 124 .maxHeight(height - 2 * padding) 125 .build(); 126 127 private final ListView<TimeTask<?>> scheduledTaskListView; 128 129 public ExecutorStatusBar(AnchorPane overlayPane) { 130 final BorderPane overallPane = 131 BorderPaneBuilder.create().left(overallStatusLabel).right(overallProgressBar).build(); 132 statusBar = BorderPaneBuilder 133 .create() 134 .padding(new Insets(padding, padding, padding, padding)) 135 .minHeight(height) 136 .maxHeight(height) 137 .right(overallPane) 138 .build(); 139 scheduledTaskListView = new ListView<TimeTask<?>>(); 140// scheduledTaskListView.setOnMouseClicked(e -> executor.clearFinished()); 141 scheduledTaskListView.setPrefSize(800, 200); 142 scheduledTaskListView.setCellFactory(l -> { 143 final ListCell<TimeTask<?>> cell = new ListCell<TimeTask<?>>() { 144 145 private final TaskItem taskItem = new TaskItem(); 146 147 @Override 148 protected void updateItem(TimeTask<?> p, boolean empty) { 149 if (empty) 150 return; 151 taskItem.bindTo(p); 152 setGraphic(taskItem.currentPane); 153 } 154 }; 155 return cell; 156 }); 157 overallPane.setOnMouseEntered(e -> { 158 overlayPane.setMouseTransparent(false); 159 if (overlayPane.getChildren().contains(scheduledTaskListView)) 160 return; 161 overlayPane.getChildren().add(scheduledTaskListView); 162 AnchorPane.setRightAnchor(scheduledTaskListView, 4d); 163 AnchorPane.setBottomAnchor(scheduledTaskListView, 4d); 164 }); 165 scheduledTaskListView.setOnMouseExited(e -> { 166 overlayPane.setMouseTransparent(true); 167 overlayPane.getChildren().clear(); 168 }); 169 } 170 171 public final void setOnMouseExitedHandler(final Scene scene) { 172 scene.setOnMouseExited(scheduledTaskListView.getOnMouseExited()); 173 } 174 175 public void bindTo(final BlockingExecutor executor) { 176 this.executor = executor; 177 scheduledTaskListView.setItems(executor.scheduledTasks); 178 scheduledTaskListView.scrollTo(executor.currentTaskProperty.getValue()); 179 overallProgressIndicator.progressProperty().bind( 180 Bindings.createDoubleBinding( 181 () -> executor.overallProgressBinding.get() != 1 ? -1d : 1d, 182 executor.overallProgressBinding)); 183 overallProgressIndicator.visibleProperty().bind( 184 Bindings.createBooleanBinding(() -> !executor.isIdleBinding.get(), executor.isIdleBinding)); 185 executor.currentTaskProperty.addListener(new ChangeListener<TimeTask<?>>() { 186 187 public final void changed( 188 final ObservableValue<? extends TimeTask<?>> observable, 189 final TimeTask<?> oldTask, 190 final TimeTask<?> newTask) { 191 Platform2.runOnFXThread(() -> { 192 scheduledTaskListView.scrollTo(newTask); 193 overallStatusLabel 194 .textProperty() 195 .bind( 196 Bindings.createStringBinding( 197 () -> executor.isIdleBinding.get() ? "" : newTask.titleProperty().get() 198 + (newTask.messageProperty().get().equals("") ? "" : ": " + newTask.messageProperty().get()), 199 executor.isIdleBinding, 200 newTask.messageProperty(), 201 newTask.titleProperty())); 202 }); 203 } 204 }); 205 overallProgressBar.progressProperty().bind(executor.overallProgressBinding); 206 } 207}