Package org.ugate.gui.components

Source Code of org.ugate.gui.components.DisplayShelf

package org.ugate.gui.components;

import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;

import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.scene.CacheHint;
import javafx.scene.Group;
import javafx.scene.control.IndexRange;
import javafx.scene.control.ScrollBar;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.util.Duration;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;

/**
* Display for viewing images in a book shelf type manner.
*/
public class DisplayShelf extends Region {

  public static final int TOOLBAR_POSITION_NONE = DisplayShelfImage.TOOLBAR_POSITION_NONE;
  public static final int TOOLBAR_POSITION_TOP = DisplayShelfImage.TOOLBAR_POSITION_TOP;
  public static final int TOOLBAR_POSITION_BOTTOM = DisplayShelfImage.TOOLBAR_POSITION_BOTTOM;
    private static final Duration DURATION = Duration.millis(500);
    private static final Interpolator INTERPOLATOR = Interpolator.EASE_BOTH;
    public static final double IDEAL_SPACING = 50;
    private static final double LEFT_OFFSET = -110;
    private static final double RIGHT_OFFSET = 110;
    private static final double SCALE_SMALL = 0.7;
    private final IntegerProperty selectedImageIndexProperty = new SimpleIntegerProperty();
  private ObservableList<DisplayShelfImage> items = FXCollections.observableArrayList();
    private Group centered = new Group();
    private Group left = new Group();
    private Group center = new Group();
    private Group right = new Group();
    private int centerIndex = 0;
    private Timeline timeline;
    private ScrollBar scrollBar = new ScrollBar();
    private int imagesPerLoad = 5;
    private boolean localChange = false;
    private boolean flipOnNav = false;
    private File imageDirectory;
    private File[] files;
    public final double imageWidth;
    public final double imageHeight;
    public final double reflectionSize;
    public final double displayAngle;
    private double spacing;
    public final int toolBarPosition;
    public final String imageFullSizeToolTip;
   
    /**
     * Constructor using a file directory where all image files within the immediate directory will be displayed.
     * When the number of image files exceeds an internal threshold they will be loaded as the user navigates
     * to the end of the list (to increase performance).
     *
     * @param imageDirectory
     * @param imageWidth the width of the images used in the display
     * @param imageHeight the height of the images used in the display
     * @param reflectionSize the size of reflection
     * @param displayAngle the angle of images that are not in the center viewing area
     * @param spacing the spacing between images
     * @param toolBarPosition the position of the tool bar that shows the image information
     * @param imageFullSizeToolTip the tool tip for viewing image full size
     */
    public DisplayShelf(final File imageDirectory, final double imageWidth, final double imageHeight, final double reflectionSize,
        final double displayAngle, final double spacing, final int toolBarPosition, final String imageFullSizeToolTip) {
      this.imageFullSizeToolTip = imageFullSizeToolTip;
      this.imageDirectory = imageDirectory;
      this.imageWidth = imageWidth;
      this.imageHeight = imageHeight;
      this.reflectionSize = reflectionSize;
      this.displayAngle = displayAngle;
      this.spacing = spacing;
      this.toolBarPosition = toolBarPosition;
        init(null);
    }

    /**
     * Constructor using predefined images
     *
     * @param images the images to display
     * @param imageWidth the width of the images used in the display
     * @param imageHeight the height of the images used in the display
     * @param reflectionSize the size of reflection
     * @param displayAngle the angle of images that are not in the center viewing area
     * @param spacing the spacing between images
     * @param toolBarPosition the position of the tool bar that shows the image information
     * @param imageFullSizeToolTip the tool tip for viewing image full size
     */
    public DisplayShelf(final Image[] images, final double imageWidth, final double imageHeight, final double reflectionSize,
        final double displayAngle, final double spacing, final int toolBarPosition, final String imageFullSizeToolTip) {
      this.imageFullSizeToolTip = imageFullSizeToolTip;
      this.imageDirectory = null;
      this.imageWidth = imageWidth;
      this.imageHeight = imageHeight;
      this.reflectionSize = reflectionSize;
      this.displayAngle = displayAngle;
      this.spacing = spacing;
      this.toolBarPosition = toolBarPosition;
      init(images);
    }
   
