/** * Copyright [2015] [Christian Loehnert] * * 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.gallery.ui.slideshow; import de.ks.BaseController; import de.ks.activity.executor.ActivityExecutor; import de.ks.gallery.GalleryItem; import de.ks.i18n.Localized; import de.ks.javafx.ScreenResolver; import javafx.beans.binding.Bindings; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Rectangle2D; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.ChoiceBox; import javafx.scene.control.Label; import javafx.scene.control.ToggleButton; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; 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.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ResourceBundle; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public class Slideshow extends BaseController<Object> { private static final Logger log = LoggerFactory.getLogger(Slideshow.class); @FXML private GridPane menuBar; @FXML private Button markForDeletion; @FXML private Button markImage; @FXML private ChoiceBox<Integer> speed; @FXML private ToggleButton startStop; @FXML private StackPane root; @FXML private ImageView imageView; @FXML private Label imageTitle; protected final ObservableList<GalleryItem> items = FXCollections.observableArrayList(); protected final List<GalleryItem> sorted = new ArrayList<>(); protected final AtomicInteger currentIndex = new AtomicInteger(); protected Stage fullscreenStage; protected Scene scene; protected final ObservableList<GalleryItem> markedForDeletion = FXCollections.observableArrayList(); protected final ObservableList<GalleryItem> markedItems = FXCollections.observableArrayList(); @Inject ActivityExecutor executor; private ScheduledFuture<?> scheduledFuture; @Override public void initialize(URL location, ResourceBundle resources) { items.addListener((ListChangeListener<GalleryItem>) c -> { sorted.clear(); sorted.addAll(items); Collections.sort(sorted); }); root.setStyle("-fx-background-color: black;"); root.setOnMouseClicked(e -> { if (e.getSceneX() < root.getScene().getWidth() / 2) { previous(); } else if (e.getSceneX() > root.getScene().getWidth() / 2) { next(); } }); controller.getJavaFXExecutor().submit(this::createStage); speed.setItems(FXCollections.observableArrayList(1, 2, 3, 4, 5, 7, 8, 10, 15, 20)); speed.setValue(3); speed.valueProperty().addListener((p, o, n) -> restartWithTimeout()); menuBar.setVisible(false); root.setOnMouseMoved(e -> { if (e.getSceneY() < 100) { menuBar.setVisible(true); } else { menuBar.setVisible(false); } }); String s = getClass().getResource("slideshow.css").toExternalForm(); root.getStylesheets().add(s); } protected void createStage() { fullscreenStage = new Stage(); fullscreenStage.setFullScreen(true); fullscreenStage.setFullScreenExitHint(""); scene = new Scene(root); scene.setOnKeyReleased(e -> { if (e.getCode() == KeyCode.ESCAPE) { fullscreenStage.close(); } if (e.getCode() == KeyCode.SPACE) { next(); } else if (e.getCode() == KeyCode.PAGE_DOWN) { next(); } else if (e.getCode() == KeyCode.PAGE_UP) { previous(); } else if (e.getCode() == KeyCode.RIGHT) { next(); } else if (e.getCode() == KeyCode.LEFT) { previous(); } else if (e.getCode() == KeyCode.ALT) { menuBar.setVisible(false); } }); scene.setOnKeyPressed(e -> { if (e.getCode() == KeyCode.ALT) { menuBar.setVisible(true); } }); scene.setOnScroll(e -> { double deltaY = e.getDeltaY(); if (deltaY > 0) { previous(); } else { next(); } }); fullscreenStage.setScene(scene); Rectangle2D bounds = new ScreenResolver().getScreenToShow().getBounds(); fullscreenStage.setX(bounds.getMinX()); fullscreenStage.setY(bounds.getMinY()); fullscreenStage.setWidth(bounds.getWidth()); fullscreenStage.setHeight(bounds.getHeight()); fullscreenStage.initModality(Modality.NONE); fullscreenStage.setOnHiding(e -> stop()); } public void show(GalleryItem item) { show(sorted.indexOf(item)); } public void show(int index) { currentIndex.set(index); GalleryItem item = sorted.get(index); Rectangle2D bounds = new ScreenResolver().getScreenToShow().getBounds(); Image image = item.getImage(); if (bounds.getWidth() > bounds.getHeight()) { imageView.fitHeightProperty().bind(Bindings.min(image.getHeight(), root.heightProperty())); } else { imageView.fitWidthProperty().bind(Bindings.min(image.getWidth(), root.widthProperty())); } imageView.setImage(item.getImage()); imageTitle.setText(item.getName()); fullscreenStage.setTitle(item.getName()); fullscreenStage.setFullScreen(true); fullscreenStage.show(); } public void next() { clearPrevious(); int index = getNextIndex(); show(index); preloadNext(); } protected void clearPrevious() { if (sorted.size() <= 3) { return; } int old = currentIndex.get() - 1; if (old < 0) { old = sorted.size() - 1; } sorted.get(old).clear(); } protected void preloadNext() { int next = currentIndex.get() + 1; if (next >= sorted.size()) { next = 0; } final int load = next; executor.submit(() -> sorted.get(load).getImage()); } protected int getNextIndex() { int index = currentIndex.incrementAndGet(); if (index >= items.size()) { index = 0; currentIndex.set(0); } return index; } public void previous() { clearNext(); int index = getPreviousIndex(); show(index); preloadPrevious(); } protected void clearNext() { if (sorted.size() <= 3) { return; } int old = currentIndex.get() + 1; if (old >= sorted.size()) { old = 0; } sorted.get(old).clear(); } protected void preloadPrevious() { int next = currentIndex.get() - 1; if (next < 0) { next = sorted.size() - 1; } final int load = next; executor.submit(() -> sorted.get(load).getImage()); } protected int getPreviousIndex() { int index = currentIndex.decrementAndGet(); if (index < 0) { currentIndex.set(items.size() - 1); index = currentIndex.get(); } return index; } public ObservableList<GalleryItem> getItems() { return items; } public void setItems(ObservableList<GalleryItem> items) { this.items.clear(); this.items.addAll(items); } @FXML void onMarkImage() { GalleryItem item = sorted.get(currentIndex.get()); if (!markedForDeletion.contains(item)) { log.info("Marked item {}", item.getName()); markedItems.add(item); } } @FXML void onMarkForDeletion() { GalleryItem item = sorted.get(currentIndex.get()); if (!markedForDeletion.contains(item)) { log.info("Marked item {} for deletion", item.getName()); markedForDeletion.add(item); } } @FXML void onStartStop() { if (startStop.isSelected()) { start(); } else { stop(); } } private void start() { startStop.setText(Localized.get("gallery.stop.mneominc")); restartWithTimeout(); } private void stop() { log.info("Stopping slideshow"); if (scheduledFuture != null) { scheduledFuture.cancel(true); scheduledFuture = null; } startStop.setText(Localized.get("gallery.start.mneominc")); startStop.setSelected(false); } private void restartWithTimeout() { if (scheduledFuture != null) { scheduledFuture.cancel(true); scheduledFuture = null; } log.info("Starting slideshow with timeout {}s", speed.getValue()); Integer delay = speed.getValue(); scheduledFuture = controller.getExecutorService().scheduleAtFixedRate(() -> controller.getJavaFXExecutor().submit(this::next), delay, delay, TimeUnit.SECONDS); } public Button getMarkImage() { return markImage; } public Button getMarkForDeletion() { return markForDeletion; } public ObservableList<GalleryItem> getMarkedForDeletion() { return markedForDeletion; } public ObservableList<GalleryItem> getMarkedItems() { return markedItems; } }