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}