    /**
     * Initializes the content of the component
     *
     * @param images the images to add as image items for (null if an image directory has been defined)
     */
    protected void init(final Image[] images) {
      setCache(true);
      setCacheHint(CacheHint.SCALE_AND_ROTATE);
      setMinSize(imageWidth * 2, imageHeight);
        getStyleClass().add("displayshelf");
        // create items
        if (images != null) {
          setImageItems(images);
        } else {
          updateFiles();
          setImageItems(null);
        }
        // setup scroll bar
        items.addListener(new InvalidationListener() {
      @Override
      public void invalidated(Observable observable) {
        scrollBar.setMax(items.size() - 1);
      }
    });
        scrollBar.setMax(items.size() - 1);
        scrollBar.setVisibleAmount(1);
        scrollBar.setUnitIncrement(1);
        scrollBar.setBlockIncrement(1);
        scrollBar.valueProperty().addListener(new InvalidationListener() {
            public void invalidated(Observable ov) {
                if (!localChange) {
                  shiftToCenter(items.get((int)scrollBar.getValue()));
                }
            }
        });
        // create content
        centered.getChildren().addAll(left, right, center);
        getChildren().addAll(centered, scrollBar);
        // listen for keyboard events
        setFocusTraversable(true);
        setOnKeyPressed(new EventHandler<KeyEvent>() {
            public void handle(KeyEvent ke) {
                if (ke.getCode() == KeyCode.LEFT) {
                    shift(1);
                    localChange = true;
                    scrollBar.setValue(centerIndex);
                    localChange = false;
                } else if (ke.getCode() == KeyCode.RIGHT) {
                    shift(-1);
                    localChange = true;
                    scrollBar.setValue(centerIndex);
                    localChange = false;
                }
            }
        });
        // update
        update(null);
    }
   
    /**
     * Creates the image items from the image files (up to max) or the passed images themselves.
     *
     * @param files the files to add image items for (null if images are supplied)
     * @param images the images to add as image items for (null if files are supplied)
     */
    protected void setImageItems(final Image[] images) {
        items.clear();
        int itemCount = images == null ? files.length : images.length;
        if (itemCount > imagesPerLoad) {
          itemCount = imagesPerLoad;
        }
        for (int i=0; i<itemCount; i++) {
          addItem(images == null ? files[i] : null, images != null ? images[i] : null, i);
        }
    }
   
