Compare commits

..

7 Commits
v1.0 ... master

10 changed files with 190 additions and 75 deletions

View File

@ -7,6 +7,6 @@
</content> </content>
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="lib1" level="project" /> <orderEntry type="library" name="javaFX15" level="project" />
</component> </component>
</module> </module>

View File

@ -2,10 +2,19 @@
Implementation of a simple Photo viewer with zoom-functions and a slide-show mode Implementation of a simple Photo viewer with zoom-functions and a slide-show mode
University Project using Java 15 and JavaFX 15 University Project using Java and JavaFX
Note for using project in Intelij IDEA: Note for using project in Intelij IDEA:
To use JavaFX 15 with Intelij IDEA it is required to add the following settings to the VM-Options (in configuration) To use JavaFX 15 with Intelij IDEA it is required to add the following settings to the VM-Options (in configuration)
``` ```
--module-path /PATH/TO/JAVAFX/BIN/DIR --add-modules javafx.controls,javafx.fxml --module-path /PATH/TO/JAVAFX/LIB/DIR --add-modules javafx.controls,javafx.fxml
``` ```
## Requirenments:
[Java 15](https://jdk.java.net/15/)
[JavaFX 15](https://gluonhq.com/products/javafx/)
## Tested on
Arch Linux x64 w/ openjfx-15, openjdk-15
Windows 10 x64 w/ openjfx-15, openjdk-15

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

BIN
icons/left-arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
icons/right-arrow.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -5,7 +5,7 @@ package de.thm.tlf.photoViewer;
* but any of the actions that involve pictures are performed. * but any of the actions that involve pictures are performed.
*/ */
public class NoPicturesLoadedException extends Exception{ public class NoPicturesLoadedException extends Exception{
NoPicturesLoadedException(String s){ NoPicturesLoadedException(){
super(s); super("No pictures have been loaded");
} }
} }

View File

@ -10,10 +10,12 @@ import javafx.concurrent.Task;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Node; import javafx.scene.Node;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.*; import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCodeCombination;
@ -23,6 +25,8 @@ import javafx.stage.FileChooser;
import javafx.stage.Stage; import javafx.stage.Stage;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.List; import java.util.List;
/** /**
@ -31,12 +35,17 @@ import java.util.List;
* *
* @author Tim Lukas Förster * @author Tim Lukas Förster
* @version 1.0 * @version 1.0
* repository: https://gitlab.com/n0x_io/PhotoViewer
*/ */
public class Controller extends Application { public class PVController extends Application {
//////////////////////////// ////////////////////////////
//------ Attributes ------// //------ Attributes ------//
//////////////////////////// ////////////////////////////
// Constants //
private static final String ARROWPREV = "icons/left-arrow.png";
private static final String ARROWNEXT = "icons/right-arrow.png";
// CENTER // // CENTER //
private final ScrollPane currentViewSP = new ScrollPane(); private final ScrollPane currentViewSP = new ScrollPane();
private final ImageView centerImageView = new ImageView(); private final ImageView centerImageView = new ImageView();
@ -46,17 +55,18 @@ public class Controller extends Application {
private final MenuBar menuBar = new MenuBar(); private final MenuBar menuBar = new MenuBar();
private final Menu fileMenu = new Menu("File"); private final Menu fileMenu = new Menu("File");
private final Menu aboutMenu = new Menu("About"); private final Menu aboutMenu = new Menu("About");
private final MenuItem openFiles = new MenuItem("Open"); private final MenuItem menuItemOpenFiles = new MenuItem("Open");
private final MenuItem clearViewer = new Menu("Close all"); private final MenuItem menuItemClearViewer = new Menu("Close all pictures");
private final MenuItem startSlideShow = new MenuItem("Start Slide Show"); private final MenuItem menuItemStartSlideShow = new MenuItem("Start Slide Show");
private final MenuItem exitViewer = new Menu("Exit"); private final MenuItem menuItemExitViewer = new Menu("Exit");
private final MenuItem showInfo = new Menu("Information"); private final MenuItem menuItemShowInfo = new Menu("Information");
// BOTTOM // // BOTTOM //
private final VBox bottomPanel = new VBox(); private final VBox bottomPanel = new VBox();
private final HBox selectionPane = new HBox(); //private final HBox selectionPane = new HBox();
private final ScrollPane pictureSelector = new ScrollPane(); private final ListView<ImageView> previewPane = new ListView<>();
private final VBox pictureSelector = new VBox();
private final BorderPane bottomLowerPanel = new BorderPane(); private final BorderPane bottomLowerPanel = new BorderPane();
private final HBox bottomLeft = new HBox(); private final HBox bottomLeft = new HBox();
@ -77,11 +87,13 @@ public class Controller extends Application {
private PictureHandler picHandler; private PictureHandler picHandler;
// Etc. // // Etc. //
private Thread slideShowThread;
private boolean bIsFullScreen = false; private boolean bIsFullScreen = false;
private boolean bSlideShowActive = false; private boolean bSlideShowActive = false;
private final DoubleProperty slideShowSpeed = new SimpleDoubleProperty(4); private final DoubleProperty slideShowSpeed = new SimpleDoubleProperty(4);
private final DoubleProperty zoomProperty = new SimpleDoubleProperty(200); private final DoubleProperty zoomProperty = new SimpleDoubleProperty(200);
// Key Combinations & Shortcuts //
private final KeyCombination keyCrtlQ = new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_ANY); private final KeyCombination keyCrtlQ = new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_ANY);
//////////////////////////////// ////////////////////////////////
//------ End Attributes ------// //------ End Attributes ------//
@ -116,7 +128,7 @@ public class Controller extends Application {
// Create all Actions // // Create all Actions //
createSliderActions(); createSliderActions();
createMenuActions(primaryStage); createMenuActions(primaryStage);
createButtonActions(primaryStage); createClickActions(primaryStage);
// Keypress Actions // // Keypress Actions //
mainScene.setOnKeyPressed(event -> { mainScene.setOnKeyPressed(event -> {
@ -151,12 +163,11 @@ public class Controller extends Application {
primaryStage.show(); primaryStage.show();
} }
/** /**
* Task used to run the slideshow in separate Threat. * Task used to run the slideshow in separate Threat.
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
Task slideShowTask = new Task<Void>(){ private final Task slideShowTask = new Task<Void>(){
@Override @Override
@SuppressWarnings("BusyWait") @SuppressWarnings("BusyWait")
protected Void call() throws Exception { protected Void call() throws Exception {
@ -179,10 +190,26 @@ public class Controller extends Application {
/** /**
* Event wrapper for clear function * Event wrapper for clear function
* @return EventHandle executing the clear function * @return EventHandle executing the clearPreviewView function
*/ */
private EventHandler<ActionEvent> clearPreviewViewEvent(){ private EventHandler<ActionEvent> clearPreviewViewEvent(){
return e -> clearPreviewView(); return e -> clearViewer();
}
/**
* Event wrapper for showAboutDialog function
* @return EventHandle executing the showAboutDialog function
*/
private EventHandler<ActionEvent> showAboutDialogEvent(){
return e -> showAboutDialog();
}
/**
* Event wrapper for showAboutDialog function
* @return EventHandle executing the showAboutDialog function
*/
private EventHandler<ActionEvent> toggleSlidesHowEvent(){
return e -> toggleSlideShow();
} }
//////////////////////////////// ////////////////////////////////
@ -192,6 +219,7 @@ public class Controller extends Application {
* Helper method for handling sliders * Helper method for handling sliders
*/ */
private void createSliderActions() { private void createSliderActions() {
//TODO: Zoom kinda wonky... should be redone.
// Zoom Action // // Zoom Action //
zoomProperty.addListener(observable -> { zoomProperty.addListener(observable -> {
centerImageView.setFitWidth(zoomProperty.get() * 4); centerImageView.setFitWidth(zoomProperty.get() * 4);
@ -217,18 +245,22 @@ public class Controller extends Application {
* @param stage the primary stage, used to display the file-open-dialog * @param stage the primary stage, used to display the file-open-dialog
*/ */
private void createMenuActions(Stage stage) { private void createMenuActions(Stage stage) {
openFiles.setOnAction(openFileDialogEvent(stage)); menuItemOpenFiles.setOnAction(openFileDialogEvent(stage));
exitViewer.setOnAction( e -> Platform.exit()); menuItemExitViewer.setOnAction(e -> Platform.exit());
clearViewer.setOnAction(clearPreviewViewEvent()); menuItemClearViewer.setOnAction(clearPreviewViewEvent());
menuItemStartSlideShow.setOnAction(toggleSlidesHowEvent());
menuItemShowInfo.setOnAction(showAboutDialogEvent());
} }
/** /**
* Helper method for handling button interaction * Helper method for handling button interaction
* @param stage the primary stage, used to display the file-open-dialog and control fullscreen functions * @param stage the primary stage, used to display the file-open-dialog and control fullscreen functions
*/ */
private void createButtonActions(Stage stage) { private void createClickActions(Stage stage) {
// Opens the File dialog
openFilesButton.setOnAction(openFileDialogEvent(stage)); openFilesButton.setOnAction(openFileDialogEvent(stage));
// Displays the previous Picture
prevPicBtn.setOnAction(e -> { prevPicBtn.setOnAction(e -> {
try{ try{
centerImageView.setImage(picHandler.getPrevPicture().getImage()); centerImageView.setImage(picHandler.getPrevPicture().getImage());
@ -236,6 +268,7 @@ public class Controller extends Application {
catch (NoPicturesLoadedException npl){ showNoPicturesLoadedWarning();} catch (NoPicturesLoadedException npl){ showNoPicturesLoadedWarning();}
}); });
// Displays the next picture
nextPicBtn.setOnAction(e -> { nextPicBtn.setOnAction(e -> {
try{ try{
centerImageView.setImage(picHandler.getNextPicture().getImage()); centerImageView.setImage(picHandler.getNextPicture().getImage());
@ -243,35 +276,51 @@ public class Controller extends Application {
catch (NoPicturesLoadedException npl){ showNoPicturesLoadedWarning();} catch (NoPicturesLoadedException npl){ showNoPicturesLoadedWarning();}
}); });
// Sets the PictureViewer to fullscreen
fullScreenBtn.setOnAction(e -> { fullScreenBtn.setOnAction(e -> {
stage.setFullScreen(!bIsFullScreen); stage.setFullScreen(!bIsFullScreen);
bIsFullScreen ^= true; bIsFullScreen ^= true;
}); });
slideShowBtn.setOnAction(new EventHandler<>() { // Starts the slideshow on first click, stops it on the next one
Thread th; slideShowBtn.setOnAction(toggleSlidesHowEvent());
@Override // Clicking on any picture in the Previews opens said picture in the main view
public void handle(ActionEvent actionEvent) { previewPane.setOnMouseClicked(e -> {
try {
centerImageView.setImage(picHandler.getPictureByID(previewPane.getSelectionModel().getSelectedIndex()).getImage());
} catch (NoPicturesLoadedException ignored) {
}
});
}
/**
* Method that will start and stop (toggle) the Slide show and set the corresponding texts to all buttons etc.
*/
private void toggleSlideShow(){
try { try {
if (!bSlideShowActive) { if (!bSlideShowActive) {
// This statement is to catch any errors regarding no images loaded // This statement is to catch any errors regarding no images loaded
centerImageView.setImage(picHandler.getNextPicture().getImage()); centerImageView.setImage(picHandler.getNextPicture().getImage());
bSlideShowActive = true; bSlideShowActive = true;
slideShowThread = new Thread(slideShowTask);
slideShowThread.start();
// Change text of slideshow switches
slideShowBtn.setText("Stop Slide Show"); slideShowBtn.setText("Stop Slide Show");
th = new Thread(slideShowTask); menuItemStartSlideShow.setText("Stop Slide Show");
th.start();
} else { } else {
bSlideShowActive = false; bSlideShowActive = false;
// Change text of slideshow switches
slideShowBtn.setText("Slide Show"); slideShowBtn.setText("Slide Show");
th.interrupt(); menuItemStartSlideShow.setText("Start Slide Show");
assert slideShowThread != null;
slideShowThread.interrupt();
} }
} catch (NoPicturesLoadedException npl){ } catch (NoPicturesLoadedException npl){
showNoPicturesLoadedWarning(); showNoPicturesLoadedWarning();
} }
} }
});
}
/** /**
* Open file-dialog allowing to select multiple pictures (via filter), * Open file-dialog allowing to select multiple pictures (via filter),
@ -287,7 +336,7 @@ public class Controller extends Application {
if (selectedPictures != null) { if (selectedPictures != null) {
try { try {
picHandler.loadPictures(selectedPictures); picHandler.loadPictures(selectedPictures);
Controller.this.updatePreviewView(); PVController.this.updatePreviewView();
centerImageView.setImage(picHandler.getNextPicture().getImage()); centerImageView.setImage(picHandler.getNextPicture().getImage());
} catch (NoPicturesLoadedException npl) { } catch (NoPicturesLoadedException npl) {
showNoPicturesLoadedWarning(); showNoPicturesLoadedWarning();
@ -299,26 +348,58 @@ public class Controller extends Application {
* Method that updates the preview Pane with all PreviewPictures provided by the Picture Handler * Method that updates the preview Pane with all PreviewPictures provided by the Picture Handler
*/ */
private void updatePreviewView(){ private void updatePreviewView(){
clearPreviewView(); clearViewer();
selectionPane.setPadding(new Insets(5,5,5,5)); //previewPane.setPadding(new Insets(5,5,5,5));
for(PicturePreview pp: picHandler.getPreviews()){ for(PicturePreview pp: picHandler.getPreviews()){
ImageView iv = new ImageView(pp.getImage()); ImageView iv = new ImageView(pp.getImage());
iv.setFitWidth(150); iv.setFitWidth(150);
iv.setFitHeight(150);
iv.setSmooth(true); iv.setSmooth(true);
iv.setPreserveRatio(true); iv.setPreserveRatio(true);
selectionPane.getChildren().add(iv); previewPane.getItems().add(iv);
} }
} }
/** /**
* Used to clear the preview and center panel when the viewer is cleared * Used to clear the preview and center panel
*/ */
private void clearPreviewView(){ private void clearViewer(){
selectionPane.getChildren().clear(); previewPane.getItems().clear();
selectionPane.setPadding(new Insets(5,155,5,5)); //previewPane.setPadding(new Insets(5,5,5,5));
centerImageView.setImage(null); centerImageView.setImage(null);
} }
/**
* Show a dialogue displaying copyright information about the program.
*/
private void showAboutDialog(){
Dialog<String> aboutDialog = new Dialog<>();
aboutDialog.setTitle("About this program");
ButtonType btnType = new ButtonType("Ok", ButtonBar.ButtonData.OK_DONE);
aboutDialog.setContentText("""
Created by Tim Lukas Förster
Icons made by www.flaticon.com/authors/roundicons
for www.flaticon.com""");
//Adding buttons to the aboutDialog pane
aboutDialog.getDialogPane().getButtonTypes().add(btnType);
aboutDialog.show();
}
/**
* Displays a warning dialogue informing the user that no pictures has been loaded yet
* and the executed action is not possible.
*/
private void showNoPicturesLoadedWarning(){
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("No pictures loaded");
alert.setContentText("No pictures have been loaded" +
"\nPlease select pictures via the menu or via the open button to view them.");
alert.showAndWait().ifPresent(rs -> {
});
}
/** /**
* Method that handles the settings and layout of the top Panel * Method that handles the settings and layout of the top Panel
* @return Node, set up for displaying on the top of a GridPane * @return Node, set up for displaying on the top of a GridPane
@ -326,9 +407,9 @@ public class Controller extends Application {
private Node createTop(){ private Node createTop(){
SeparatorMenuItem sep = new SeparatorMenuItem(); SeparatorMenuItem sep = new SeparatorMenuItem();
// File Menu // File Menu
fileMenu.getItems().addAll(openFiles, clearViewer, sep, startSlideShow, exitViewer); fileMenu.getItems().addAll(menuItemOpenFiles, menuItemClearViewer, sep, menuItemStartSlideShow, menuItemExitViewer);
// About Menu // About Menu
aboutMenu.getItems().addAll(showInfo); aboutMenu.getItems().addAll(menuItemShowInfo);
// Menu bar // Menu bar
menuBar.getMenus().addAll(fileMenu, aboutMenu); menuBar.getMenus().addAll(fileMenu, aboutMenu);
// Adding Menus to Top Panel // Adding Menus to Top Panel
@ -372,11 +453,31 @@ public class Controller extends Application {
zoomSlider.setShowTickMarks(true); zoomSlider.setShowTickMarks(true);
Label zoomLabel = new Label("Zoom:"); Label zoomLabel = new Label("Zoom:");
bottomLeft.getChildren().addAll(openFilesButton, zoomLabel, zoomSlider); bottomLeft.getChildren().addAll(openFilesButton, zoomLabel, zoomSlider);
//bottomLeft.getChildren().addAll(zoomLabel, zoomSlider);
bottomLeft.setSpacing(5); bottomLeft.setSpacing(5);
bottomLowerPanel.setLeft(bottomLeft); bottomLowerPanel.setLeft(bottomLeft);
// Middle Bottom Part // Middle Bottom Part
try {
// Set pictures as prev/next buttons
ImageView prevPic = new ImageView(new Image(new FileInputStream(ARROWPREV)));
prevPic.setFitHeight(10);
prevPic.setPreserveRatio(true);
prevPicBtn.setText(null);
prevPicBtn.setGraphic(prevPic);
ImageView nextPic = new ImageView(new Image(new FileInputStream(ARROWNEXT)));
nextPic.setFitHeight(10);
nextPic.setPreserveRatio(true);
nextPicBtn.setText(null);
nextPicBtn.setGraphic(nextPic);
} catch (FileNotFoundException ignored){
// fallback to text
prevPicBtn.setGraphic(null);
nextPicBtn.setGraphic(null);
prevPicBtn.setText("<-");
nextPicBtn.setText("->");
}
bottomMid.setAlignment(Pos.CENTER); bottomMid.setAlignment(Pos.CENTER);
bottomMid.setSpacing(5); bottomMid.setSpacing(5);
bottomMid.getChildren().addAll(prevPicBtn, slideShowBtn, nextPicBtn); bottomMid.getChildren().addAll(prevPicBtn, slideShowBtn, nextPicBtn);
@ -391,28 +492,16 @@ public class Controller extends Application {
bottomLowerPanel.setRight(bottomRight); bottomLowerPanel.setRight(bottomRight);
// Bottom upper Part -> Picture preview // Bottom upper Part -> Picture preview
selectionPane.setPadding(new Insets(5,5,155,5)); pictureSelector.setPadding(new Insets(2,2,2,2));
selectionPane.autosize(); previewPane.setOrientation(Orientation.HORIZONTAL);
selectionPane.setSpacing(5); previewPane.setMaxHeight(150);
pictureSelector.setContent(selectionPane); //selectionPane.setSpacing(5);
pictureSelector.getChildren().add(previewPane);
bottomPanel.getChildren().addAll(pictureSelector, bottomLowerPanel); bottomPanel.getChildren().addAll(pictureSelector, bottomLowerPanel);
return (bottomPanel); return (bottomPanel);
} }
/**
* Displays a warning dialogue informing the user that no pictures has been loaded yet
* and the executed action is not possible.
*/
private void showNoPicturesLoadedWarning(){
Alert alert = new Alert(Alert.AlertType.WARNING);
alert.setTitle("No pictures loaded");
alert.setContentText("No pictures have been loaded" +
"\nPlease select pictures via the menu or via the open button to view them.");
alert.showAndWait().ifPresent(rs -> {
});
}
//////////////////////////////////// ////////////////////////////////////
//------ End Helper-Methods ------// //------ End Helper-Methods ------//
//////////////////////////////////// ////////////////////////////////////

View File

@ -62,13 +62,14 @@ public final class PictureHandler {
} }
/** /**
* Determines the next picture that should be displayed * Determines the next picture that should be displayed.
* Wraps around to the beginning of the list if picture after the last one is requested.
* @return Picture object that should be displayed next from the ArrayList pictures * @return Picture object that should be displayed next from the ArrayList pictures
* @throws NoPicturesLoadedException Exception for when no pictures are loaded yet but tried to access them * @throws NoPicturesLoadedException Exception for when no pictures are loaded yet but tried to access them
*/ */
public Picture getNextPicture() throws NoPicturesLoadedException { public Picture getNextPicture() throws NoPicturesLoadedException {
if(pictures.size() <= 0){ if(pictures.size() <= 0){
throw new NoPicturesLoadedException("No pictures have been loaded"); throw new NoPicturesLoadedException();
} }
else { else {
currentPictureID = (currentPictureID + 1) % pictures.size(); currentPictureID = (currentPictureID + 1) % pictures.size();
@ -77,19 +78,35 @@ public final class PictureHandler {
} }
/** /**
* Determines the previous picture that should be displayed * Determines the previous picture that should be displayed.
* Wraps around to the end of the list if picture before the first one is requested.
* @return Picture object that should be displayed next from the ArrayList pictures * @return Picture object that should be displayed next from the ArrayList pictures
* @throws NoPicturesLoadedException Exception for when no pictures are loaded yet but tried to access them * @throws NoPicturesLoadedException Exception for when no pictures are loaded yet but tried to access them
*/ */
public Picture getPrevPicture() throws NoPicturesLoadedException{ public Picture getPrevPicture() throws NoPicturesLoadedException{
if(pictures.size() <= 0){ if(pictures.size() <= 0){
throw new NoPicturesLoadedException("No pictures have been loaded"); throw new NoPicturesLoadedException();
} }
else { else {
currentPictureID = (currentPictureID + pictures.size() - 1) % pictures.size(); currentPictureID = (currentPictureID + pictures.size() - 1) % pictures.size();
return pictures.get(currentPictureID); return pictures.get(currentPictureID);
} }
} }
/**
* Method to acquire picture by the list-ID of a picture
* @param pictureID the ID of the Picture that is being requested
* @return Picture from the pictures ArrayList on position of the provided ID
* @throws NoPicturesLoadedException Exception for when no pictures are loaded yet but tried to access them
*/
public Picture getPictureByID(int pictureID) throws NoPicturesLoadedException{
if(pictures.size() <= 0){
throw new NoPicturesLoadedException();
}
else {
return pictures.get(pictureID);
}
}
//------ End Methods ------// //------ End Methods ------//
} }