    /**
     * Adds a image item
     *
     * @param file the file to add (if image is null) or as a reference (if image is not null)
     * @param image the image to add (null if using the file to create the image)
     * @param index the index to add the image item at
     * @return the created image item
     */
    protected DisplayShelfImage addItem(final File file, final Image image, final int index) {
        final DisplayShelfImage item = image != null
      new DisplayShelfImage(null, image, reflectionSize, displayAngle,
          toolBarPosition, imageWidth, imageHeight, false, false, imageFullSizeToolTip) :
            new DisplayShelfImage(file, null, reflectionSize, displayAngle,
                toolBarPosition, imageWidth, imageHeight, false, false, imageFullSizeToolTip);
        items.add(item);
        item.setOnMouseClicked(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent me) {
                localChange = true;
                scrollBar.setValue(index);
                localChange = false;
                shiftToCenter(item);
            }
        });
        return item;
    }
   
    /**
     * Updates the image files within the image directory (sorted by modified date- newest to oldest).
     *
     * @return the image files
     */
    protected void updateFiles() {
      //final MimetypesFileTypeMap mftm = new MimetypesFileTypeMap();
      final FileFilter imageFilter = new FileFilter() {
          @Override
            public boolean accept(final File file) {
                return !file.isDirectory() && file.canRead() && !file.isHidden() && isImage(file);
                  //mftm.getContentType(file).startsWith("image");
            }
        };imageDirectory.getAbsolutePath();
        files = imageDirectory.listFiles(imageFilter);
        Arrays.sort(files, new Comparator<File>() {
          @Override
          public int compare(final File o1, final File o2) {
            if (o1.lastModified() > o2.lastModified()) {
              return -1;
            } else if (o1.lastModified() < o2.lastModified()) {
              return +1;
            } else {
              return 0;
            }
          }
        });
    }
   
    /**
     * @param fileOrStream the {@linkplain File} or {@linkplain FileInputStream} to check
     * @return true when the file is an image
     */
    private static boolean isImage(final Object fileOrStream) {
      final String imgFormatName = getImageFormatName(fileOrStream);
      return imgFormatName != null && (imgFormatName.equals("JPEG") ||
          imgFormatName.equals("PNG") || imgFormatName.equals("BMP") ||
          imgFormatName.equals("WBMP") || imgFormatName.equals("GIF"));
    }
   
    /**
     * @param fileOrStream the {@linkplain File} or {@linkplain FileInputStream} to check
     * @return the name of the image type (null when not an image)
     */
    private static String getImageFormatName(final Object fileOrStream) {
      ImageInputStream iis = null;
        try {
            iis = ImageIO.createImageInputStream(fileOrStream);
            // Find all image readers that recognize the image format
            Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
            if (!iter.hasNext()) {
                return null;
            }
            return iter.next().getFormatName().toUpperCase();
        } catch (final IOException e) {
          // image cannot be read
        } finally {
          if (iis != null) {
            try {
          iis.close();
        } catch (final IOException e) {
          // cannot be closed
        }
          }
        }
        return null;
    }
   
    /**
     * Sets the images viewed in the display and centers the first image (newest image when using an image directory)
     *
     * @param imageDirectory the directory where the desired image files exist that will be added to
     *       the display (null if images are supplied)
     * @param images the images to add as image items for (null if an image directory is supplied)
     */
    public void setImages(final File imageDirectory, final Image[] images) {
      if (imageDirectory != null) {
        this.imageDirectory = imageDirectory;
        refreshImages();
      } else {
        setImageItems(images);
        update(null);
      }
    }
   
    /**
     * Refreshes the images viewed in the display and centers the first image (newest image when using an image directory)
     */
    public void refreshImages() {
      if (imageDirectory != null) {
        updateFiles();
        setImageItems(null);
      }
      update(null);
    }
   
    /**
     * Gets a image display item at the specified index (if exists)
     *
     * @param index the index
     * @return the image display item
     */
    public DisplayShelfImage getImageAt(final int index) {
      return index >= 0 && index < items.size()? items.get(index) : null;
    }
   
    /**
     * @return the image display item that is currently selected
     */
    public DisplayShelfImage getSelectedImage() {
      return getImageAt(centerIndex);
    }
   
    /**
     * Listens for changes to the image directory for changes to any of the images and updates the display accordingly
     */
    protected void listenForFileChanges() {
      // http://fxexperience.com/2011/07/worker-threading-in-javafx-2-0/
      /*final Service<File[]> imgDirServcice = new Service<File[]>() {
      @Override
      protected Task<File[]> createTask() {
            return new Task<File[]>() {
          @Override
          protected File[] call() throws Exception {
              final MimetypesFileTypeMap mftm = new MimetypesFileTypeMap();
                final FileFilter imageFilter = new FileFilter() {
                  @Override
                    public boolean accept(final File file) {
                        return !file.isDirectory() && file.canRead() && !file.isHidden() &&
                          mftm.getContentType(file).startsWith("image");
                    }
                };
                return imageDirectory.listFiles(imageFilter);
          }
        };
      }
    };
    imgDirServcice.stateProperty().addListener(new ChangeListener<State>() {
      @Override
      public void changed(ObservableValue<? extends State> observable, State oldValue, State newValue) {
        if (newValue == State.SUCCEEDED) {
          update();
        }
      }
    });*/

      /* JDK 7 can listen for file changes in a directory...
      final Path path = file.toPath();
      WatchService ws = path.getFileSystem().newWatchService();
      path.register(ws, StandardWatchEventKind.ENTRY_CREATE, StandardWatchEventKind.ENTRY_DELETE, StandardWatchEventKind.ENTRY_MODIFY);
        WatchKey watch = null;
        while (true) {
            try {
                watch = ws.take();
            } catch (InterruptedException ex) {
                // continue to watch
            }
            List<WatchEvent<?>> events = watch.pollEvents();
            watch.reset();
            for (WatchEvent<?> event : events) {
                Kind<Path> kind = (Kind<Path>) event.kind();
                Path context = (Path) event.context();
                if (kind.equals(StandardWatchEventKind.ENTRY_CREATE)) {
                    System.out.println("Created: " + context.getFileName());
                } else if (kind.equals(StandardWatchEventKind.ENTRY_DELETE)) {
                    System.out.println("Deleted: " + context.getFileName());
                } else if (kind.equals(StandardWatchEventKind.ENTRY_MODIFY)) {
                    System.out.println("Modified: " + context.getFileName());
                }
            }
        }*/
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected void layoutChildren() {
        // keep centered centered
        centered.setLayoutY((getHeight() - imageHeight) / 2);
        centered.setLayoutX((getWidth() - imageWidth) / 2);
        // position scroll bar at bottom
        scrollBar.setLayoutX(10);
        scrollBar.setLayoutY(getHeight() - 25);
        scrollBar.resize(getWidth() - 20, 15);
    }

    /**
     * Updates the image items and corresponding animation positions. Each group will be updated with new members
     * based upon the center index
     */
    protected void update(final IndexRange excludeInAnimation) {
        // reset the time line and clear the children
        if (timeline!=null) timeline.stop();
        timeline = new Timeline();
        left.getChildren().clear();
        center.getChildren().clear();
        right.getChildren().clear();
        if (items.size() <= 0) {
          return;
        }
        final ObservableList<KeyFrame> keyFrames = timeline.getKeyFrames();
        // add left items
        for (int i = 0; i < centerIndex; i++) {
          final DisplayShelfImage it = items.get(i);
          left.getChildren().add(it);
            it.getToolBar().setVisible(false);
            double newX = -centerIndex * spacing + spacing * i + LEFT_OFFSET;
            updateItemPositon(keyFrames, it, newX, displayAngle, SCALE_SMALL, DURATION, excludeInAnimation, i);
        }
        // add center item
        center.getChildren().add(items.get(centerIndex));
        final DisplayShelfImage centerItem = items.get(centerIndex);
        centerItem.getToolBar().setVisible(true);
        updateItemPositon(keyFrames, centerItem, 0, 90.0, 1.0, DURATION, excludeInAnimation, centerIndex);
        // add right items
        final int rightCount = items.size() - centerIndex;
        for (int i = items.size() - 1; i > centerIndex; i--) {
            final DisplayShelfImage it = items.get(i);
            right.getChildren().add(it);
            it.getToolBar().setVisible(false);
            final double newX = rightCount * spacing - spacing * (items.size() - i) + RIGHT_OFFSET;
            final double newAngle = (displayAngle * - 1.0) + (getFlipOnNav() ? 0 : 180.0);
            updateItemPositon(keyFrames, it, newX, newAngle, SCALE_SMALL, DURATION, excludeInAnimation, i);
        }
        // play animation
        timeline.play();
    }
   
    /**
     * Updates the items position
     *
     * @param keyFrames the key frames to add to
     * @param item the image item to position
     * @param newX the new X coordinate
     * @param newAngle the new angle
     * @param newScale the new scale
     * @param duration the new duration
     * @param excludeInAnimation the range of indexes to exclude from animation
     * @param index the index of the item
     */
    protected void updateItemPositon(final ObservableList<KeyFrame> keyFrames, final DisplayShelfImage item, double newX,
        final double newAngle, final double newScale, final Duration duration, final IndexRange excludeInAnimation, final int index) {
        if (excludeInAnimation != null && index >= excludeInAnimation.getStart() &&
            index <= excludeInAnimation.getEnd()) {
          item.setTranslateX(newX);
          item.scaleXProperty().set(newScale);
          item.scaleYProperty().set(newScale);
          item.angle.set(newAngle);
        } else {
            keyFrames.add(new KeyFrame(duration,
                    new KeyValue(item.translateXProperty(), newX, INTERPOLATOR),
                    new KeyValue(item.scaleXProperty(), newScale, INTERPOLATOR),
                    new KeyValue(item.scaleYProperty(), newScale, INTERPOLATOR),
                    new KeyValue(item.angle, newAngle, INTERPOLATOR)));
        }
    }

    /**
     * Shifts an image item to the center viewing area
     *
     * @param item the item to center in the view
     */
    private void shiftToCenter(final DisplayShelfImage item) {
        if (center.getChildren().get(0) == item) {
            return;
        }
        for (int i = 0; i < left.getChildren().size(); i++) {
            if (left.getChildren().get(i) == item) {
                int shiftAmount = left.getChildren().size() - i;
                // positive shift indicates navigation to the left
                shift(shiftAmount);
                return;
            }
        }
        for (int i = 0; i < right.getChildren().size(); i++) {
            if (right.getChildren().get(i) == item) {
                int shiftAmount = -(right.getChildren().size() - i);
                // negative shift indicates navigation to the right
                shift(shiftAmount);
                return;
            }
        }
    }

    /**
     * Shifts the image items (negative shift amount) or right (positive shift amount)
     *
     * @param shiftAmount the amount of the shift
     */
    public void shift(final int shiftAmount) {
        if (centerIndex <= 0 && shiftAmount > 0) return;
        if (centerIndex >= items.size() - 1 && shiftAmount < 0) return;
        centerIndex -= shiftAmount;
        IndexRange newItemRange = null;
        if (files != null && centerIndex < (files.length - 1) && centerIndex > (items.size() - 3)) {
          final int startIndex = items.size();
          for (int i=startIndex; i<(startIndex + imagesPerLoad); i++) {
            if (i >= files.length) {
              break;
            }
            addItem(files[i], null, items.size() - 1);
          }
          if (startIndex < (items.size() - 1)) {
            newItemRange = new IndexRange(startIndex, items.size() - 1);
          }
        }
        update(newItemRange);
        selectedImageIndexProperty.set(centerIndex);
    }
   
    /**
     * @return true to flip the image while transitioning between images
     */
    public boolean getFlipOnNav() {
      return flipOnNav;
    }
    /**
     * @param flipOnNav true to flip the image while transitioning between images
     */
    public void setFlipOnNav(boolean flipOnNav) {
      this.flipOnNav = flipOnNav;
    }
   
    /**
     * @return the selected image index property
     */
    public IntegerProperty getSelectedImageIndexProperty() {
    return selectedImageIndexProperty;
  }

    /**
     * @return the spacing between images
     */
  public double getSpacing() {
    return spacing;
  }

  /**
   * @param spacing the spacing between images
   */
  public void setSpacing(double spacing) {
    this.spacing = spacing;
  }

  /**
   * @return the number of images to between navigation (files or urls)
   */
  public int getImagesPerLoad() {
    return imagesPerLoad;
  }

  /**
   * The number of images to between navigation (files or urls)
   *
   * @param imagesPerLoad the number of images per load
   */
  public void setImagesPerLoad(int imagesPerLoad) {
    this.imagesPerLoad = imagesPerLoad;
  }
}
TOP

Related Classes of org.ugate.gui.components.DisplayShelf

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.