diff --git a/viewer/viewer-fx/README.md b/viewer/viewer-fx/README.md new file mode 100644 index 000000000..c60d1745f --- /dev/null +++ b/viewer/viewer-fx/README.md @@ -0,0 +1 @@ +mvn javafx:run -f ./viewer/viewer-fx/pom.xml diff --git a/viewer/viewer-fx/build.gradle b/viewer/viewer-fx/build.gradle index 5e3bdbacd..a8c81f352 100644 --- a/viewer/viewer-fx/build.gradle +++ b/viewer/viewer-fx/build.gradle @@ -9,10 +9,10 @@ repositories { description 'ICEpdf FX viewer reference implementation project' -mainClassName = "org.icepdf.fx.viewer.Main" +mainClassName = "org.icepdf.fx.ri.viewer.Launcher" applicationDefaultJvmArgs = ["-Xms64m", "-Xmx1024m"] -def sectionName = 'com/icepdf/fx/viewer' +def sectionName = 'org/icepdf/fx/ri/viewer' def baseJarName = 'icepdf' def baseAppendixName = 'viewer-fx' diff --git a/viewer/viewer-fx/pom.xml b/viewer/viewer-fx/pom.xml index 80b1b4340..74dc25e7d 100644 --- a/viewer/viewer-fx/pom.xml +++ b/viewer/viewer-fx/pom.xml @@ -19,6 +19,11 @@ javafx-controls 11 + + org.openjfx + javafx-swing + 11 + com.github.pcorless.icepdf icepdf-core @@ -43,7 +48,7 @@ javafx-maven-plugin 0.0.8 - org.icepdf.qa.viewer.Launcher + org.icepdf.fx.ri.viewer.Launcher diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/core/util/FontPropertiesManager.java b/viewer/viewer-fx/src/main/java/com/icepdf/core/util/FontPropertiesManager.java deleted file mode 100644 index ef9bce157..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/core/util/FontPropertiesManager.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.icepdf.core.util; - -import org.icepdf.core.pobjects.fonts.FontManager; - -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.BackingStoreException; -import java.util.prefs.Preferences; - -/** - * \ - */ -public class FontPropertiesManager { - - private static final Logger logger = Logger.getLogger(FontPropertiesManager.class.toString()); - - private static Preferences prefs = Preferences.userNodeForPackage(FontPropertiesManager.class); - - private static FontPropertiesManager fontPropertiesManager; - - private static FontManager fontManager = FontManager.getInstance(); - - private FontPropertiesManager() { - - } - - public static FontPropertiesManager getInstance() { - if (fontPropertiesManager == null) { - fontPropertiesManager = new FontPropertiesManager(); - } - return fontPropertiesManager; - } - - public void readDefaultProperties(String... paths) { - try { - fontManager.readSystemFonts(paths); - } catch (Exception e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, "Error reading system fonts path: ", e); - } - } - } - - public void readFontProperties(String... paths) { - try { - // If you application needs to look at other font directories - // they can be added via the readSystemFonts method. - fontManager.readFonts(paths); - } catch (Exception e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, "Error reading system paths:", e); - } - } - } - - public void loadProperties() { - fontManager.setFontProperties(prefs); - } - - public void clearProperties() { - try { - prefs.clear(); - } catch (BackingStoreException e) { - if (logger.isLoggable(Level.WARNING)) { - logger.log(Level.WARNING, "Error reading system paths:", e); - } - } - } - - public void updateProperties() { - Properties fontProps = fontManager.getFontProperties(); - for (Object key : fontProps.keySet()) { - prefs.put((String) key, fontProps.getProperty((String) key)); - } - } - - public boolean isPropertiesEmpty() { - try { - return prefs.keys().length == 0; - } catch (BackingStoreException e) { - e.printStackTrace(); - } - return false; - } - - - public static FontManager getFontManager() { - return FontManager.getInstance(); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/core/util/PropertiesManager.java b/viewer/viewer-fx/src/main/java/com/icepdf/core/util/PropertiesManager.java deleted file mode 100644 index e7d13d15d..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/core/util/PropertiesManager.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.icepdf.core.util; - -import java.util.logging.Logger; - -/** - * - */ -public class PropertiesManager { - - private static final Logger logger = Logger.getLogger(PropertiesManager.class.toString()); - - public static final String DEFAULT_MESSAGE_BUNDLE = "com.icepdf.fx.resources.MessageBundle"; - public static final String DEFAULT_SPLASH_CSS = "/com/icepdf/fx/css/splash.css"; - - private static PropertiesManager propertiesManager; - - private PropertiesManager() { - } - - public static PropertiesManager getInstance() { - if (propertiesManager == null) { - propertiesManager = new PropertiesManager(); - } - return propertiesManager; - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/DocumentView.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/DocumentView.java deleted file mode 100644 index b6a729e22..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/DocumentView.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.icepdf.fx.scene.control; - -import com.icepdf.fx.scene.control.skin.DocumentViewSkin; -import javafx.beans.property.DoubleProperty; -import javafx.beans.property.ObjectProperty; -import javafx.beans.property.SimpleDoubleProperty; -import javafx.beans.property.SimpleObjectProperty; -import javafx.scene.control.Control; -import javafx.scene.control.ScrollBar; -import javafx.scene.control.Skin; -import javafx.scene.paint.Color; -import org.icepdf.core.pobjects.Document; - -/** - * - */ -public class DocumentView extends Control { - - private enum PageViewMode {SINGLE_PAGE, SINGLE_COLUMN, DOUBLE_PAGE, DOUBLE_COLUMN} - - ; - - private DoubleProperty scaleIncrementValue; - private DoubleProperty scaleMaxValue; - private DoubleProperty scaleMinValue; - - private int currentPageIndex; - - private DoubleProperty scale; - private DoubleProperty rotation; - private ObjectProperty document; - - // viewport - private ScrollBar hbar; - - private ObjectProperty backgroundFill; - - private int numberOfPages; - - // todo Document insertion. - public DocumentView(Document document) { - this.numberOfPages = document.getNumberOfPages(); - this.document = new SimpleObjectProperty<>(document); - scale = new SimpleDoubleProperty(1); - rotation = new SimpleDoubleProperty(0); - backgroundFill = new SimpleObjectProperty<>(Color.LIGHTGRAY); - - // mouse wheel and touch zoom level - scaleIncrementValue = new SimpleDoubleProperty(0.05); - scaleMaxValue = new SimpleDoubleProperty(8); - scaleMinValue = new SimpleDoubleProperty(0.05); - } - - @Override - protected Skin createDefaultSkin() { - return new DocumentViewSkin(this); - } - - public double getScale() { - return scale.get(); - } - - public DoubleProperty scaleProperty() { - return scale; - } - - public void setScale(double scale) { - this.scale.set(scale); - } - - public double getRotation() { - return rotation.get(); - } - - public DoubleProperty rotationProperty() { - return rotation; - } - - public void setRotation(double rotation) { - this.rotation.set(rotation); - } - - public Color getBackgroundFill() { - return backgroundFill.get(); - } - - public ObjectProperty backgroundFillProperty() { - return backgroundFill; - } - - public void setBackgroundFill(Color backgroundFill) { - this.backgroundFill.set(backgroundFill); - } - - public int getNumberOfPages() { - return numberOfPages; - } - - public void setNumberOfPages(int numberOfPages) { - this.numberOfPages = numberOfPages; - } - - public double getScaleIncrementValue() { - return scaleIncrementValue.get(); - } - - public DoubleProperty scaleIncrementValueProperty() { - return scaleIncrementValue; - } - - public void setScaleIncrementValue(double scaleIncrementValue) { - this.scaleIncrementValue.set(scaleIncrementValue); - } - - public double getScaleMaxValue() { - return scaleMaxValue.get(); - } - - public DoubleProperty scaleMaxValueProperty() { - return scaleMaxValue; - } - - public void setScaleMaxValue(double scaleMaxValue) { - this.scaleMaxValue.set(scaleMaxValue); - } - - public double getScaleMinValue() { - return scaleMinValue.get(); - } - - public DoubleProperty scaleMinValueProperty() { - return scaleMinValue; - } - - public void setScaleMinValue(double scaleMinValue) { - this.scaleMinValue.set(scaleMinValue); - } - - public Document getDocument() { - return document.get(); - } - - public ObjectProperty documentProperty() { - return document; - } - - public void setDocument(Document document) { - this.document.set(document); - } - - public ScrollBar getHbar() { - return hbar; - } - - public void setHbar(ScrollBar hbar) { - this.hbar = hbar; - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/PageView.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/PageView.java deleted file mode 100644 index 4bef5ba60..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/PageView.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.icepdf.fx.scene.control; - -import com.icepdf.fx.scene.control.skin.PageViewSkin; -import javafx.beans.property.*; -import javafx.geometry.BoundingBox; -import javafx.geometry.Bounds; -import javafx.scene.control.Control; -import javafx.scene.control.Skin; -import javafx.scene.paint.Color; - -import java.util.Random; - -/** - * - */ -public class PageView extends Control { - - private DoubleProperty scale; - private DoubleProperty rotation; - private ObjectProperty documentView; - private ObjectProperty clipBounds; - private ObjectProperty pageBounds; - - private ReadOnlyIntegerWrapper index = new ReadOnlyIntegerWrapper(this, "index", -1); - - private ObjectProperty backgroundFill; - - private static Random random = new Random(); - - public PageView() { - scale = new SimpleDoubleProperty(1); - rotation = new SimpleDoubleProperty(0); - documentView = new SimpleObjectProperty<>(); - backgroundFill = new SimpleObjectProperty<>(Color.WHITE); - clipBounds = new SimpleObjectProperty<>(new BoundingBox(0, 0, 0, 0)); - pageBounds = new SimpleObjectProperty<>(new BoundingBox(0, 0, 0, 0)); - - } - - - @Override - protected Skin createDefaultSkin() { - return new PageViewSkin(this); - } - - public double getScale() { - return scale.get(); - } - - public DoubleProperty scaleProperty() { - return scale; - } - - public void setScale(double scale) { - this.scale.set(scale); - } - - public void setScale(float scale) { - this.scale.set(scale); - } - - public double getRotation() { - return rotation.get(); - } - - public DoubleProperty rotationProperty() { - return rotation; - } - - public void setRotation(double rotation) { - this.rotation.set(rotation); - } - - - public Color getBackgroundFill() { - return backgroundFill.get(); - } - - public ObjectProperty backgroundFillProperty() { - return backgroundFill; - } - - public void setBackgroundFill(Color backgroundFill) { - this.backgroundFill.set(backgroundFill); - } - - - public Bounds getClipBounds() { - return clipBounds.get(); - } - - public ObjectProperty clipBoundsProperty() { - return clipBounds; - } - - public Bounds getPageBounds() { - return pageBounds.get(); - } - - public ObjectProperty pageBoundsProperty() { - return pageBounds; - } - - public final int getIndex() { - return index.get(); - } - - public ReadOnlyIntegerWrapper indexProperty() { - return index; - } - - public void updateIndex(int i) { - final int oldIndex = index.get(); - index.set(i); -// indexChanged(oldIndex, i); - } - - public DocumentView getDocumentView() { - return documentView.get(); - } - - - public ObjectProperty documentViewProperty() { - return documentView; - } - - public void setDocumentView(DocumentView documentView) { - this.documentView.set(documentView); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/behavior/DocumentViewBehavior.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/behavior/DocumentViewBehavior.java deleted file mode 100644 index 25a10400f..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/behavior/DocumentViewBehavior.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.icepdf.fx.scene.control.behavior; - -import com.icepdf.fx.scene.control.DocumentView; -import com.sun.javafx.scene.control.behavior.BehaviorBase; -import com.sun.javafx.scene.control.behavior.KeyBinding; - -import java.util.ArrayList; -import java.util.List; - -import static javafx.scene.input.KeyCode.PAGE_DOWN; -import static javafx.scene.input.KeyCode.PAGE_UP; - -/** - * - */ -public class DocumentViewBehavior extends BehaviorBase { - - protected static final List DOCUMENT_VIEW_BINDINGS = new ArrayList(); - - static { - DOCUMENT_VIEW_BINDINGS.add(new KeyBinding(PAGE_UP, "ScrollUp")); - DOCUMENT_VIEW_BINDINGS.add(new KeyBinding(PAGE_DOWN, "ScrollDown")); - } - - public DocumentViewBehavior(DocumentView control) { - super(control, DOCUMENT_VIEW_BINDINGS); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/DocumentViewSkin.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/DocumentViewSkin.java deleted file mode 100644 index d06981cdb..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/DocumentViewSkin.java +++ /dev/null @@ -1,269 +0,0 @@ -package com.icepdf.fx.scene.control.skin; - -import com.icepdf.fx.scene.control.DocumentView; -import com.icepdf.fx.scene.control.PageView; -import com.icepdf.fx.scene.control.behavior.DocumentViewBehavior; -import javafx.geometry.Dimension2D; -import javafx.geometry.Insets; -import javafx.scene.Node; -import javafx.scene.control.Label; -import javafx.scene.effect.DropShadow; -import javafx.scene.image.Image; -import javafx.scene.layout.StackPane; -import javafx.scene.paint.Color; -import org.icepdf.core.pobjects.Document; -import org.icepdf.core.pobjects.PDimension; - -import java.lang.ref.SoftReference; -import java.util.HashMap; - -/** - * - */ -public class DocumentViewSkin extends VirtualContainerBase { - - private HashMap> imageCaptureCache = new HashMap<>(25); - - private StackPane placeholderRegion; - private Node placeholderNode; - - private int itemCount = -1; - - private boolean needCellsRebuilt = true; - private boolean needCellsReconfigured = false; - - private Dimension2D defaultDimension; - - private DropShadow dropShadow; - - public DocumentViewSkin(DocumentView documentView) { - super(documentView, new DocumentViewBehavior(documentView)); - - updateListViewItems(); - - flow.setId("virtual-flow"); - flow.setPannable(true); - flow.setVertical(true); - flow.setCreateCell(flow1 -> DocumentViewSkin.this.createCell()); - getChildren().add(flow); - - updatePageCount(); - - // page shadow reuse. - dropShadow = new DropShadow(); - dropShadow.setRadius(5.0); - dropShadow.setOffsetX(3.0); - dropShadow.setOffsetY(3.0); - dropShadow.setColor(Color.color(0.4, 0.5, 0.5)); - - - if (getSkinnable().getNumberOfPages() > 0) { - // find page size so we can apply a default size. - Document document = getSkinnable().getDocument(); - PDimension dim = document.getPageDimension(0, (float) getSkinnable().getRotation(), - (float) getSkinnable().getScale()); - defaultDimension = new Dimension2D(dim.getWidth(), dim.getHeight()); - } -// flow.setOnScroll(t -> { -// // todo centering point -// double x = t.getX(); -// double y = t.getY(); -// if (t.isControlDown()) { -// if (t.getDeltaY() > 0) { -// incrementScale(); -// } else { -// decrementScale(); -// } -// t.consume(); -// } -// }); - - // init the behavior 'closures' -// getBehavior().setOnFocusPreviousRow(() -> { onFocusPreviousCell(); }); -// getBehavior().setOnFocusNextRow(() -> { onFocusNextCell(); }); -// getBehavior().setOnMoveToFirstCell(() -> { onMoveToFirstCell(); }); -// getBehavior().setOnMoveToLastCell(() -> { onMoveToLastCell(); }); -// getBehavior().setOnScrollPageDown(isFocusDriven -> onScrollPageDown(isFocusDriven)); -// getBehavior().setOnScrollPageUp(isFocusDriven -> onScrollPageUp(isFocusDriven)); -// getBehavior().setOnSelectPreviousRow(() -> { onSelectPreviousCell(); }); -// getBehavior().setOnSelectNextRow(() -> { onSelectNextCell(); }); - -// documentView.itemsProperty().addListener(new WeakInvalidationListener(itemsChangeListener)); - } - - private void incrementScale() { - DocumentView documentView = getSkinnable(); - double scale = documentView.getScale() + documentView.getScaleIncrementValue(); - if (scale <= documentView.getScaleMaxValue()) { - documentView.setScale(scale); - } - } - - private void decrementScale() { - DocumentView documentView = getSkinnable(); - double scale = documentView.getScale() - documentView.getScaleIncrementValue(); - if (scale >= documentView.getScaleMinValue()) { - documentView.setScale(scale); - } - } - - @Override - protected void handleControlPropertyChanged(String p) { - super.handleControlPropertyChanged(p); - if ("ITEMS".equals(p)) { - updateListViewItems(); - } else if ("ORIENTATION".equals(p)) { -// flow.setVertical(getSkinnable().getOrientation() == Orientation.VERTICAL); - } else if ("CELL_FACTORY".equals(p)) { - flow.recreateCells(); - } else if ("PARENT".equals(p)) { - if (getSkinnable().getParent() != null && getSkinnable().isVisible()) { - getSkinnable().requestLayout(); - } - } else if ("PLACEHOLDER".equals(p)) { - updatePlaceholderRegionVisibility(); - } -// else if ("FIXED_CELL_SIZE".equals(p)) { -// flow.setFixedCellSize(getSkinnable().getFixedCellSize()); -// } - } -// -// private MapChangeListener propertiesMapListener = c -> { -// if (! c.wasAdded()) return; -// if (RECREATE.equals(c.getKey())) { -// needCellsRebuilt = true; -// getSkinnable().requestLayout(); -// getSkinnable().getProperties().remove(RECREATE); -// } -// }; - - - public void updateListViewItems() { - - pageCountDirty = true; - getSkinnable().requestLayout(); - } - - @Override - public int getPageCount() { - return itemCount; - } - - @Override - protected void updatePageCount() { - if (flow == null) return; - - Document document = getSkinnable().getDocument(); - - int oldCount = itemCount; - int newCount = document == null ? 0 : document.getNumberOfPages(); - - itemCount = newCount; - - flow.setCellCount(newCount); - - getSkinnable().setHbar(flow.getHbar()); - - updatePlaceholderRegionVisibility(); - if (newCount != oldCount) { - needCellsRebuilt = true; - } else { - needCellsReconfigured = true; - } - } - - protected final void updatePlaceholderRegionVisibility() { - boolean visible = getPageCount() == 0; - - if (visible) { - placeholderNode = null;//getSkinnable().getPlaceholder(); - if (placeholderNode == null) { - placeholderNode = new Label(); - ((Label) placeholderNode).setText("empty document"); - } - if (placeholderNode != null) { - if (placeholderRegion == null) { - placeholderRegion = new StackPane(); - placeholderRegion.getStyleClass().setAll("placeholder"); - getChildren().add(placeholderRegion); - } - placeholderRegion.getChildren().setAll(placeholderNode); - } - } - flow.setVisible(!visible); - if (placeholderRegion != null) { - placeholderRegion.setVisible(visible); - } - } - - @Override - public PageView createCell() { - PageView cell = createDefaultCellImpl(); - cell.setLayoutX(-1); - cell.setLayoutY(-1); - cell.setDocumentView(getSkinnable()); - cell.scaleProperty().bind(getSkinnable().scaleProperty()); - cell.rotationProperty().bind(getSkinnable().rotationProperty()); - cell.setPadding(new Insets(10, 20, 10, 20)); - cell.setEffect(dropShadow); - - return cell; - } - - private static PageView createDefaultCellImpl() { - // todo add back factory to build pageView or documentView - return new PageView(); - } - - @Override - protected void layoutChildren(final double x, final double y, - final double w, final double h) { - super.layoutChildren(x, y, w, h); - - if (needCellsRebuilt) { - flow.rebuildCells(); - } else if (needCellsReconfigured) { - flow.reconfigureCells(); - } - - needCellsRebuilt = false; - needCellsReconfigured = false; - - if (getPageCount() == 0) { - // show message overlay instead of empty listview - if (placeholderRegion != null) { - placeholderRegion.setVisible(w > 0 && h > 0); - placeholderRegion.resizeRelocate(x, y, w, h); - } - } else { - flow.resizeRelocate(x, y, w, h); - } - } - - @Override - protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) { - checkState(); - -// if (getPageCount() == 0) { -// if (placeholderRegion == null) { -// updatePlaceholderRegionVisibility(); -// } -// if (placeholderRegion != null) { -// return placeholderRegion.prefWidth(height) + leftInset + rightInset; -// } -// } -// -// return computePrefHeight(-1, topInset, rightInset, bottomInset, leftInset) * 0.618033987; - return defaultDimension.getWidth(); - } - - @Override - protected double computePrefHeight(double width, double topInset, double rightInset, double bottomInset, double leftInset) { - return defaultDimension.getHeight(); - } - - public HashMap> getImageCaptureCache() { - return imageCaptureCache; - } - -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/PageCaptureTask.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/PageCaptureTask.java deleted file mode 100644 index 13052d6c3..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/PageCaptureTask.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.icepdf.fx.scene.control.skin; - -import com.icepdf.fx.scene.control.DocumentView; -import javafx.application.Platform; -import javafx.concurrent.Task; -import javafx.embed.swing.SwingFXUtils; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.shape.Rectangle; -import org.icepdf.core.pobjects.Catalog; -import org.icepdf.core.pobjects.graphics.images.ImageUtility; -import org.icepdf.core.pobjects.PDimension; -import org.icepdf.core.pobjects.Page; -import org.icepdf.core.util.GraphicsRenderingHints; - -import java.awt.*; -import java.awt.image.BufferedImage; -import java.lang.ref.SoftReference; - -/** - * Created by pcorl_000 on 2017-03-31. - */ -public class PageCaptureTask extends Task { - - private ImageView pageImageView; - private DocumentView documentView; - - private javafx.scene.shape.Rectangle clip; - - private int index; - - - public PageCaptureTask(int index, ImageView pageImageView, javafx.scene.shape.Rectangle clip, DocumentView documentView) { - this.pageImageView = pageImageView; - this.index = index; - this.documentView = documentView; - this.clip = clip; - } - - @Override - protected Image call() throws Exception { - - try { - Catalog catalog = documentView.getDocument().getCatalog(); - - Page page = catalog.getPageTree().getPage(index); - page.init(); - //todo make pageBounder a property - PDimension sz = page.getSize(Page.BOUNDARY_MEDIABOX, 0, (float) documentView.getScale()); - - int pageWidth = (int) sz.getWidth(); - int pageHeight = (int) sz.getHeight(); - if (clip != null && clip.getWidth() > 0) { - pageWidth = (int) Math.round(clip.getWidth()); - pageHeight = (int) Math.round(clip.getHeight()); - } - - if (isCancelled()) { - return null; - } - - BufferedImage image = ImageUtility.createCompatibleImage(pageWidth, pageHeight); -// System.out.println(pageWidth + " " + pageHeight); - Graphics g = image.createGraphics(); - g.translate((int) -clip.getX(), (int) -clip.getY()); - page.paint(g, GraphicsRenderingHints.SCREEN, Page.BOUNDARY_CROPBOX, 0, (float) documentView.getScale()); - g.dispose(); - - if (isCancelled()) { - return null; - } - - final Image pageImage = SwingFXUtils.toFXImage(image, null); - - if (!isCancelled()) { - DocumentViewSkin documentViewSkin = (DocumentViewSkin) documentView.getSkin(); - documentViewSkin.getImageCaptureCache().put(index, new SoftReference<>(pageImage)); - Platform.runLater(() -> { - // update the location - pageImageView.relocate(clip.getX(), clip.getY()); -// pageImageView.setScaleX(1); -// pageImageView.setScaleY(1); -// pageImageView.setTranslateX(0); -// pageImageView.setTranslateY(0); - pageImageView.setImage(pageImage); - pageImageView.setFitWidth(clip.getWidth()); - pageImageView.setClip(new Rectangle(0, 0, clip.getWidth(), clip.getHeight())); - // make sure it's visible. - pageImageView.setVisible(true); - }); - } - - return pageImage; - } catch (InterruptedException e) { -// e.printStackTrace(); - } - - return null; - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/PageViewSkin.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/PageViewSkin.java deleted file mode 100644 index 06e93a8d8..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/PageViewSkin.java +++ /dev/null @@ -1,319 +0,0 @@ -package com.icepdf.fx.scene.control.skin; - -import com.icepdf.fx.scene.control.DocumentView; -import com.icepdf.fx.scene.control.PageView; -import javafx.concurrent.Task; -import javafx.geometry.Bounds; -import javafx.geometry.HPos; -import javafx.geometry.Point2D; -import javafx.geometry.VPos; -import javafx.scene.Group; -import javafx.scene.control.SkinBase; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.Region; -import javafx.scene.paint.Color; -import javafx.scene.shape.*; -import org.icepdf.core.pobjects.Document; -import org.icepdf.core.pobjects.PDimension; -import org.icepdf.core.util.Library; - -import java.lang.ref.SoftReference; - -/** - * - */ -public class PageViewSkin extends SkinBase { - - // default for a4 is 595 X 842, letter, 595 x 792 - public static final double WIDTH = 595; - public static final double HEIGHT = 842; - - private double width = WIDTH; - private double height = HEIGHT; - - private double xViewport = -1; - private double yViewport = -1; - - private Path pageOutline; - private Group pageGroup; - private ImageView pageImageView; - private Rectangle clipRectangle; - private Point2D oldImageView; - - private PageCaptureTask pageCaptureTask; - - private boolean invalidPage = true; - - public PageViewSkin(PageView control) { - super(control); - // match min and max to preferred - getSkinnable().setMaxWidth(Region.USE_PREF_SIZE); - getSkinnable().setMaxHeight(Region.USE_PREF_SIZE); - getSkinnable().setMinWidth(Region.USE_PREF_SIZE); - getSkinnable().setMinHeight(Region.USE_PREF_SIZE); - - // todo remove. - control.backgroundFillProperty().addListener(observable -> { - updatePageColour(); - }); - - control.scaleProperty().addListener((observable, oldValue, newValue) -> { - updateClipRectangle(); - scalePreviousRendering(oldValue.doubleValue(), newValue.doubleValue()); - updatePageZoom(); - }); - control.rotationProperty().addListener(observable -> { - updateClipRectangle(); - updatePageRotation(); - }); - - control.indexProperty().addListener((observable, oldValue, newValue) -> { - updateClipRectangle(); - updatePageRendering(); - }); - - // listen for x:y changes so we can recalculate the page image clip - getSkinnable().layoutYProperty().addListener((observable, oldValue, newValue) -> { - updateClipRectangle(); - updatePageRendering(); - }); - - getSkinnable().getDocumentView().getHbar().valueProperty().addListener((observable, oldValue, newValue) -> { - updateClipRectangle(); - updatePageRendering(); - }); - - getSkinnable().getDocumentView().widthProperty().addListener((observable, oldValue, newValue) -> { - updateClipRectangle(); - updatePageRendering(); - }); - - getSkinnable().getDocumentView().heightProperty().addListener((observable, oldValue, newValue) -> { - updateClipRectangle(); - updatePageRendering(); - }); - - } - - private void initialize() { - - if (pageGroup != null) { - pageGroup.getChildren().clear(); - } else { - pageGroup = new Group(); - getChildren().add(pageGroup); - } - int index = getSkinnable().getIndex(); - Document document = getSkinnable().getDocumentView().getDocument(); - PDimension dimension = document.getPageDimension(index, 0,//(float) getSkinnable().getRotation(), - (float) getSkinnable().getScale()); - pageOutline = new Path(); - pageOutline.getElements().add(new MoveTo(0, 0)); - pageOutline.getElements().add(new LineTo(dimension.getWidth(), 0)); - pageOutline.getElements().add(new LineTo(dimension.getWidth(), dimension.getHeight())); - pageOutline.getElements().add(new LineTo(0, dimension.getHeight())); - pageOutline.getElements().add(new ClosePath()); - pageOutline.setStroke(Color.BLACK); - pageOutline.setFill(Color.WHITE); - - if (pageImageView == null) { - pageImageView = new ImageView(); - pageImageView.setPreserveRatio(true); - } - oldImageView = new Point2D(pageImageView.getLayoutX(), pageImageView.getLayoutY()); - - if (clipRectangle == null) { - clipRectangle = new Rectangle(0, 0, dimension.getWidth(), dimension.getHeight()); - } - // trim rectangle to view port size. - updateClipRectangle(); - clipRectangle.setStroke(Color.RED); - clipRectangle.setFill(null); - updatePageRendering(); - - pageGroup.getChildren().addAll(pageOutline, pageImageView, clipRectangle); - } - - public void updatePageColour() { - if (pageOutline != null) { - pageOutline.setFill(getSkinnable().getBackgroundFill()); - } - } - - public void updatePageRotation() { - if (pageGroup != null) { - pageGroup.setRotate(getSkinnable().getRotation()); - getSkinnable().requestLayout(); - } - } - - public void updatePageZoom() { - if (pageGroup != null) { - invalidPage = true; - getSkinnable().requestLayout(); - } - } - - public void updateClipRectangle() { - DocumentView documentView = getSkinnable().getDocumentView(); - int index = getSkinnable().getIndex(); - - // if page index -1, avoid doing clip work? - if (index == -1) { - return; - } - - Bounds viewport = getNode().getParent().getParent().getLayoutBounds();//getSkinnable().getDocumentView().getBoundsInLocal(); - double scrollBarWidth = getSkinnable().getDocumentView().getHbar().getHeight(); - double x = getSkinnable().getDocumentView().getHbar().getValue(); - double y = getSkinnable().getLayoutY(); // subtract inset. - -// System.out.println(viewport); - - PDimension pDimension = documentView.getDocument().getPageDimension(index, 0,//(float) getSkinnable().getRotation(), - (float) getSkinnable().getScale()); - double pageWidth = pDimension.getWidth(); - double pageHeight = pDimension.getHeight(); - double insetTop = getSkinnable().getInsets().getTop(); - double insetLeft = getSkinnable().getInsets().getLeft(); - double viewportWidth = viewport.getWidth(); - double viewportHeight = viewport.getHeight(); - double xClip = 0; - double yClip = 0; - double clipWidth = pageWidth; - double clipHeight = pageHeight; - - // todo further work is needed here to tighten up the bounds as well as support side by side page views - if (pageWidth >= viewportWidth) { - double max = (pageWidth + insetLeft + scrollBarWidth) - viewportWidth; - if (x > insetLeft) { - xClip = x - insetLeft; - } - if (x >= max) { - clipWidth = viewportWidth - (x - max) - scrollBarWidth; - } else { - clipWidth = viewportWidth;//- scrollBarWidth; - } - - } - if (pageHeight >= viewportHeight) { - if (y < 0) { - yClip = -y - insetTop; - if (yClip < 0) yClip = 0; - clipHeight = pageHeight - yClip; - if (clipHeight > viewportHeight) { - clipHeight = viewportHeight; - } - } else { - clipHeight = viewportHeight - y; - } - } - // update our clip outline. - if (clipRectangle != null) { - clipRectangle.setX(xClip); - clipRectangle.setY(yClip); - clipRectangle.setWidth(clipWidth); - clipRectangle.setHeight(clipHeight); -// System.out.println(clipRectangle); - } - } - - private void scalePreviousRendering(double previousScale, double newScale) { -// pageImageView.setVisible(false); - if (getSkinnable().getIndex() < 0) { - return; - } - - double scaleIncrement = newScale / previousScale; //newScale - previousScale; - - -// double diff = oldImageView.getX() / oldClipRectangle.getWidth(); - -// System.out.println(clipRectangle.getX() + " " +clipRectangle.getY()); -// System.out.println(oldImageView.getX() + " " +oldImageView.getY()); -// System.out.println(); - -// pageImageView.relocate(clipRectangle.getX() * scaleIncrement, clipRectangle.getY() * scaleIncrement); - pageImageView.setFitWidth(clipRectangle.getWidth()); - } - - - /** - * Index has changed and we need to update the contents fo the cell with the new page data. - */ - public void updatePageRendering() { - - int index = getSkinnable().getIndex(); - - stopTask(); - if (index >= 0 && pageImageView != null) { -// pageImageView.setVisible(false); - DocumentView documentView = getSkinnable().getDocumentView(); - // todo cache needs to store previous x,y position, so we can make the repaint smarter - DocumentViewSkin documentViewSkin = (DocumentViewSkin) documentView.getSkin(); - SoftReference cachedImageReference = documentViewSkin.getImageCaptureCache().get(index); - if (cachedImageReference != null) { - Image cachedImage = cachedImageReference.get(); - if (cachedImage != null) { - pageImageView.setImage(cachedImage); - pageImageView.setVisible(true); -// pageImageView.setFitWidth(clipRectangle.getWidth()); - } - } - - if ((pageCaptureTask == null || !pageCaptureTask.isRunning())) { - PDimension pDimension = documentView.getDocument().getPageDimension(index, 0, (float) 1.0f); - width = pDimension.getWidth(); - height = pDimension.getHeight(); - - pageImageView.setClip(new Rectangle(0, 0, clipRectangle.getWidth(), clipRectangle.getHeight())); - - oldImageView = new Point2D(pageImageView.getLayoutX(), pageImageView.getLayoutY()); - - // we have a new page size so we can setup a paint - pageCaptureTask = new PageCaptureTask(index, pageImageView, clipRectangle, documentView); - Library.execute(pageCaptureTask); - - pageCaptureTask.setOnCancelled(t -> { - pageImageView.setVisible(false); - }); - } - } - invalidPage = true; - getSkinnable().requestLayout(); - } - - private boolean stopTask() { - if (pageCaptureTask != null) { - pageCaptureTask.cancel(); - boolean cancelled = pageCaptureTask.isCancelled() || pageCaptureTask.getState() == Task.State.READY; - pageCaptureTask = null; - return cancelled; - } - return false; - } - - - @Override - protected void layoutChildren(double contentX, double contentY, double contentWidth, double contentHeight) { - if (invalidPage) { - initialize(); - invalidPage = false; - } - layoutInArea(pageGroup, contentX, contentY, contentWidth, contentHeight, -1, - HPos.CENTER, VPos.CENTER); - } - - @Override - protected double computePrefWidth(double height, double topInset, double rightInset, - double bottomInset, double leftInset) { - return leftInset + rightInset + (width * getSkinnable().getScale()); - } - - @Override - protected double computePrefHeight(double width, double topInset, double rightInset, - double bottomInset, double leftInset) { - return topInset + bottomInset + (height * getSkinnable().getScale()); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualContainerBase.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualContainerBase.java deleted file mode 100644 index 0984f706f..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualContainerBase.java +++ /dev/null @@ -1,94 +0,0 @@ -package com.icepdf.fx.scene.control.skin; - - -import com.icepdf.fx.scene.control.PageView; -import com.sun.javafx.scene.control.behavior.BehaviorBase; -import com.sun.javafx.scene.control.skin.BehaviorSkinBase; -import javafx.scene.control.Control; -import javafx.scene.control.ScrollToEvent; - -/** - * Parent class to control skins whose contents are virtualized and scrollable. - * This class handles the interaction with the VirtualFlow class, which is the - * main class handling the virtualization of the contents of this container. - * - * @profile common - */ -public abstract class VirtualContainerBase, I extends PageView> extends BehaviorSkinBase { - - protected boolean pageCountDirty; - - public VirtualContainerBase(final C control, B behavior) { - super(control, behavior); - flow = createVirtualFlow(); - - control.addEventHandler(ScrollToEvent.scrollToTopIndex(), event -> { - // Fix for RT-24630: The row count in VirtualFlow was incorrect - // (normally zero), so the scrollTo call was misbehaving. - if (pageCountDirty) { - // update row count before we do a scroll - updatePageCount(); - pageCountDirty = false; - } - flow.scrollTo(event.getScrollTarget()); - }); - } - - /** - * The virtualized container which handles the layout and scrolling of - * all the cells. - */ - protected final VirtualPageFlow flow; - - /** - * Returns a Cell available to be used in the virtual flow. This means you - * may return either a previously used, but now unrequired cell, or alternatively - * create a new Cell instance. - *

- * Preference is obviously given to reusing cells whenever possible, to keep - * performance costs down. - */ - public abstract I createCell(); - - /** - * This enables skin subclasses to provide a custom VirtualFlow implementation, - * rather than have VirtualContainerBase instantiate the default instance. - */ - protected VirtualPageFlow createVirtualFlow() { - return new VirtualPageFlow<>(); - } - - /** - * Returns the total number of items in this container, including those - * that are currently hidden because they are out of view. - */ - public abstract int getPageCount(); - - protected abstract void updatePageCount(); - - double getMaxCellWidth(int rowsToCount) { - return snappedLeftInset() + flow.getMaxCellWidth(rowsToCount) + snappedRightInset(); - } - - double getVirtualFlowPreferredHeight(int rows) { - double height = 1.0; - - for (int i = 0; i < rows && i < getPageCount(); i++) { - height += flow.getCellLength(i); - } - - return height + snappedTopInset() + snappedBottomInset(); - } - - @Override - protected void layoutChildren(double x, double y, double w, double h) { - checkState(); - } - - protected void checkState() { - if (pageCountDirty) { - updatePageCount(); - pageCountDirty = false; - } - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualPageFlow.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualPageFlow.java deleted file mode 100644 index b2a144a55..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualPageFlow.java +++ /dev/null @@ -1,2852 +0,0 @@ -package com.icepdf.fx.scene.control.skin; - - -import com.icepdf.fx.scene.control.PageView; -import com.sun.javafx.scene.control.Logging; -import com.sun.javafx.scene.control.skin.Utils; -import com.sun.javafx.scene.traversal.Algorithm; -import com.sun.javafx.scene.traversal.Direction; -import com.sun.javafx.scene.traversal.ParentTraversalEngine; -import com.sun.javafx.scene.traversal.TraversalContext; -import javafx.animation.KeyFrame; -import javafx.animation.Timeline; -import javafx.application.ConditionalFeature; -import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.BooleanPropertyBase; -import javafx.beans.value.ChangeListener; -import javafx.collections.ObservableList; -import javafx.event.EventDispatcher; -import javafx.event.EventHandler; -import javafx.geometry.Orientation; -import javafx.scene.*; -import javafx.scene.control.ScrollBar; -import javafx.scene.input.MouseEvent; -import javafx.scene.input.ScrollEvent; -import javafx.scene.layout.Region; -import javafx.scene.layout.StackPane; -import javafx.scene.shape.Rectangle; -import javafx.util.Callback; -import javafx.util.Duration; -import sun.util.logging.PlatformLogger; - -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; - -/** - * Implementation of a virtualized container using a cell based mechanism. - */ -public class VirtualPageFlow extends Region { - - protected final static boolean IS_TOUCH_SUPPORTED = Platform.isSupported(ConditionalFeature.INPUT_TOUCH); - - /** - * Scroll events may request to scroll about a number of "lines". We first - * decide how big one "line" is - for fixed cell size it's clear, - * for variable cell size we settle on a single number so that the scrolling - * speed is consistent. Now if the line is so big that - * MIN_SCROLLING_LINES_PER_PAGE of them don't fit into one page, we make - * them smaller to prevent the scrolling step to be too big (perhaps - * even more than one page). - */ - private static final int MIN_SCROLLING_LINES_PER_PAGE = 8; - - private boolean touchDetected = false; - private boolean mouseDown = false; - - - private BooleanProperty vertical; - - public final void setVertical(boolean value) { - verticalProperty().set(value); - } - - public final boolean isVertical() { - return vertical == null ? true : vertical.get(); - } - - public final BooleanProperty verticalProperty() { - if (vertical == null) { - vertical = new BooleanPropertyBase(true) { - @Override - protected void invalidated() { - pile.clear(); - sheetChildren.clear(); - cells.clear(); - lastWidth = lastHeight = -1; - setMaxPrefBreadth(-1); - setViewportBreadth(0); - setViewportLength(0); - lastPosition = 0; - hbar.setValue(0); - vbar.setValue(0); - setPosition(0.0f); - setNeedsLayout(true); - requestLayout(); - } - - @Override - public Object getBean() { - return VirtualPageFlow.this; - } - - @Override - public String getName() { - return "vertical"; - } - }; - } - return vertical; - } - - /** - * Indicates whether the VirtualFlow viewport is capable of being panned - * by the user (either via the mouse or touch events). - */ - private boolean pannable = true; - - public boolean isPannable() { - return pannable; - } - - public void setPannable(boolean value) { - this.pannable = value; - } - - /** - * Indicates the number of cells that should be in the flow. The user of - * the VirtualFlow must set this appropriately. When the cell count changes - * the VirtualFlow responds by updating the visuals. If the items backing - * the cells change, but the count has not changed, you must call the - * reconfigureCells() function to update the visuals. - */ - private int cellCount; - - public int getCellCount() { - return cellCount; - } - - public void setCellCount(int i) { - int oldCount = cellCount; - this.cellCount = i; - - boolean countChanged = oldCount != cellCount; - - // ensure that the virtual scrollbar adjusts in size based on the current - // cell count. - if (countChanged) { - VirtualScrollBar lengthBar = isVertical() ? vbar : hbar; - lengthBar.setMax(i); - } - - // I decided *not* to reset maxPrefBreadth here for the following - // situation. Suppose I have 30 cells and then I add 10 more. Just - // because I added 10 more doesn't mean the max pref should be - // reset. Suppose the first 3 cells were extra long, and I was - // scrolled down such that they weren't visible. If I were to reset - // maxPrefBreadth when subsequent cells were added or removed, then the - // scroll bars would erroneously reset as well. So I do not reset - // the maxPrefBreadth here. - - // Fix for RT-12512, RT-14301 and RT-14864. - // Without this, the VirtualFlow length-wise scrollbar would not change - // as expected. This would leave items unable to be shown, as they - // would exist outside of the visible area, even when the scrollbar - // was at its maximum position. - // FIXME this should be only executed on the pulse, so this will likely - // lead to performance degradation until it is handled properly. - if (countChanged) { - layoutChildren(); - - // Fix for RT-13965: Without this line of code, the number of items in - // the sheet would constantly grow, leaking memory for the life of the - // application. This was especially apparent when the total number of - // cells changes - regardless of whether it became bigger or smaller. - sheetChildren.clear(); - - Parent parent = getParent(); - if (parent != null) parent.requestLayout(); - } - // TODO suppose I had 100 cells and I added 100 more. Further - // suppose I was scrolled to the bottom when that happened. I - // actually want to update the position of the mapper such that - // the view remains "stable". - } - - /** - * The position of the VirtualFlow within its list of cells. This is a value - * between 0 and 1. - */ - private double position; - - public double getPosition() { - return position; - } - - public void setPosition(double newPosition) { - boolean needsUpdate = this.position != newPosition; - this.position = com.sun.javafx.util.Utils.clamp(0, newPosition, 1); - if (needsUpdate) { - requestLayout(); - } - } - - /** - * For optimisation purposes, some use cases can trade dynamic cell length - * for speed - if fixedCellSize is greater than zero we'll use that rather - * than determine it by querying the cell itself. - */ - private double fixedCellSize = 0; - private boolean fixedCellSizeEnabled = false; - - public void setFixedCellSize(final double value) { - this.fixedCellSize = value; - this.fixedCellSizeEnabled = fixedCellSize > 0; - needsCellsLayout = true; - layoutChildren(); - } - - /** - * Callback which is invoked whenever the VirtualFlow needs a new - * IndexedCell. The VirtualFlow attempts to reuse cells whenever possible - * and only creates the minimal number of cells necessary. - */ - private Callback createCell; - - public Callback getCreateCell() { - return createCell; - } - - public void setCreateCell(Callback cc) { - this.createCell = cc; - - if (createCell != null) { - accumCell = null; - setNeedsLayout(true); - recreateCells(); - if (getParent() != null) getParent().requestLayout(); - } - } - - /** - * The maximum preferred size in the non-virtual direction. For example, - * if vertical, then this is the max pref width of all cells encountered. - *

- * In general, this is the largest preferred size in the non-virtual - * direction that we have ever encountered. We don't reduce this size - * unless instructed to do so, so as to reduce the amount of scroll bar - * jitter. The access on this variable is package ONLY FOR TESTING. - */ - private double maxPrefBreadth; - - protected final void setMaxPrefBreadth(double value) { - this.maxPrefBreadth = value; - } - - protected final double getMaxPrefBreadth() { - return maxPrefBreadth; - } - - /** - * The breadth of the viewport portion of the VirtualFlow as computed during - * the layout pass. In a vertical flow this would be the same as the clip - * view width. In a horizontal flow this is the clip view height. - * The access on this variable is package ONLY FOR TESTING. - */ - private double viewportBreadth; - - protected final void setViewportBreadth(double value) { - this.viewportBreadth = value; - } - - protected final double getViewportBreadth() { - return viewportBreadth; - } - - /** - * The length of the viewport portion of the VirtualFlow as computed - * during the layout pass. In a vertical flow this would be the same as the - * clip view height. In a horizontal flow this is the clip view width. - * The access on this variable is package ONLY FOR TESTING. - */ - private double viewportLength; - - void setViewportLength(double value) { - this.viewportLength = value; - } - - protected double getViewportLength() { - return viewportLength; - } - - - /** - * The width of the VirtualFlow the last time it was laid out. We - * use this information for several fast paths during the layout pass. - */ - double lastWidth = -1; - - /** - * The height of the VirtualFlow the last time it was laid out. We - * use this information for several fast paths during the layout pass. - */ - double lastHeight = -1; - - /** - * The number of "virtual" cells in the flow the last time it was laid out. - * For example, there may have been 1000 virtual cells, but only 20 actual - * cells created and in use. In that case, lastCellCount would be 1000. - */ - int lastCellCount = 0; - - /** - * We remember the last value for vertical the last time we laid out the - * flow. If vertical has changed, we will want to change the max & value - * for the different scroll bars. Since we do all the scroll bar update - * work in the layoutChildren function, we need to know what the old value for - * vertical was. - */ - boolean lastVertical; - - /** - * The position last time we laid out. If none of the lastXXX vars have - * changed respective to their values in layoutChildren, then we can just punt - * out of the method (I hope...) - */ - double lastPosition; - - /** - * The breadth of the first visible cell last time we laid out. - */ - double lastCellBreadth = -1; - - /** - * The length of the first visible cell last time we laid out. - */ - double lastCellLength = -1; - - /** - * The list of cells representing those cells which actually make up the - * current view. The cells are ordered such that the first cell in this - * list is the first in the view, and the last cell is the last in the - * view. When pixel scrolling, the list is simply shifted and items drop - * off the beginning or the end, depending on the order of scrolling. - *

- * This is package private ONLY FOR TESTING - */ - final ArrayLinkedList cells = new ArrayLinkedList(); - - protected List getCells() { - return cells; - } - - /** - * A structure containing cells that can be reused later. These are cells - * that at one time were needed to populate the view, but now are no longer - * needed. We keep them here until they are needed again. - *

- * This is package private ONLY FOR TESTING - */ - final ArrayLinkedList pile = new ArrayLinkedList(); - - /** - * A special cell used to accumulate bounds, such that we reduce object - * churn. This cell must be recreated whenever the cell factory function - * changes. This has package access ONLY for testing. - */ - T accumCell; - - /** - * This group is used for holding the 'accumCell'. 'accumCell' must - * be added to the skin for it to be styled. Otherwise, it doesn't - * report the correct width/height leading to issues when scrolling - * the flow - */ - Group accumCellParent; - - /** - * The group which holds the cells. - */ - final Group sheet; - - final ObservableList sheetChildren; - - /** - * The scroll bar used for scrolling horizontally. This has package access - * ONLY for testing. - */ - private VirtualScrollBar hbar = new VirtualScrollBar(this); - - protected final VirtualScrollBar getHbar() { - return hbar; - } - - /** - * The scroll bar used to scrolling vertically. This has package access - * ONLY for testing. - */ - private VirtualScrollBar vbar = new VirtualScrollBar(this); - - protected final VirtualScrollBar getVbar() { - return vbar; - } - - /** - * Control in which the cell's sheet is placed and forms the viewport. The - * viewportBreadth and viewportLength are simply the dimensions of the - * clipView. This has package access ONLY for testing. - */ - ClippedContainer clipView; - - /** - * When both the horizontal and vertical scroll bars are visible, - * we have to 'fill in' the bottom right corner where the two scroll bars - * meet. This is handled by this corner region. This has package access - * ONLY for testing. - */ - StackPane corner; - - // used for panning the virtual flow - private double lastX; - private double lastY; - private boolean isPanning = false; - - public VirtualPageFlow() { - getStyleClass().add("virtual-flow"); - setId("virtual-flow"); - - // initContent - // --- sheet - sheet = new Group(); - sheet.getStyleClass().add("sheet"); - sheet.setStyle("-fx-background-color: red"); - sheet.setAutoSizeChildren(false); - - sheetChildren = sheet.getChildren(); - - // --- clipView - clipView = new ClippedContainer(this); - clipView.setNode(sheet); - getChildren().add(clipView); - - // --- accumCellParent - accumCellParent = new Group(); - - accumCellParent.setVisible(false); - getChildren().add(accumCellParent); - - - /* - ** don't allow the ScrollBar to handle the ScrollEvent, - ** In a VirtualFlow a vertical scroll should scroll on the vertical only, - ** whereas in a horizontal ScrollBar it can scroll horizontally. - */ - // block the event from being passed down to children - final EventDispatcher blockEventDispatcher = (event, tail) -> event; - // block ScrollEvent from being passed down to scrollbar's skin - final EventDispatcher oldHsbEventDispatcher = hbar.getEventDispatcher(); - hbar.setEventDispatcher((event, tail) -> { - if (event.getEventType() == ScrollEvent.SCROLL && - !((ScrollEvent) event).isDirect()) { - tail = tail.prepend(blockEventDispatcher); - tail = tail.prepend(oldHsbEventDispatcher); - return tail.dispatchEvent(event); - } - return oldHsbEventDispatcher.dispatchEvent(event, tail); - }); - // block ScrollEvent from being passed down to scrollbar's skin - final EventDispatcher oldVsbEventDispatcher = vbar.getEventDispatcher(); - vbar.setEventDispatcher((event, tail) -> { - if (event.getEventType() == ScrollEvent.SCROLL && - !((ScrollEvent) event).isDirect()) { - tail = tail.prepend(blockEventDispatcher); - tail = tail.prepend(oldVsbEventDispatcher); - return tail.dispatchEvent(event); - } - return oldVsbEventDispatcher.dispatchEvent(event, tail); - }); - /* - ** listen for ScrollEvents over the whole of the VirtualFlow - ** area, the above dispatcher having removed the ScrollBars - ** scroll event handling. - */ - setOnScroll(new EventHandler() { - @Override - public void handle(ScrollEvent event) { - if (IS_TOUCH_SUPPORTED) { - if (touchDetected == false && mouseDown == false) { - startSBReleasedAnimation(); - } - } - /* - ** calculate the delta in the direction of the flow. - */ - double virtualDelta = 0.0; - if (isVertical()) { - switch (event.getTextDeltaYUnits()) { - case PAGES: - virtualDelta = event.getTextDeltaY() * lastHeight; - break; - case LINES: - double lineSize; -// if (fixedCellSizeEnabled) { -// lineSize = fixedCellSize; -// } else { - // For the scrolling to be reasonably consistent - // we set the lineSize to the average size - // of all currently loaded lines. - T lastCell = cells.getLast(); - lineSize = - (getCellPosition(lastCell) - + getCellLength(lastCell) - - getCellPosition(cells.getFirst())) - / cells.size(); -// } - - if (lastHeight / lineSize < MIN_SCROLLING_LINES_PER_PAGE) { - lineSize = lastHeight / MIN_SCROLLING_LINES_PER_PAGE; - } - - virtualDelta = event.getTextDeltaY() * lineSize; - break; - case NONE: - virtualDelta = event.getDeltaY(); - } - } else { // horizontal - switch (event.getTextDeltaXUnits()) { - case CHARACTERS: - // can we get character size here? - // for now, fall through to pixel values - case NONE: - double dx = event.getDeltaX(); - double dy = event.getDeltaY(); - - virtualDelta = (Math.abs(dx) > Math.abs(dy) ? dx : dy); - } - } - - if (virtualDelta != 0.0) { - /* - ** only consume it if we use it - */ - double result = adjustPixels(-virtualDelta); - if (result != 0.0) { - event.consume(); - } - } - - ScrollBar nonVirtualBar = isVertical() ? hbar : vbar; - if (needBreadthBar) { - double nonVirtualDelta = isVertical() ? event.getDeltaX() : event.getDeltaY(); - if (nonVirtualDelta != 0.0) { - double newValue = nonVirtualBar.getValue() - nonVirtualDelta; - if (newValue < nonVirtualBar.getMin()) { - nonVirtualBar.setValue(nonVirtualBar.getMin()); - } else if (newValue > nonVirtualBar.getMax()) { - nonVirtualBar.setValue(nonVirtualBar.getMax()); - } else { - nonVirtualBar.setValue(newValue); - } - event.consume(); - } - } - } - }); - - - addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler() { - @Override - public void handle(MouseEvent e) { - mouseDown = true; - if (IS_TOUCH_SUPPORTED) { - scrollBarOn(); - } - if (isFocusTraversable()) { - // We check here to see if the current focus owner is within - // this VirtualFlow, and if so we back-off from requesting - // focus back to the VirtualFlow itself. This is particularly - // relevant given the bug identified in RT-32869. In this - // particular case TextInputControl was clearing selection - // when the focus on the TextField changed, meaning that the - // right-click context menu was not showing the correct - // options as there was no selection in the TextField. - boolean doFocusRequest = true; - Node focusOwner = getScene().getFocusOwner(); - if (focusOwner != null) { - Parent parent = focusOwner.getParent(); - while (parent != null) { - if (parent.equals(VirtualPageFlow.this)) { - doFocusRequest = false; - break; - } - parent = parent.getParent(); - } - } - - if (doFocusRequest) { - requestFocus(); - } - } - - lastX = e.getX(); - lastY = e.getY(); - - // determine whether the user has push down on the virtual flow, - // or whether it is the scrollbar. This is done to prevent - // mouse events being 'doubled up' when dragging the scrollbar - // thumb - it has the side-effect of also starting the panning - // code, leading to flicker - isPanning = !(vbar.getBoundsInParent().contains(e.getX(), e.getY()) - || hbar.getBoundsInParent().contains(e.getX(), e.getY())); - } - }); - addEventFilter(MouseEvent.MOUSE_RELEASED, e -> { - mouseDown = false; - if (IS_TOUCH_SUPPORTED) { - startSBReleasedAnimation(); - } - }); - addEventFilter(MouseEvent.MOUSE_DRAGGED, e -> { - if (IS_TOUCH_SUPPORTED) { - scrollBarOn(); - } - if (!isPanning || !isPannable()) return; - - // With panning enabled, we support panning in both vertical - // and horizontal directions, regardless of the fact that - // VirtualFlow is virtual in only one direction. - double xDelta = lastX - e.getX(); - double yDelta = lastY - e.getY(); - - // figure out the distance that the mouse moved in the virtual - // direction, and then perform the movement along that axis - // virtualDelta will contain the amount we actually did move - double virtualDelta = isVertical() ? yDelta : xDelta; - double actual = adjustPixels(virtualDelta); - if (actual != 0) { - // update last* here, as we know we've just adjusted the - // scrollbar. This means we don't get the situation where a - // user presses-and-drags a long way past the min or max - // values, only to change directions and see the scrollbar - // start moving immediately. - if (isVertical()) lastY = e.getY(); - else lastX = e.getX(); - } - - // similarly, we do the same in the non-virtual direction - double nonVirtualDelta = isVertical() ? xDelta : yDelta; - ScrollBar nonVirtualBar = isVertical() ? hbar : vbar; - if (nonVirtualBar.isVisible()) { - double newValue = nonVirtualBar.getValue() + nonVirtualDelta; - if (newValue < nonVirtualBar.getMin()) { - nonVirtualBar.setValue(nonVirtualBar.getMin()); - } else if (newValue > nonVirtualBar.getMax()) { - nonVirtualBar.setValue(nonVirtualBar.getMax()); - } else { - nonVirtualBar.setValue(newValue); - - // same as the last* comment above - if (isVertical()) lastX = e.getX(); - else lastY = e.getY(); - } - } - }); - - /* - * We place the scrollbars _above_ the rectangle, such that the drag - * operations often used in conjunction with scrollbars aren't - * misinterpreted as drag operations on the rectangle as well (which - * would be the case if the scrollbars were underneath it as the - * rectangle itself doesn't block the mouse. - */ - // --- vbar - vbar.setOrientation(Orientation.VERTICAL); - vbar.addEventHandler(MouseEvent.ANY, event -> { - event.consume(); - }); - getChildren().add(vbar); - - // --- hbar - hbar.setOrientation(Orientation.HORIZONTAL); - hbar.addEventHandler(MouseEvent.ANY, event -> { - event.consume(); - }); - getChildren().add(hbar); - - // --- corner - corner = new StackPane(); - corner.getStyleClass().setAll("corner"); - getChildren().add(corner); - - - // initBinds - // clipView binds - InvalidationListener listenerX = valueModel -> { - updateHbar(); - }; - verticalProperty().addListener(listenerX); - hbar.valueProperty().addListener(listenerX); - hbar.visibleProperty().addListener(listenerX); - -// ChangeListener listenerY = new ChangeListener() { -// @Override public void handle(Bean bean, PropertyReference property) { -// clipView.setClipY(isVertical() ? 0 : vbar.getValue()); -// } -// }; -// addChangedListener(VERTICAL, listenerY); -// vbar.addChangedListener(ScrollBar.VALUE, listenerY); - - ChangeListener listenerY = (ov, t, t1) -> { - clipView.setClipY(isVertical() ? 0 : vbar.getValue()); -// System.out.println("v" + getBoundsInLocal()); -// for (T cell : cells){ -// Node parent = cell.getParent(); -// System.out.println(cell.getBoundsInParent()); -// System.out.println(cell.getLocalToParentTransform()); -// } -// System.out.println(); - }; - vbar.valueProperty().addListener(listenerY); - - super.heightProperty().addListener((observable, oldHeight, newHeight) -> { - // Fix for RT-8480, where the VirtualFlow does not show its content - // after changing size to 0 and back. - if (oldHeight.doubleValue() == 0 && newHeight.doubleValue() > 0) { - recreateCells(); - } - }); - - - /* - ** there are certain animations that need to know if the touch is - ** happening..... - */ - setOnTouchPressed(e -> { - touchDetected = true; - scrollBarOn(); - }); - - setOnTouchReleased(e -> { - touchDetected = false; - startSBReleasedAnimation(); - }); - - setImpl_traversalEngine(new ParentTraversalEngine(this, new Algorithm() { - - Node selectNextAfterIndex(int index, TraversalContext context) { - T nextCell; - while ((nextCell = getVisibleCell(++index)) != null) { - if (nextCell.isFocusTraversable()) { - return nextCell; - } - Node n = context.selectFirstInParent(nextCell); - if (n != null) { - return n; - } - } - return null; - } - - Node selectPreviousBeforeIndex(int index, TraversalContext context) { - T prevCell; - while ((prevCell = getVisibleCell(--index)) != null) { - Node prev = context.selectLastInParent(prevCell); - if (prev != null) { - return prev; - } - if (prevCell.isFocusTraversable()) { - return prevCell; - } - } - return null; - } - - @Override - public Node select(Node owner, Direction dir, TraversalContext context) { - T cell; - if (cells.isEmpty()) return null; - if (cells.contains(owner)) { - cell = (T) owner; - } else { - cell = findOwnerCell(owner); - Node next = context.selectInSubtree(cell, owner, dir); - if (next != null) { - return next; - } - if (dir == Direction.NEXT) dir = Direction.NEXT_IN_LINE; - } - int cellIndex = cell.getIndex(); - switch (dir) { - case PREVIOUS: - return selectPreviousBeforeIndex(cellIndex, context); - case NEXT: - Node n = context.selectFirstInParent(cell); - if (n != null) { - return n; - } - // Intentional fall-through - case NEXT_IN_LINE: - return selectNextAfterIndex(cellIndex, context); - } - return null; - } - - private T findOwnerCell(Node owner) { - Parent p = owner.getParent(); - while (!cells.contains(p)) { - p = p.getParent(); - } - return (T) p; - } - - @Override - public Node selectFirst(TraversalContext context) { - T firstCell = cells.getFirst(); - if (firstCell == null) return null; - if (firstCell.isFocusTraversable()) return firstCell; - Node n = context.selectFirstInParent(firstCell); - if (n != null) { - return n; - } - return selectNextAfterIndex(firstCell.getIndex(), context); - } - - @Override - public Node selectLast(TraversalContext context) { - T lastCell = cells.getLast(); - if (lastCell == null) return null; - Node p = context.selectLastInParent(lastCell); - if (p != null) { - return p; - } - if (lastCell.isFocusTraversable()) return lastCell; - return selectPreviousBeforeIndex(lastCell.getIndex(), context); - } - })); - - } - - void updateHbar() { - // Bring the clipView.clipX back to 0 if control is vertical or - // the hbar isn't visible (fix for RT-11666) - if (!isVisible() || getScene() == null) return; - - if (isVertical()) { - if (hbar.isVisible()) { - clipView.setClipX(hbar.getValue()); - } else { - // all cells are now less than the width of the flow, - // so we should shift the hbar/clip such that - // everything is visible in the viewport. - clipView.setClipX(0); - hbar.setValue(0); - } - } - - } - - /*************************************************************************** - * * - * Layout Functionality * - * * - **************************************************************************/ - - /** - * Overridden to implement somewhat more efficient support for layout. The - * VirtualFlow can generally be considered as being unmanaged, in that - * whenever the position changes, or other such things change, we need - * to perform a layout but there is no reason to notify the parent. However - * when things change which may impact the preferred size (such as - * vertical, createCell, and configCell) then we need to notify the - * parent. - */ - @Override - public void requestLayout() { - // isNeedsLayout() is commented out due to RT-21417. This does not - // appear to impact performance (indeed, it may help), and resolves the - // issue identified in RT-21417. - setNeedsLayout(true); - } - - @Override - protected void layoutChildren() { - if (needsRecreateCells) { - lastWidth = -1; - lastHeight = -1; - releaseCell(accumCell); -// accumCell = null; -// accumCellParent.getChildren().clear(); - sheet.getChildren().clear(); - for (int i = 0, max = cells.size(); i < max; i++) { - cells.get(i).updateIndex(-1); - } - cells.clear(); - pile.clear(); - releaseAllPrivateCells(); - } else if (needsRebuildCells) { - lastWidth = -1; - lastHeight = -1; - releaseCell(accumCell); - for (int i = 0; i < cells.size(); i++) { - cells.get(i).updateIndex(-1); - } - addAllToPile(); - releaseAllPrivateCells(); - } else if (needsReconfigureCells) { - setMaxPrefBreadth(-1); - lastWidth = -1; - lastHeight = -1; - } - - if (!dirtyCells.isEmpty()) { - int index; - final int cellsSize = cells.size(); - while ((index = dirtyCells.nextSetBit(0)) != -1 && index < cellsSize) { - T cell = cells.get(index); - // updateIndex(-1) works for TableView, but breaks ListView. - // For now, the TableView just does not use the dirtyCells API -// cell.updateIndex(-1); - if (cell != null) { - cell.requestLayout(); - } - dirtyCells.clear(index); - } - - setMaxPrefBreadth(-1); - lastWidth = -1; - lastHeight = -1; - } - - final boolean hasSizeChange = sizeChanged; - boolean recreatedOrRebuilt = needsRebuildCells || needsRecreateCells || sizeChanged; - - needsRecreateCells = false; - needsReconfigureCells = false; - needsRebuildCells = false; - sizeChanged = false; - - if (needsCellsLayout) { - for (int i = 0, max = cells.size(); i < max; i++) { - PageView cell = cells.get(i); - if (cell != null) { - cell.requestLayout(); - } - } - needsCellsLayout = false; - - // yes, we return here - if needsCellsLayout was set to true, we - // only did it to do the above - not rerun the entire layout. - return; - } - - final double width = getWidth(); - final double height = getHeight(); - final boolean isVertical = isVertical(); - final double position = getPosition(); - - // if the width and/or height is 0, then there is no point doing - // any of this work. In particular, this can happen during startup - if (width <= 0 || height <= 0) { - addAllToPile(); - lastWidth = width; - lastHeight = height; - hbar.setVisible(false); - vbar.setVisible(false); - corner.setVisible(false); - return; - } - - // we check if any of the cells in the cells list need layout. This is a - // sign that they are perhaps animating their sizes. Without this check, - // we may not perform a layout here, meaning that the cell will likely - // 'jump' (in height normally) when the user drags the virtual thumb as - // that is the first time the layout would occur otherwise. - boolean cellNeedsLayout = false; - boolean thumbNeedsLayout = false; - - if (IS_TOUCH_SUPPORTED) { - if ((tempVisibility == true && (hbar.isVisible() == false || vbar.isVisible() == false)) || - (tempVisibility == false && (hbar.isVisible() == true || vbar.isVisible() == true))) { - thumbNeedsLayout = true; - } - } - - if (!cellNeedsLayout) { - for (int i = 0; i < cells.size(); i++) { - PageView cell = cells.get(i); - cellNeedsLayout = cell.isNeedsLayout(); - if (cellNeedsLayout) break; - } - } - - - T firstCell = getFirstVisibleCell(); - - // If no cells need layout, we check other criteria to see if this - // layout call is even necessary. If it is found that no layout is - // needed, we just punt. - if (!cellNeedsLayout && !thumbNeedsLayout) { - boolean cellSizeChanged = false; - if (firstCell != null) { - double breadth = getCellBreadth(firstCell); - double length = getCellLength(firstCell); - cellSizeChanged = (breadth != lastCellBreadth) || (length != lastCellLength); - lastCellBreadth = breadth; - lastCellLength = length; - } - - if (width == lastWidth && - height == lastHeight && - cellCount == lastCellCount && - isVertical == lastVertical && - position == lastPosition && - !cellSizeChanged) { - // TODO this happens to work around the problem tested by - // testCellLayout_LayoutWithoutChangingThingsUsesCellsInSameOrderAsBefore - // but isn't a proper solution. Really what we need to do is, when - // laying out cells, we need to make sure that if a cell is pressed - // AND we are doing a full rebuild then we need to make sure we - // use that cell in the same physical location as before so that - // it gets the mouse release event. - return; - } - } - - /* - * This function may get called under a variety of circumstances. - * It will determine what has changed from the last time it was laid - * out, and will then take one of several execution paths based on - * what has changed so as to perform minimal layout work and also to - * give the expected behavior. One or more of the following may have - * happened: - * - * 1) width/height has changed - * - If the width and/or height has been reduced (but neither of - * them has been expanded), then we simply have to reposition and - * resize the scroll bars - * - If the width (in the vertical case) has expanded, then we - * need to resize the existing cells and reposition and resize - * the scroll bars - * - If the height (in the vertical case) has expanded, then we - * need to resize and reposition the scroll bars and add - * any trailing cells - * - * 2) cell count has changed - * - If the number of cells is bigger, or it is smaller but not - * so small as to move the position then we can just update the - * cells in place without performing layout and update the - * scroll bars. - * - If the number of cells has been reduced and it affects the - * position, then move the position and rebuild all the cells - * and update the scroll bars - * - * 3) size of the cell has changed - * - If the size changed in the virtual direction (ie: height - * in the case of vertical) then layout the cells, adding - * trailing cells as necessary and updating the scroll bars - * - If the size changed in the non virtual direction (ie: width - * in the case of vertical) then simply adjust the widths of - * the cells as appropriate and adjust the scroll bars - * - * 4) vertical changed, cells is empty, maxPrefBreadth == -1, etc - * - Full rebuild. - * - * Each of the conditions really resolves to several of a handful of - * possible outcomes: - * a) reposition & rebuild scroll bars - * b) resize cells in non-virtual direction - * c) add trailing cells - * d) update cells - * e) resize cells in the virtual direction - * f) all of the above - * - * So this function first determines what outcomes need to occur, and - * then will execute all the ones that really need to happen. Every code - * path ends up touching the "reposition & rebuild scroll bars" outcome, - * so that one will be executed every time. - */ - boolean needTrailingCells = false; - boolean rebuild = cellNeedsLayout || - isVertical != lastVertical || - cells.isEmpty() || - getMaxPrefBreadth() == -1 || - position != lastPosition || - cellCount != lastCellCount || - hasSizeChange || - (isVertical && height < lastHeight) || (!isVertical && width < lastWidth); - - if (!rebuild) { - // Check if maxPrefBreadth didn't change - double maxPrefBreadth = getMaxPrefBreadth(); - boolean foundMax = false; - for (int i = 0; i < cells.size(); ++i) { - double breadth = getCellBreadth(cells.get(i)); - if (maxPrefBreadth == breadth) { - foundMax = true; - } else if (breadth > maxPrefBreadth) { - rebuild = true; - break; - } - } - if (!foundMax) { // All values were lower - rebuild = true; - } - } - - if (!rebuild) { - if ((isVertical && height > lastHeight) || (!isVertical && width > lastWidth)) { - // resized in the virtual direction - needTrailingCells = true; - } - } - - initViewport(); - - // Get the index of the "current" cell - int currentIndex = computeCurrentIndex(); - if (lastCellCount != cellCount) { - // The cell count has changed. We want to keep the viewport - // stable if possible. If position was 0 or 1, we want to keep - // the position in the same place. If the new cell count is >= - // the currentIndex, then we will adjust the position to be 1. - // Otherwise, our goal is to leave the index of the cell at the - // top consistent, with the same translation etc. - if (position == 0 || position == 1) { - // Update the item count -// setItemCount(cellCount); - } else if (currentIndex >= cellCount) { - setPosition(1.0f); -// setItemCount(cellCount); - } else if (firstCell != null) { - double firstCellOffset = getCellPosition(firstCell); - int firstCellIndex = getCellIndex(firstCell); -// setItemCount(cellCount); - adjustPositionToIndex(firstCellIndex); - double viewportTopToCellTop = -computeOffsetForCell(firstCellIndex); - adjustByPixelAmount(viewportTopToCellTop - firstCellOffset); - } - - // Update the current index - currentIndex = computeCurrentIndex(); - } - - if (rebuild) { - setMaxPrefBreadth(-1); - // Start by dumping all the cells into the pile - addAllToPile(); - - // The distance from the top of the viewport to the top of the - // cell for the current index. - double offset = -computeViewportOffset(getPosition()); - - // Add all the leading and trailing cells (the call to add leading - // cells will add the current cell as well -- that is, the one that - // represents the current position on the mapper). - addLeadingCells(currentIndex, offset); - - // Force filling of space with empty cells if necessary - addTrailingCells(false); - } else if (needTrailingCells) { - addTrailingCells(false); - } - - computeBarVisiblity(); - updateScrollBarsAndCells(recreatedOrRebuilt); - - lastWidth = getWidth(); - lastHeight = getHeight(); - lastCellCount = getCellCount(); - lastVertical = isVertical(); - lastPosition = getPosition(); - - cleanPile(); - } - - /** - * Adds all the cells prior to and including the given currentIndex, until - * no more can be added without falling off the flow. The startOffset - * indicates the distance from the leading edge (top) of the viewport to - * the leading edge (top) of the currentIndex. - */ - protected void addLeadingCells(int currentIndex, double startOffset) { - // The offset will keep track of the distance from the top of the - // viewport to the top of the current index. We will increment it - // as we lay out leading cells. - double offset = startOffset; - // The index is the absolute index of the cell being laid out - int index = currentIndex; - - // Offset should really be the bottom of the current index - boolean first = true; // first time in, we just fudge the offset and let - // it be the top of the current index then redefine - // it as the bottom of the current index thereafter - // while we have not yet laid out so many cells that they would fall - // off the flow, we will continue to create and add cells. The - // offset is our indication of whether we can lay out additional - // cells. If the offset is ever < 0, except in the case of the very - // first cell, then we must quit. - T cell = null; - - // special case for the position == 1.0, skip adding last invisible cell - if (index == cellCount && offset == getViewportLength()) { - index--; - first = false; - } - while (index >= 0 && (offset > 0 || first)) { - cell = getAvailableCell(index); - setCellIndex(cell, index); - resizeCellSize(cell); // resize must be after config - cells.addFirst(cell); - - // A little gross but better than alternatives because it reduces - // the number of times we have to update a cell or compute its - // size. The first time into this loop "offset" is actually the - // top of the current index. On all subsequent visits, it is the - // bottom of the current index. - if (first) { - first = false; - } else { - offset -= getCellLength(cell); - } - - // Position the cell, and update the maxPrefBreadth variable as we go. - positionCell(cell, offset); - setMaxPrefBreadth(Math.max(getMaxPrefBreadth(), getCellBreadth(cell))); - cell.setVisible(true); - --index; - } - - // There are times when after laying out the cells we discover that - // the top of the first cell which represents index 0 is below the top - // of the viewport. In these cases, we have to adjust the cells up - // and reset the mapper position. This might happen when items got - // removed at the top or when the viewport size increased. - if (cells.size() > 0) { - cell = cells.getFirst(); - int firstIndex = getCellIndex(cell); - double firstCellPos = getCellPosition(cell); - if (firstIndex == 0 && firstCellPos > 0) { - setPosition(0.0f); - offset = 0; - for (int i = 0; i < cells.size(); i++) { - cell = cells.get(i); - positionCell(cell, offset); - offset += getCellLength(cell); - } - } - } else { - // reset scrollbar to top, so if the flow sees cells again it starts at the top - vbar.setValue(0); - hbar.setValue(0); - } - } - - /** - * Adds all the trailing cells that come after the last index in - * the cells ObservableList. - */ - protected boolean addTrailingCells(boolean fillEmptyCells) { - // If cells is empty then addLeadingCells bailed for some reason and - // we're hosed, so just punt - if (cells.isEmpty()) return false; - - // While we have not yet laid out so many cells that they would fall - // off the flow, so we will continue to create and add cells. When the - // offset becomes greater than the width/height of the flow, then we - // know we cannot add any more cells. - T startCell = cells.getLast(); - double offset = getCellPosition(startCell) + getCellLength(startCell); - int index = getCellIndex(startCell) + 1; - boolean filledWithNonEmpty = index <= cellCount; - - final double viewportLength = getViewportLength(); - - // Fix for RT-37421, which was a regression caused by RT-36556 - if (offset < 0 && !fillEmptyCells) { - return false; - } - - // - // RT-36507: viewportLength - offset gives the maximum number of - // additional cells that should ever be able to fit in the viewport if - // every cell had a height of 1. If index ever exceeds this count, - // then offset is not incrementing fast enough, or at all, which means - // there is something wrong with the cell size calculation. - // - final double maxCellCount = viewportLength - offset; - while (offset < viewportLength) { - if (index >= cellCount) { - if (offset < viewportLength) filledWithNonEmpty = false; - if (!fillEmptyCells) return filledWithNonEmpty; - // RT-36507 - return if we've exceeded the maximum - if (index > maxCellCount) { - final PlatformLogger logger = Logging.getControlsLogger(); - if (logger.isLoggable(PlatformLogger.Level.INFO)) { - if (startCell != null) { - logger.info("index exceeds maxCellCount. Check size calculations for " + startCell.getClass()); - } else { - logger.info("index exceeds maxCellCount"); - } - } - return filledWithNonEmpty; - } - } - T cell = getAvailableCell(index); - setCellIndex(cell, index); - resizeCellSize(cell); // resize happens after config! - cells.addLast(cell); - - // Position the cell and update the max pref - positionCell(cell, offset); - setMaxPrefBreadth(Math.max(getMaxPrefBreadth(), getCellBreadth(cell))); - - offset += getCellLength(cell); - cell.setVisible(true); - ++index; - } - - // Discover whether the first cell coincides with index #0. If after - // adding all the trailing cells we find that a) the first cell was - // not index #0 and b) there are trailing cells, then we have a - // problem. We need to shift all the cells down and add leading cells, - // one at a time, until either the very last non-empty cells is aligned - // with the bottom OR we have laid out cell index #0 at the first - // position. - T firstCell = cells.getFirst(); - index = getCellIndex(firstCell); - T lastNonEmptyCell = getLastVisibleCell(); - double start = getCellPosition(firstCell); - double end = getCellPosition(lastNonEmptyCell) + getCellLength(lastNonEmptyCell); - if ((index != 0 || (index == 0 && start < 0)) && fillEmptyCells && - lastNonEmptyCell != null && getCellIndex(lastNonEmptyCell) == cellCount - 1 && end < viewportLength) { - - double prospectiveEnd = end; - double distance = viewportLength - end; - while (prospectiveEnd < viewportLength && index != 0 && (-start) < distance) { - index--; - T cell = getAvailableCell(index); - setCellIndex(cell, index); - resizeCellSize(cell); // resize must be after config - cells.addFirst(cell); - double cellLength = getCellLength(cell); - start -= cellLength; - prospectiveEnd += cellLength; - positionCell(cell, start); - setMaxPrefBreadth(Math.max(getMaxPrefBreadth(), getCellBreadth(cell))); - cell.setVisible(true); - } - - // The amount by which to translate the cells down - firstCell = cells.getFirst(); - start = getCellPosition(firstCell); - double delta = viewportLength - end; - if (getCellIndex(firstCell) == 0 && delta > (-start)) { - delta = (-start); - } - // Move things - for (int i = 0; i < cells.size(); i++) { - T cell = cells.get(i); - positionCell(cell, getCellPosition(cell) + delta); - } - - // Check whether the first cell, subsequent to our adjustments, is - // now index #0 and aligned with the top. If so, change the position - // to be at 0 instead of 1. - start = getCellPosition(firstCell); - if (getCellIndex(firstCell) == 0 && start == 0) { - setPosition(0); - } else if (getPosition() != 1) { - setPosition(1); - } - } - - return filledWithNonEmpty; - } - - /** - * @return true if bar visibility changed - */ - private boolean computeBarVisiblity() { - if (cells.isEmpty()) { - // In case no cells are set yet, we assume no bars are needed - needLengthBar = false; - needBreadthBar = false; - return true; - } - - final boolean isVertical = isVertical(); - boolean barVisibilityChanged = false; - - VirtualScrollBar breadthBar = isVertical ? hbar : vbar; - VirtualScrollBar lengthBar = isVertical ? vbar : hbar; - - final double viewportBreadth = getViewportBreadth(); - - final int cellsSize = cells.size(); - for (int i = 0; i < 2; i++) { - final boolean lengthBarVisible = getPosition() > 0 - || cellCount > cellsSize - || (cellCount == cellsSize && (getCellPosition(cells.getLast()) + getCellLength(cells.getLast())) > getViewportLength()) - || (cellCount == cellsSize - 1 && barVisibilityChanged && needBreadthBar); - - if (lengthBarVisible ^ needLengthBar) { - needLengthBar = lengthBarVisible; - barVisibilityChanged = true; - } - - // second conditional removed for RT-36669. - final boolean breadthBarVisible = (maxPrefBreadth > viewportBreadth);// || (needLengthBar && maxPrefBreadth > (viewportBreadth - lengthBarBreadth)); - if (breadthBarVisible ^ needBreadthBar) { - needBreadthBar = breadthBarVisible; - barVisibilityChanged = true; - } - } - - // Start by optimistically deciding whether the length bar and - // breadth bar are needed and adjust the viewport dimensions - // accordingly. If during layout we find that one or the other of the - // bars actually is needed, then we will perform a cleanup pass - - if (!IS_TOUCH_SUPPORTED) { - updateViewportDimensions(); - breadthBar.setVisible(needBreadthBar); - lengthBar.setVisible(needLengthBar); - } else { - breadthBar.setVisible(needBreadthBar && tempVisibility); - lengthBar.setVisible(needLengthBar && tempVisibility); - } - - return barVisibilityChanged; - } - - private void updateViewportDimensions() { - final boolean isVertical = isVertical(); - final double breadthBarLength = snapSize(isVertical ? hbar.prefHeight(-1) : vbar.prefWidth(-1)); - final double lengthBarBreadth = snapSize(isVertical ? vbar.prefWidth(-1) : hbar.prefHeight(-1)); - - setViewportBreadth((isVertical ? getWidth() : getHeight()) - (needLengthBar ? lengthBarBreadth : 0)); - setViewportLength((isVertical ? getHeight() : getWidth()) - (needBreadthBar ? breadthBarLength : 0)); - } - - private void initViewport() { - // Initialize the viewportLength and viewportBreadth to match the - // width/height of the flow - final boolean isVertical = isVertical(); - - updateViewportDimensions(); - - VirtualScrollBar breadthBar = isVertical ? hbar : vbar; - VirtualScrollBar lengthBar = isVertical ? vbar : hbar; - - // If there has been a switch between the virtualized bar, then we - // will want to do some stuff TODO. - breadthBar.setVirtual(false); - lengthBar.setVirtual(true); - } - - @Override - protected void setWidth(double value) { - if (value != lastWidth) { - super.setWidth(value); - sizeChanged = true; - setNeedsLayout(true); - requestLayout(); - } - } - - @Override - protected void setHeight(double value) { - if (value != lastHeight) { - super.setHeight(value); - sizeChanged = true; - setNeedsLayout(true); - requestLayout(); - } - } - - private void updateScrollBarsAndCells(boolean recreate) { - // Assign the hbar and vbar to the breadthBar and lengthBar so as - // to make some subsequent calculations easier. - final boolean isVertical = isVertical(); - VirtualScrollBar breadthBar = isVertical ? hbar : vbar; - VirtualScrollBar lengthBar = isVertical ? vbar : hbar; - - // We may have adjusted the viewport length and breadth after the - // layout due to scroll bars becoming visible. So we need to perform - // a follow up pass and resize and shift all the cells to fit the - // viewport. Note that the prospective viewport size is always >= the - // final viewport size, so we don't have to worry about adding - // cells during this cleanup phase. - fitCells(); - - // Update cell positions. - // When rebuilding the cells, we add the cells and along the way compute - // the maxPrefBreadth. Based on the computed value, we may add - // the breadth scrollbar which changes viewport length, so we need - // to re-position the cells. - if (!cells.isEmpty()) { - final double currOffset = -computeViewportOffset(getPosition()); - final int currIndex = computeCurrentIndex() - cells.getFirst().getIndex(); - final int size = cells.size(); - - // position leading cells - double offset = currOffset; - - for (int i = currIndex - 1; i >= 0 && i < size; i--) { - final T cell = cells.get(i); - - offset -= getCellLength(cell); - - positionCell(cell, offset); - } - - // position trailing cells - offset = currOffset; - for (int i = currIndex; i >= 0 && i < size; i++) { - final T cell = cells.get(i); - positionCell(cell, offset); - - offset += getCellLength(cell); - } - } - - // Toggle visibility on the corner - corner.setVisible(breadthBar.isVisible() && lengthBar.isVisible()); - - double sumCellLength = 0; - double flowLength = (isVertical ? getHeight() : getWidth()) - - (breadthBar.isVisible() ? breadthBar.prefHeight(-1) : 0); - - final double viewportBreadth = getViewportBreadth(); - final double viewportLength = getViewportLength(); - - // Now position and update the scroll bars - if (breadthBar.isVisible()) { - /* - ** Positioning the ScrollBar - */ - if (!IS_TOUCH_SUPPORTED) { - if (isVertical) { - hbar.resizeRelocate(0, viewportLength, - viewportBreadth, hbar.prefHeight(viewportBreadth)); - } else { - vbar.resizeRelocate(viewportLength, 0, - vbar.prefWidth(viewportBreadth), viewportBreadth); - } - } else { - if (isVertical) { - hbar.resizeRelocate(0, (viewportLength - hbar.getHeight()), - viewportBreadth, hbar.prefHeight(viewportBreadth)); - } else { - vbar.resizeRelocate((viewportLength - vbar.getWidth()), 0, - vbar.prefWidth(viewportBreadth), viewportBreadth); - } - } - - if (getMaxPrefBreadth() != -1) { - double newMax = Math.max(1, getMaxPrefBreadth() - viewportBreadth); - if (newMax != breadthBar.getMax()) { - breadthBar.setMax(newMax); - - double breadthBarValue = breadthBar.getValue(); - boolean maxed = breadthBarValue != 0 && newMax == breadthBarValue; - if (maxed || breadthBarValue > newMax) { - breadthBar.setValue(newMax); - } - - breadthBar.setVisibleAmount((viewportBreadth / getMaxPrefBreadth()) * newMax); - } - } - } - - // determine how many cells there are on screen so that the scrollbar - // thumb can be appropriately sized - if (recreate && (lengthBar.isVisible() || IS_TOUCH_SUPPORTED)) { - int numCellsVisibleOnScreen = 0; - for (int i = 0, max = cells.size(); i < max; i++) { - T cell = cells.get(i); - if (cell != null) {// && !cell.isEmpty()) { - sumCellLength += (isVertical ? cell.getHeight() : cell.getWidth()); - if (sumCellLength > flowLength) { - break; - } - - numCellsVisibleOnScreen++; - } - } - - lengthBar.setMax(1); - if (numCellsVisibleOnScreen == 0 && cellCount == 1) { - // special case to help resolve RT-17701 and the case where we have - // only a single row and it is bigger than the viewport - lengthBar.setVisibleAmount(flowLength / sumCellLength); - } else { - lengthBar.setVisibleAmount(numCellsVisibleOnScreen / (float) cellCount); - } - } - - if (lengthBar.isVisible()) { - // Fix for RT-11873. If this isn't here, we can have a situation where - // the scrollbar scrolls endlessly. This is possible when the cell - // count grows as the user hits the maximal position on the scrollbar - // (i.e. the list size dynamically grows as the user needs more). - // - // This code was commented out to resolve RT-14477 after testing - // whether RT-11873 can be recreated. It could not, and therefore - // for now this code will remained uncommented until it is deleted - // following further testing. -// if (lengthBar.getValue() == 1.0 && lastCellCount != cellCount) { -// lengthBar.setValue(0.99); -// } - - /* - ** Positioning the ScrollBar - */ - if (!IS_TOUCH_SUPPORTED) { - if (isVertical) { - vbar.resizeRelocate(viewportBreadth, 0, vbar.prefWidth(viewportLength), viewportLength); - } else { - hbar.resizeRelocate(0, viewportBreadth, viewportLength, hbar.prefHeight(-1)); - } - } else { - if (isVertical) { - vbar.resizeRelocate((viewportBreadth - vbar.getWidth()), 0, vbar.prefWidth(viewportLength), viewportLength); - } else { - hbar.resizeRelocate(0, (viewportBreadth - hbar.getHeight()), viewportLength, hbar.prefHeight(-1)); - } - } - } - - if (corner.isVisible()) { - if (!IS_TOUCH_SUPPORTED) { - corner.resize(vbar.getWidth(), hbar.getHeight()); - corner.relocate(hbar.getLayoutX() + hbar.getWidth(), vbar.getLayoutY() + vbar.getHeight()); - } else { - corner.resize(vbar.getWidth(), hbar.getHeight()); - corner.relocate(hbar.getLayoutX() + (hbar.getWidth() - vbar.getWidth()), vbar.getLayoutY() + (vbar.getHeight() - hbar.getHeight())); - hbar.resize(hbar.getWidth() - vbar.getWidth(), hbar.getHeight()); - vbar.resize(vbar.getWidth(), vbar.getHeight() - hbar.getHeight()); - } - } - - clipView.resize(snapSize(isVertical ? viewportBreadth : viewportLength), - snapSize(isVertical ? viewportLength : viewportBreadth)); - - // If the viewportLength becomes large enough that all cells fit - // within the viewport, then we want to update the value to match. - if (getPosition() != lengthBar.getValue()) { - lengthBar.setValue(getPosition()); - } - } - - /** - * Adjusts the cells location and size if necessary. The breadths of all - * cells will be adjusted to fit the viewportWidth or maxPrefBreadth, and - * the layout position will be updated if necessary based on index and - * offset. - */ - private void fitCells() { - double size = Math.max(getMaxPrefBreadth(), getViewportBreadth()); - boolean isVertical = isVertical(); - - // Note: Do not optimise this loop by pre-calculating the cells size and - // storing that into a int value - this can lead to RT-32828 - for (int i = 0; i < cells.size(); i++) { - PageView cell = cells.get(i); - if (isVertical) { - cell.resize(size, cell.prefHeight(size)); - } else { - cell.resize(cell.prefWidth(size), size); - } - } - } - - private void cull() { - final double viewportLength = getViewportLength(); - for (int i = cells.size() - 1; i >= 0; i--) { - T cell = cells.get(i); - double cellSize = getCellLength(cell); - double cellStart = getCellPosition(cell); - double cellEnd = cellStart + cellSize; - if (cellStart >= viewportLength || cellEnd < 0) { - addToPile(cells.remove(i)); - } - } - } - - /*************************************************************************** - * * - * Helper functions for working with cells * - * * - **************************************************************************/ - - /** - * Return the index for a given cell. This allows subclasses to customise - * how cell indices are retrieved. - */ - protected int getCellIndex(T cell) { - return cell.getIndex(); - } - - - /** - * Return a cell for the given index. This may be called for any cell, - * including beyond the range defined by cellCount, in which case an - * empty cell will be returned. The returned value should not be stored for - * any reason. - */ - public T getCell(int index) { - // If there are cells, then we will attempt to get an existing cell - if (!cells.isEmpty()) { - // First check the cells that have already been created and are - // in use. If this call returns a value, then we can use it - T cell = getVisibleCell(index); - if (cell != null) return cell; - } - - // check the pile - for (int i = 0; i < pile.size(); i++) { - T cell = pile.get(i); - if (getCellIndex(cell) == index) { - // Note that we don't remove from the pile: if we do it leads - // to a severe performance decrease. This seems to be OK, as - // getCell() is only used for cell measurement purposes. - // pile.remove(i); - return cell; - } - } - - if (pile.size() > 0) { - return pile.get(0); - } - - // We need to use the accumCell and return that - if (accumCell == null) { - Callback createCell = getCreateCell(); - if (createCell != null) { - accumCell = createCell.call(this); - accumCell.getProperties().put(NEW_CELL, null); - accumCellParent.getChildren().setAll(accumCell); - - // Note the screen reader will attempt to find all - // the items inside the view to calculate the item count. - // Having items under different parents (sheet and accumCellParent) - // leads the screen reader to compute wrong values. - // The regular scheme to provide items to the screen reader - // uses getPrivateCell(), which places the item in the sheet. - // The accumCell, and its children, should be ignored by the - // screen reader. - accumCell.setAccessibleRole(AccessibleRole.NODE); - accumCell.getChildrenUnmodifiable().addListener((Observable c) -> { - for (Node n : accumCell.getChildrenUnmodifiable()) { - n.setAccessibleRole(AccessibleRole.NODE); - } - }); - } - } - setCellIndex(accumCell, index); - resizeCellSize(accumCell); - return accumCell; - } - - /** - * After using the accum cell, it needs to be released! - */ - private void releaseCell(T cell) { - if (accumCell != null && cell == accumCell) { - accumCell.updateIndex(-1); - } - } - - /** - * This method is an experts-only method - if the requested index is not - * already an existing visible cell, it will create a cell for the - * given index and insert it into the sheet. From that point on it will be - * unmanaged, and is up to the caller of this method to manage it. - */ - T getPrivateCell(int index) { - T cell = null; - - // If there are cells, then we will attempt to get an existing cell - if (!cells.isEmpty()) { - // First check the cells that have already been created and are - // in use. If this call returns a value, then we can use it - cell = getVisibleCell(index); - if (cell != null) { - // Force the underlying text inside the cell to be updated - // so that when the screen reader runs, it will match the - // text in the cell (force updateDisplayedText()) - cell.layout(); - return cell; - } - } - - // check the existing sheet children - if (cell == null) { - for (int i = 0; i < sheetChildren.size(); i++) { - T _cell = (T) sheetChildren.get(i); - if (getCellIndex(_cell) == index) { - return _cell; - } - } - } - - if (cell == null) { - Callback createCell = getCreateCell(); - if (createCell != null) { - cell = createCell.call(this); - } - } - - if (cell != null) { - setCellIndex(cell, index); - resizeCellSize(cell); - cell.setVisible(false); - sheetChildren.add(cell); - privateCells.add(cell); - } - - return cell; - } - - private final List privateCells = new ArrayList<>(); - - private void releaseAllPrivateCells() { - sheetChildren.removeAll(privateCells); - } - - /** - * Compute and return the length of the cell for the given index. This is - * called both internally when adjusting by pixels, and also at times - * by PositionMapper (see the getItemSize callback). When called by - * PositionMapper, it is possible that it will be called for some index - * which is not associated with any cell, so we have to do a bit of work - * to use a cell as a helper for computing cell size in some cases. - */ - protected double getCellLength(int index) { - if (fixedCellSizeEnabled) return fixedCellSize; - - T cell = getCell(index); - double length = getCellLength(cell); - releaseCell(cell); - return length; - } - - /** - */ - protected double getCellBreadth(int index) { - T cell = getCell(index); - double b = getCellBreadth(cell); - releaseCell(cell); - return b; - } - - /** - * Gets the length of a specific cell - */ - protected double getCellLength(T cell) { - if (cell == null) return 0; - if (fixedCellSizeEnabled) return fixedCellSize; - - return isVertical() ? - cell.getLayoutBounds().getHeight() - : cell.getLayoutBounds().getWidth(); - } - -// private double getCellPrefLength(T cell) { -// return isVertical() ? -// cell.prefHeight(-1) -// : cell.prefWidth(-1); -// } - - /** - * Gets the breadth of a specific cell - */ - protected double getCellBreadth(PageView cell) { - return isVertical() ? - cell.prefWidth(-1) - : cell.prefHeight(-1); - } - - /** - * Gets the layout position of the cell along the length axis - */ - protected double getCellPosition(T cell) { - if (cell == null) return 0; - - return isVertical() ? - cell.getLayoutY() - : cell.getLayoutX(); - } - - protected void positionCell(T cell, double position) { - if (isVertical()) { - cell.setLayoutX(0);//getWidth()/2 - cell.getPageBounds().getWidth()/2); - cell.setLayoutY((position)); - } else { - cell.setLayoutX(snapSize(position)); - cell.setLayoutY(0); - } - } - - protected void resizeCellSize(T cell) { - if (cell == null) return; - - if (isVertical()) { - double width = Math.max(getMaxPrefBreadth(), getViewportBreadth()); - cell.resize(width, fixedCellSizeEnabled ? fixedCellSize : Utils.boundedSize(cell.prefHeight(width), cell.minHeight(width), cell.maxHeight(width))); - } else { - double height = Math.max(getMaxPrefBreadth(), getViewportBreadth()); - cell.resize(fixedCellSizeEnabled ? fixedCellSize : Utils.boundedSize(cell.prefWidth(height), cell.minWidth(height), cell.maxWidth(height)), height); - } - } - - protected void setCellIndex(T cell, int index) { - assert cell != null; - - cell.updateIndex(index); - - // make sure the cell is sized correctly. This is important for both - // general layout of cells in a VirtualFlow, but also in cases such as - // RT-34333, where the sizes were being reported incorrectly to the - // ComboBox popup. - if ((cell.isNeedsLayout() && cell.getScene() != null) || cell.getProperties().containsKey(NEW_CELL)) { - cell.applyCss(); - cell.getProperties().remove(NEW_CELL); - } - } - - /*************************************************************************** - * * - * Helper functions for cell management * - * * - **************************************************************************/ - - - /** - * Indicates that this is a newly created cell and we need call impl_processCSS for it. - *

- * See RT-23616 for more details. - */ - private static final String NEW_CELL = "newcell"; - - /** - * Get a cell which can be used in the layout. This function will reuse - * cells from the pile where possible, and will create new cells when - * necessary. - */ - protected T getAvailableCell(int prefIndex) { - T cell = null; - - // Fix for RT-12822. We try to retrieve the cell from the pile rather - // than just grab a random cell from the pile (or create another cell). - for (int i = 0, max = pile.size(); i < max; i++) { - T _cell = pile.get(i); - assert _cell != null; - - if (getCellIndex(_cell) == prefIndex) { - cell = _cell; - pile.remove(i); - break; - } - cell = null; - } - - if (cell == null) { - if (pile.size() > 0) { - // we try to get a cell with an index that is the same even/odd - // as the prefIndex. This saves us from having to run so much - // css on the cell as it will not change from even to odd, or - // vice versa - final boolean prefIndexIsEven = (prefIndex & 1) == 0; - for (int i = 0, max = pile.size(); i < max; i++) { - final T c = pile.get(i); - final int cellIndex = getCellIndex(c); - - if ((cellIndex & 1) == 0 && prefIndexIsEven) { - cell = c; - pile.remove(i); - break; - } else if ((cellIndex & 1) == 1 && !prefIndexIsEven) { - cell = c; - pile.remove(i); - break; - } - } - - if (cell == null) { - cell = pile.removeFirst(); - } - } else { - cell = getCreateCell().call(this); - cell.getProperties().put(NEW_CELL, null); - } - } - - if (cell.getParent() == null) { - sheetChildren.add(cell); - } - - return cell; - } - - // protected to allow subclasses to clean up - protected void addAllToPile() { - for (int i = 0, max = cells.size(); i < max; i++) { - addToPile(cells.removeFirst()); - } - } - - /** - * Puts the given cell onto the pile. This is called whenever a cell has - * fallen off the flow's start. - */ - private void addToPile(T cell) { - assert cell != null; - pile.addLast(cell); - } - - private void cleanPile() { - boolean wasFocusOwner = false; - - for (int i = 0, max = pile.size(); i < max; i++) { - T cell = pile.get(i); - wasFocusOwner = wasFocusOwner || doesCellContainFocus(cell); - cell.setVisible(false); - } - - // Fix for RT-35876: Rather than have the cells do weird things with - // focus (in particular, have focus jump between cells), we return focus - // to the VirtualFlow itself. - if (wasFocusOwner) { - requestFocus(); - } - } - - private boolean doesCellContainFocus(PageView c) { - Scene scene = c.getScene(); - final Node focusOwner = scene == null ? null : scene.getFocusOwner(); - - if (focusOwner != null) { - if (c.equals(focusOwner)) { - return true; - } - - Parent p = focusOwner.getParent(); - while (p != null && !(p instanceof VirtualPageFlow)) { - if (c.equals(p)) { - return true; - } - p = p.getParent(); - } - } - - return false; - } - - /** - * Gets a cell for the given index if the cell has been created and laid out. - * "Visible" is a bit of a misnomer, the cell might not be visible in the - * viewport (it may be clipped), but does distinguish between cells that - * have been created and are in use vs. those that are in the pile or - * not created. - */ - public T getVisibleCell(int index) { - if (cells.isEmpty()) return null; - - // check the last index - T lastCell = cells.getLast(); - int lastIndex = getCellIndex(lastCell); - if (index == lastIndex) return lastCell; - - // check the first index - T firstCell = cells.getFirst(); - int firstIndex = getCellIndex(firstCell); - if (index == firstIndex) return firstCell; - - // if index is > firstIndex and < lastIndex then we can get the index - if (index > firstIndex && index < lastIndex) { - T cell = cells.get(index - firstIndex); - if (getCellIndex(cell) == index) return cell; - } - - // there is no visible cell for the specified index - return null; - } - - /** - * Locates and returns the last non-empty IndexedCell that is currently - * partially or completely visible. This function may return null if there - * are no cells, or if the viewport length is 0. - */ - public T getLastVisibleCell() { - if (cells.isEmpty() || getViewportLength() <= 0) return null; - - T cell; - for (int i = cells.size() - 1; i >= 0; i--) { - cell = cells.get(i); -// if (!cell.isEmpty()) { - return cell; -// } - } - - return null; - } - - /** - * Locates and returns the first non-empty IndexedCell that is partially or - * completely visible. This really only ever returns null if there are no - * cells or the viewport length is 0. - */ - public T getFirstVisibleCell() { - if (cells.isEmpty() || getViewportLength() <= 0) return null; - T cell = cells.getFirst(); - return cell; //cell.isEmpty() ? null : cell; - } - - // Returns last visible cell whose bounds are entirely within the viewport - public T getLastVisibleCellWithinViewPort() { - if (cells.isEmpty() || getViewportLength() <= 0) return null; - - T cell; - final double max = getViewportLength(); - for (int i = cells.size() - 1; i >= 0; i--) { - cell = cells.get(i); -// if (cell.isEmpty()) continue; - - final double cellStart = getCellPosition(cell); - final double cellEnd = cellStart + getCellLength(cell); - - // we use the magic +2 to allow for a little bit of fuzziness, - // this is to help in situations such as RT-34407 - if (cellEnd <= (max + 2)) { - return cell; - } - } - - return null; - } - - // Returns first visible cell whose bounds are entirely within the viewport - public T getFirstVisibleCellWithinViewPort() { - if (cells.isEmpty() || getViewportLength() <= 0) return null; - - T cell; - for (int i = 0; i < cells.size(); i++) { - cell = cells.get(i); -// if (cell.isEmpty()) continue; - - final double cellStart = getCellPosition(cell); - if (cellStart >= 0) { - return cell; - } - } - - return null; - } - - /** - * Adjust the position of cells so that the specified cell - * will be positioned at the start of the viewport. The given cell must - * already be "live". This is bad public API! - */ - public void showAsFirst(T firstCell) { - if (firstCell != null) { - adjustPixels(getCellPosition(firstCell)); - } - } - - /** - * Adjust the position of cells so that the specified cell - * will be positioned at the end of the viewport. The given cell must - * already be "live". This is bad public API! - */ - public void showAsLast(T lastCell) { - if (lastCell != null) { - adjustPixels(getCellPosition(lastCell) + getCellLength(lastCell) - getViewportLength()); - } - } - - /** - * Adjusts the cells such that the selected cell will be fully visible in - * the viewport (but only just). - */ - public void show(T cell) { - if (cell != null) { - final double start = getCellPosition(cell); - final double length = getCellLength(cell); - final double end = start + length; - final double viewportLength = getViewportLength(); - - if (start < 0) { - adjustPixels(start); - } else if (end > viewportLength) { - adjustPixels(end - viewportLength); - } - } - } - - public void show(int index) { - T cell = getVisibleCell(index); - if (cell != null) { - show(cell); - } else { - // See if the previous index is a visible cell - T prev = getVisibleCell(index - 1); - if (prev != null) { - // Need to add a new cell and then we can show it -// layingOut = true; - cell = getAvailableCell(index); - setCellIndex(cell, index); - resizeCellSize(cell); // resize must be after config - cells.addLast(cell); - positionCell(cell, getCellPosition(prev) + getCellLength(prev)); - setMaxPrefBreadth(Math.max(getMaxPrefBreadth(), getCellBreadth(cell))); - cell.setVisible(true); - show(cell); -// layingOut = false; - return; - } - // See if the next index is a visible cell - T next = getVisibleCell(index + 1); - if (next != null) { -// layingOut = true; - cell = getAvailableCell(index); - setCellIndex(cell, index); - resizeCellSize(cell); // resize must be after config - cells.addFirst(cell); - positionCell(cell, getCellPosition(next) - getCellLength(cell)); - setMaxPrefBreadth(Math.max(getMaxPrefBreadth(), getCellBreadth(cell))); - cell.setVisible(true); - show(cell); -// layingOut = false; - return; - } - - // In this case, we're asked to show a random cell -// layingOut = true; - adjustPositionToIndex(index); - addAllToPile(); - requestLayout(); -// layingOut = false; - } - } - - public void scrollTo(int index) { - boolean posSet = false; - - if (index >= cellCount - 1) { - setPosition(1); - posSet = true; - } else if (index < 0) { - setPosition(0); - posSet = true; - } - - if (!posSet) { - adjustPositionToIndex(index); - double offset = -computeOffsetForCell(index); - adjustByPixelAmount(offset); - } - - requestLayout(); - } - - //TODO We assume all the cell have the same length. We will need to support - // cells of different lengths. - public void scrollToOffset(int offset) { - adjustPixels(offset * getCellLength(0)); - } - - /** - * Given a delta value representing a number of pixels, this method attempts - * to move the VirtualFlow in the given direction (positive is down/right, - * negative is up/left) the given number of pixels. It returns the number of - * pixels actually moved. - */ - public double adjustPixels(final double delta) { - // Short cut this method for cases where nothing should be done - if (delta == 0) return 0; - - final boolean isVertical = isVertical(); - if (((isVertical && (tempVisibility ? !needLengthBar : !vbar.isVisible())) || - (!isVertical && (tempVisibility ? !needLengthBar : !hbar.isVisible())))) return 0; - - double pos = getPosition(); - if (pos == 0.0f && delta < 0) return 0; - if (pos == 1.0f && delta > 0) return 0; - - adjustByPixelAmount(delta); - if (pos == getPosition()) { - // The pos hasn't changed, there's nothing to do. This is likely - // to occur when we hit either extremity - return 0; - } - - // Now move stuff around. Translating by pixels fundamentally means - // moving the cells by the delta. However, after having - // done that, we need to go through the cells and see which cells, - // after adding in the translation factor, now fall off the viewport. - // Also, we need to add cells as appropriate to the end (or beginning, - // depending on the direction of travel). - // - // One simplifying assumption (that had better be true!) is that we - // will only make it this far in the function if the virtual scroll - // bar is visible. Otherwise, we never will pixel scroll. So as we go, - // if we find that the maxPrefBreadth exceeds the viewportBreadth, - // then we will be sure to show the breadthBar and update it - // accordingly. - if (cells.size() > 0) { - double position; - for (int i = 0; i < cells.size(); i++) { - T cell = cells.get(i); - assert cell != null; - position = getCellPosition(cell) - delta; - positionCell(cell, position); - } - - // Fix for RT-32908 - T firstCell = cells.getFirst(); - double layoutY = firstCell == null ? 0 : getCellPosition(firstCell); - for (int i = 0; i < cells.size(); i++) { - T cell = cells.get(i); - assert cell != null; - double actualLayoutY = getCellPosition(cell); - if (actualLayoutY != layoutY) { - // we need to shift the cell to layoutY - positionCell(cell, layoutY); - } - - layoutY += getCellLength(cell); - } - // end of fix for RT-32908 - cull(); - firstCell = cells.getFirst(); - - // Add any necessary leading cells - if (firstCell != null) { - int firstIndex = getCellIndex(firstCell); - double prevIndexSize = getCellLength(firstIndex - 1); - addLeadingCells(firstIndex - 1, getCellPosition(firstCell) - prevIndexSize); - } else { - int currentIndex = computeCurrentIndex(); - - // The distance from the top of the viewport to the top of the - // cell for the current index. - double offset = -computeViewportOffset(getPosition()); - - // Add all the leading and trailing cells (the call to add leading - // cells will add the current cell as well -- that is, the one that - // represents the current position on the mapper). - addLeadingCells(currentIndex, offset); - } - - // Starting at the tail of the list, loop adding cells until - // all the space on the table is filled up. We want to make - // sure that we DO NOT add empty trailing cells (since we are - // in the full virtual case and so there are no trailing empty - // cells). - if (!addTrailingCells(false)) { - // Reached the end, but not enough cells to fill up to - // the end. So, remove the trailing empty space, and translate - // the cells down - final T lastCell = getLastVisibleCell(); - final double lastCellSize = getCellLength(lastCell); - final double cellEnd = getCellPosition(lastCell) + lastCellSize; - final double viewportLength = getViewportLength(); - - if (cellEnd < viewportLength) { - // Reposition the nodes - double emptySize = viewportLength - cellEnd; - for (int i = 0; i < cells.size(); i++) { - T cell = cells.get(i); - positionCell(cell, getCellPosition(cell) + emptySize); - } - setPosition(1.0f); - // fill the leading empty space - firstCell = cells.getFirst(); - int firstIndex = getCellIndex(firstCell); - double prevIndexSize = getCellLength(firstIndex - 1); - addLeadingCells(firstIndex - 1, getCellPosition(firstCell) - prevIndexSize); - } - } - } - - // Now throw away any cells that don't fit - cull(); - - // Finally, update the scroll bars - updateScrollBarsAndCells(false); - lastPosition = getPosition(); - - // notify - return delta; // TODO fake - } - - private boolean needsReconfigureCells = false; // when cell contents are the same - private boolean needsRecreateCells = false; // when cell factory changed - private boolean needsRebuildCells = false; // when cell contents have changed - private boolean needsCellsLayout = false; - private boolean sizeChanged = false; - private final BitSet dirtyCells = new BitSet(); - - public void reconfigureCells() { - needsReconfigureCells = true; - requestLayout(); - } - - public void recreateCells() { - needsRecreateCells = true; - requestLayout(); - } - - public void rebuildCells() { - needsRebuildCells = true; - requestLayout(); - } - - public void requestCellLayout() { - needsCellsLayout = true; - requestLayout(); - } - - public void setCellDirty(int index) { - dirtyCells.set(index); - requestLayout(); - } - - private static final double GOLDEN_RATIO_MULTIPLIER = 0.618033987; - - private double getPrefBreadth(double oppDimension) { - double max = getMaxCellWidth(10); - - // This primarily exists for the case where we do not want the breadth - // to grow to ensure a golden ratio between width and height (for example, - // when a ListView is used in a ComboBox - the width should not grow - // just because items are being added to the ListView) - if (oppDimension > -1) { - double prefLength = getPrefLength(); - max = Math.max(max, prefLength * GOLDEN_RATIO_MULTIPLIER); - } - - return max; - } - - private double getPrefLength() { - double sum = 0.0; - int rows = Math.min(10, cellCount); - for (int i = 0; i < rows; i++) { - sum += getCellLength(i); - } - return sum; - } - - @Override - protected double computePrefWidth(double height) { - double w = isVertical() ? getPrefBreadth(height) : getPrefLength(); - return w + vbar.prefWidth(-1); - } - - @Override - protected double computePrefHeight(double width) { - double h = isVertical() ? getPrefLength() : getPrefBreadth(width); - return h + hbar.prefHeight(-1); - } - - double getMaxCellWidth(int rowsToCount) { - double max = 0.0; - - // we always measure at least one row - int rows = Math.max(1, rowsToCount == -1 ? cellCount : rowsToCount); - for (int i = 0; i < rows; i++) { - max = Math.max(max, getCellBreadth(i)); - } - return max; - } - - - // Old PositionMapper - - /** - * Given a position value between 0 and 1, compute and return the viewport - * offset from the "current" cell associated with that position value. - * That is, if the return value of this function where used as a translation - * factor for a sheet that contained all the items, then the current - * item would end up positioned correctly. - */ - private double computeViewportOffset(double position) { - double p = com.sun.javafx.util.Utils.clamp(0, position, 1); - double fractionalPosition = p * getCellCount(); - int cellIndex = (int) fractionalPosition; - double fraction = fractionalPosition - cellIndex; - double cellSize = getCellLength(cellIndex); - double pixelOffset = cellSize * fraction; - double viewportOffset = getViewportLength() * p; - return pixelOffset - viewportOffset; - } - - private void adjustPositionToIndex(int index) { - int cellCount = getCellCount(); - if (cellCount <= 0) { - setPosition(0.0f); - } else { - setPosition(((double) index) / cellCount); - } - } - - /** - * Adjust the position based on a delta of pixels. If negative, then the - * position will be adjusted negatively. If positive, then the position will - * be adjusted positively. If the pixel amount is too great for the range of - * the position, then it will be clamped such that position is always - * strictly between 0 and 1 - */ - private void adjustByPixelAmount(double numPixels) { - if (numPixels == 0) return; - // Starting from the current cell, we move in the direction indicated - // by numPixels one cell at a team. For each cell, we discover how many - // pixels the "position" line would move within that cell, and adjust - // our count of numPixels accordingly. When we come to the "final" cell, - // then we can take the remaining number of pixels and multiply it by - // the "travel rate" of "p" within that cell to get the delta. Add - // the delta to "p" to get position. - - // get some basic info about the list and the current cell - boolean forward = numPixels > 0; - int cellCount = getCellCount(); - double fractionalPosition = getPosition() * cellCount; - int cellIndex = (int) fractionalPosition; - if (forward && cellIndex == cellCount) return; - double cellSize = getCellLength(cellIndex); - double fraction = fractionalPosition - cellIndex; - double pixelOffset = cellSize * fraction; - - // compute the percentage of "position" that represents each cell - double cellPercent = 1.0 / cellCount; - - // To help simplify the algorithm, we pretend as though the current - // position is at the beginning of the current cell. This reduces some - // of the corner cases and provides a simpler algorithm without adding - // any overhead to performance. - double start = computeOffsetForCell(cellIndex); - double end = cellSize + computeOffsetForCell(cellIndex + 1); - - // We need to discover the distance that the fictional "position line" - // would travel within this cell, from its current position to the end. - double remaining = end - start; - - // Keep track of the number of pixels left to travel - double n = forward ? - numPixels + pixelOffset - (getViewportLength() * getPosition()) - start - : -numPixels + end - (pixelOffset - (getViewportLength() * getPosition())); - - // "p" represents the most recent value for position. This is always - // based on the edge between two cells, except at the very end of the - // algorithm where it is added to the computed "p" offset for the final - // value of Position. - double p = cellPercent * cellIndex; - - // Loop over the cells one at a time until either we reach the end of - // the cells, or we find that the "n" will fall within the cell we're on - while (n > remaining && ((forward && cellIndex < cellCount - 1) || (!forward && cellIndex > 0))) { - if (forward) cellIndex++; - else cellIndex--; - n -= remaining; - cellSize = getCellLength(cellIndex); - start = computeOffsetForCell(cellIndex); - end = cellSize + computeOffsetForCell(cellIndex + 1); - remaining = end - start; - p = cellPercent * cellIndex; - } - - // if remaining is < n, then we must have hit an end, so as a - // fast path, we can just set position to 1.0 or 0.0 and return - // because we know we hit the end - if (n > remaining) { - setPosition(forward ? 1.0f : 0.0f); - } else if (forward) { - double rate = cellPercent / Math.abs(end - start); - setPosition(p + (rate * n)); - } else { - double rate = cellPercent / Math.abs(end - start); - setPosition((p + cellPercent) - (rate * n)); - } - } - - private int computeCurrentIndex() { - return (int) (getPosition() * getCellCount()); - } - - /** - * Given an item index, this function will compute and return the viewport - * offset from the beginning of the specified item. Notice that because each - * item has the same percentage of the position dedicated to it, and since - * we are measuring from the start of each item, this is a very simple - * calculation. - */ - private double computeOffsetForCell(int itemIndex) { - double cellCount = getCellCount(); - double p = com.sun.javafx.util.Utils.clamp(0, itemIndex, cellCount) / cellCount; - return -(getViewportLength() * p); - } - -// /** -// * Adjust the position based on a chunk of pixels. The position is based -// * on the start of the scrollbar position. -// */ -// private void adjustByPixelChunk(double numPixels) { -// setPosition(0); -// adjustByPixelAmount(numPixels); -// } - // end of old PositionMapper code - - - /** - * A simple extension to Region that ensures that anything wanting to flow - * outside of the bounds of the Region is clipped. - */ - static class ClippedContainer extends Region { - - /** - * The Node which is embedded within this {@code ClipView}. - */ - private Node node; - - public Node getNode() { - return this.node; - } - - public void setNode(Node n) { - this.node = n; - - getChildren().clear(); - getChildren().add(node); - } - - public void setClipX(double clipX) { - setLayoutX(-clipX); - clipRect.setLayoutX(clipX); - } - - public void setClipY(double clipY) { - setLayoutY(-clipY); - clipRect.setLayoutY(clipY); - } - - private final Rectangle clipRect; - - public ClippedContainer(final VirtualPageFlow flow) { - if (flow == null) { - throw new IllegalArgumentException("VirtualFlow can not be null"); - } - - getStyleClass().add("clipped-container"); - - // clipping - clipRect = new Rectangle(); - clipRect.setSmooth(false); - setClip(clipRect); - // --- clipping - - super.widthProperty().addListener(valueModel -> { - clipRect.setWidth(getWidth()); - }); - super.heightProperty().addListener(valueModel -> { - clipRect.setHeight(getHeight()); - }); - } - } - - /** - * A List-like implementation that is exceedingly efficient for the purposes - * of the VirtualFlow. Typically there is not much variance in the number of - * cells -- it is always some reasonably consistent number. Yet for efficiency - * in code, we like to use a linked list implementation so as to append to - * start or append to end. However, at times when we need to iterate, LinkedList - * is expensive computationally as well as requiring the construction of - * temporary iterators. - *

- * This linked list like implementation is done using an array. It begins by - * putting the first item in the center of the allocated array, and then grows - * outward (either towards the first or last of the array depending on whether - * we are inserting at the head or tail). It maintains an index to the start - * and end of the array, so that it can efficiently expose iteration. - *

- * This class is package private solely for the sake of testing. - */ - public static class ArrayLinkedList extends AbstractList { - /** - * The array list backing this class. We default the size of the array - * list to be fairly large so as not to require resizing during normal - * use, and since that many ArrayLinkedLists won't be created it isn't - * very painful to do so. - */ - private final ArrayList array; - - private int firstIndex = -1; - private int lastIndex = -1; - - public ArrayLinkedList() { - array = new ArrayList(50); - - for (int i = 0; i < 50; i++) { - array.add(null); - } - } - - public T getFirst() { - return firstIndex == -1 ? null : array.get(firstIndex); - } - - public T getLast() { - return lastIndex == -1 ? null : array.get(lastIndex); - } - - public void addFirst(T cell) { - // if firstIndex == -1 then that means this is the first item in the - // list and we need to initialize firstIndex and lastIndex - if (firstIndex == -1) { - firstIndex = lastIndex = array.size() / 2; - array.set(firstIndex, cell); - } else if (firstIndex == 0) { - // we're already at the head of the array, so insert at position - // 0 and then increment the lastIndex to compensate - array.add(0, cell); - lastIndex++; - } else { - // we're not yet at the head of the array, so insert at the - // firstIndex - 1 position and decrement first position - array.set(--firstIndex, cell); - } - } - - public void addLast(T cell) { - // if lastIndex == -1 then that means this is the first item in the - // list and we need to initialize the firstIndex and lastIndex - if (firstIndex == -1) { - firstIndex = lastIndex = array.size() / 2; - array.set(lastIndex, cell); - } else if (lastIndex == array.size() - 1) { - // we're at the end of the array so need to "add" so as to force - // the array to be expanded in size - array.add(++lastIndex, cell); - } else { - array.set(++lastIndex, cell); - } - } - - public int size() { - return firstIndex == -1 ? 0 : lastIndex - firstIndex + 1; - } - - public boolean isEmpty() { - return firstIndex == -1; - } - - public T get(int index) { - if (index > (lastIndex - firstIndex) || index < 0) { - // Commented out exception due to RT-29111 - // throw new java.lang.ArrayIndexOutOfBoundsException(); - return null; - } - - return array.get(firstIndex + index); - } - - public void clear() { - for (int i = 0; i < array.size(); i++) { - array.set(i, null); - } - - firstIndex = lastIndex = -1; - } - - public T removeFirst() { - if (isEmpty()) return null; - return remove(0); - } - - public T removeLast() { - if (isEmpty()) return null; - return remove(lastIndex - firstIndex); - } - - public T remove(int index) { - if (index > lastIndex - firstIndex || index < 0) { - throw new java.lang.ArrayIndexOutOfBoundsException(); - } - - // if the index == 0, then we're removing the first - // item and can simply set it to null in the array and increment - // the firstIndex unless there is only one item, in which case - // we have to also set first & last index to -1. - if (index == 0) { - T cell = array.get(firstIndex); - array.set(firstIndex, null); - if (firstIndex == lastIndex) { - firstIndex = lastIndex = -1; - } else { - firstIndex++; - } - return cell; - } else if (index == lastIndex - firstIndex) { - // if the index == lastIndex - firstIndex, then we're removing the - // last item and can simply set it to null in the array and - // decrement the lastIndex - T cell = array.get(lastIndex); - array.set(lastIndex--, null); - return cell; - } else { - // if the index is somewhere in between, then we have to remove the - // item and decrement the lastIndex - T cell = array.get(firstIndex + index); - array.set(firstIndex + index, null); - for (int i = (firstIndex + index + 1); i <= lastIndex; i++) { - array.set(i - 1, array.get(i)); - } - array.set(lastIndex--, null); - return cell; - } - } - } - - Timeline sbTouchTimeline; - KeyFrame sbTouchKF1; - KeyFrame sbTouchKF2; - - private boolean needBreadthBar; - private boolean needLengthBar; - private boolean tempVisibility = false; - - protected void startSBReleasedAnimation() { - if (sbTouchTimeline == null) { - /* - ** timeline to leave the scrollbars visible for a short - ** while after a scroll/drag - */ - sbTouchTimeline = new Timeline(); - sbTouchKF1 = new KeyFrame(Duration.millis(0), event -> { - tempVisibility = true; - requestLayout(); - }); - - sbTouchKF2 = new KeyFrame(Duration.millis(1000), event -> { - if (touchDetected == false && mouseDown == false) { - tempVisibility = false; - requestLayout(); - } - }); - sbTouchTimeline.getKeyFrames().addAll(sbTouchKF1, sbTouchKF2); - } - sbTouchTimeline.playFromStart(); - } - - protected void scrollBarOn() { - tempVisibility = true; - requestLayout(); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualScrollBar.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualScrollBar.java deleted file mode 100644 index 2319ba557..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/scene/control/skin/VirtualScrollBar.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.icepdf.fx.scene.control.skin; - - -import com.icepdf.fx.scene.control.PageView; -import com.sun.javafx.util.Utils; -import javafx.scene.control.ScrollBar; - -/** - * This custom ScrollBar is used to map the increment & decrement features - * to pixel based scrolling rather than thumb/track based scrolling, if the - * "virtual" attribute is true. - */ -public class VirtualScrollBar extends ScrollBar { - private final VirtualPageFlow flow; - - private boolean virtual; - - private boolean adjusting; - - public VirtualScrollBar(final VirtualPageFlow flow) { - this.flow = flow; - - super.valueProperty().addListener(valueModel -> { - if (isVirtual()/* && oldValue != newValue*/) { - if (adjusting) { - // no-op - } else { - flow.setPosition(getValue()); - } - } - }); - } - - public void setVirtual(boolean virtual) { - this.virtual = virtual; - } - - public boolean isVirtual() { - return this.virtual; - } - - @Override - public void decrement() { - if (isVirtual()) { - flow.adjustPixels(-10); - } else { - super.decrement(); - } - } - - @Override - public void increment() { - if (isVirtual()) { - flow.adjustPixels(10); - } else { - super.increment(); - } - } - -// private double lastAdjustValue = 0.0; - - // this method is called when the user clicks in the scrollbar track, so - // we special-case it to allow for page-up and page-down clicking to work - // as expected. - @Override - public void adjustValue(double pos) { - if (isVirtual()) { -// if (pos == lastAdjustValue) { -// return; -// } - - adjusting = true; - double oldValue = flow.getPosition(); - - double newValue = ((getMax() - getMin()) * Utils.clamp(0, pos, 1)) + getMin(); - if (newValue < oldValue) { - PageView cell = flow.getFirstVisibleCell(); - if (cell == null) return; - flow.showAsLast(cell); - } else if (newValue > oldValue) { - PageView cell = flow.getLastVisibleCell(); - if (cell == null) return; - flow.showAsFirst(cell); - } -// lastAdjustValue = pos; - - adjusting = false; - } else { - super.adjustValue(pos); - } - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/util/ImageLoader.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/util/ImageLoader.java deleted file mode 100644 index 71918107b..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/util/ImageLoader.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.icepdf.fx.util; - -import javafx.scene.image.Image; - -/** - * Aid for loading image from image resources path. - */ -public class ImageLoader { - - public static Image loadImage(String filename) { - return new Image(filename.getClass().getResourceAsStream( - "/com/icepdf/fx/images/" + filename)); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/util/SettingsLoaderTask.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/util/SettingsLoaderTask.java deleted file mode 100644 index a397acf2f..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/util/SettingsLoaderTask.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.icepdf.fx.util; - -import com.icepdf.core.util.FontPropertiesManager; -import com.icepdf.core.util.PropertiesManager; -import javafx.concurrent.Task; - -import java.util.ResourceBundle; - -/** - * - */ -public class SettingsLoaderTask extends Task { - - @Override - protected Object call() throws Exception { - ResourceBundle messageBundle = ResourceBundle.getBundle( - PropertiesManager.DEFAULT_MESSAGE_BUNDLE); - FontPropertiesManager fontPropertiesManager = FontPropertiesManager.getInstance(); - - if (fontPropertiesManager.isPropertiesEmpty()) { - updateMessage(messageBundle.getString( - "icepdf.fx.common.utility.SettingsLoaderTask.loadingFonts.label")); - updateProgress(-1, 10); - fontPropertiesManager.readDefaultProperties(); - fontPropertiesManager.updateProperties(); - updateProgress(10, 10); - updateMessage(messageBundle.getString( - "icepdf.fx.common.utility.SettingsLoaderTask.loadingFontsComplete.label")); - } else { - updateMessage(messageBundle.getString( - "icepdf.fx.common.utility.SettingsLoaderTask.loadingSettings.label")); - fontPropertiesManager.loadProperties(); - for (int i = 0, max = 10; i < 10; i++) { - updateProgress(i + 1, max); - Thread.sleep(10); - } -// fontPropertiesManager.clearProperties(); - updateMessage(messageBundle.getString( - "icepdf.fx.common.utility.SettingsLoaderTask.loadingSettingsComplete.label")); - } - return null; - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/DocumentViewTest.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/DocumentViewTest.java deleted file mode 100644 index 3b44f1b51..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/DocumentViewTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.icepdf.fx.viewer; - -import com.icepdf.fx.scene.control.DocumentView; -import javafx.application.Application; -import javafx.scene.Scene; -import javafx.stage.Stage; -import org.icepdf.core.pobjects.Document; - -/** - * - */ -public class DocumentViewTest extends Application { - - public static void main(String[] args) throws Exception { - launch(args); - } - - @Override - public void start(Stage primaryStage) throws Exception { - - Document document = new Document(); - - document.setFile("d:\\pdf-qa\\pdf_reference_addendum_redaction.pdf"); - - Scene myScene = new Scene(new DocumentView(document)); - primaryStage.setScene(myScene); - - primaryStage.setWidth(600); - primaryStage.setHeight(400); - primaryStage.show(); - } -} \ No newline at end of file diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/Launcher.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/Launcher.java deleted file mode 100644 index db1b82cdd..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/Launcher.java +++ /dev/null @@ -1,208 +0,0 @@ -package com.icepdf.fx.viewer; - -import com.icepdf.core.util.PropertiesManager; -import com.icepdf.fx.scene.control.DocumentView; -import com.icepdf.fx.util.ImageLoader; -import javafx.collections.ObservableList; -import javafx.geometry.Rectangle2D; -import javafx.scene.Scene; -import javafx.scene.control.*; -import javafx.scene.layout.BorderPane; -import javafx.stage.Screen; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import org.icepdf.core.exceptions.PDFException; -import org.icepdf.core.exceptions.PDFSecurityException; -import org.icepdf.core.pobjects.Document; - -import java.io.IOException; -import java.net.URL; -import java.nio.file.Path; -import java.util.ResourceBundle; -import java.util.prefs.Preferences; - -/** - * Uses the build class to assemble full viewer RI. - */ -public class Launcher { - - private static Preferences prefs = Preferences.userNodeForPackage(Launcher.class); - private static String VIEWER_WINDOW_WIDTH = "last_used_width"; - private static String VIEWER_WINDOW_HEIGHT = "last_used_width"; - private static String VIEWER_WINDOW_X = "last_used_X"; - private static String VIEWER_WINDOW_Y = "last_used_Y"; - - private static Launcher launcher; - -// private PropertiesManager properties; - -// private ArrayList controllers; - -// private long newWindowInvocationCounter = 0; - -// private ResourceBundle messageBundle = null; - - private Launcher() { - } - - public static Launcher getInstance() { - if (launcher == null) { - launcher = new Launcher(); - } - return launcher; - } - - public Stage newViewerWindow() { - ResourceBundle messageBundle = ResourceBundle.getBundle( - PropertiesManager.DEFAULT_MESSAGE_BUNDLE); - - Stage mainStage = new Stage(StageStyle.DECORATED); - mainStage.setTitle(messageBundle.getString("icepdf.fx.viewer.ViewerPane.default.title")); - mainStage.getIcons().addAll( - ImageLoader.loadImage("app_icon_128x128.png"), - ImageLoader.loadImage("app_icon_64x64.png"), - ImageLoader.loadImage("app_icon_48x48.png"), - ImageLoader.loadImage("app_icon_32x32.png"), - ImageLoader.loadImage("app_icon_16x16.png")); - - - BorderPane borderPane = new BorderPane(); - - // zoom controls. - Slider zoomSlider = new Slider(0.05, 8, 1.0); - zoomSlider.setSnapToTicks(true); - zoomSlider.setMajorTickUnit(0.05); - zoomSlider.setBlockIncrement(0.05); - zoomSlider.setMinorTickCount(0); - zoomSlider.setShowTickLabels(false); - zoomSlider.setShowTickMarks(false); - Button zoomInButton = new Button("-"); - zoomInButton.setOnAction(event -> zoomSlider.decrement()); - Button zoomOutButton = new Button("+"); - zoomOutButton.setOnAction(event -> zoomSlider.increment()); - - // rotation controls - Slider rotationSlider = new Slider(-180, 180, 0); - rotationSlider.setSnapToTicks(true); - rotationSlider.setMajorTickUnit(15); - rotationSlider.setBlockIncrement(15); - rotationSlider.setMinorTickCount(0); - rotationSlider.setShowTickLabels(false); - rotationSlider.setShowTickMarks(false); - Button rotateLeftButton = new Button("-"); - rotateLeftButton.setOnAction(event -> rotationSlider.decrement()); - Button rotateRightButton = new Button("+"); - rotateRightButton.setOnAction(event -> rotationSlider.increment()); - - // view mode controls - - Document document = new Document(); - - try { -// document.setFile("E:\\pdf-qa\\metrics\\content-parser\\ottawa_cycling_map.pdf"); -// document.setFile("E:\\pdf-qa\\metrics\\content-parser\\map.pdf"); -// document.setFile("E:\\pdf-qa\\metrics\\content-parser\\SF_923200345630.pdf"); -// document.setFile("E:\\pdf-qa\\PDF32000_2008.pdf"); -// document.setFile("d:\\pdf-qa\\fonts\\cid\\R&D-05-Carbon.pdf"); - document.setFile("d:\\pdf-qa\\metrics\\full-monty\\fcom.pdf"); -// document.setFile("E:\\pdf-qa\\metrics\\full-monty\\ACEASPA_LIBGIO_2013_00001_53923_000001_0703467_V3.pdf"); - } catch (PDFException e) { - e.printStackTrace(); - } catch (PDFSecurityException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - // main view model - DocumentView documentView = new DocumentView(document); - - // setup scale binding for touch and wheel mouse interaction - documentView.scaleProperty().bindBidirectional(zoomSlider.valueProperty()); - documentView.scaleIncrementValueProperty().bind(zoomSlider.blockIncrementProperty()); - documentView.scaleMaxValueProperty().bind(zoomSlider.minProperty()); - documentView.scaleMaxValueProperty().bind(zoomSlider.maxProperty()); - - // rotation slider binding. - documentView.rotationProperty().bind(rotationSlider.valueProperty()); - borderPane.setCenter(documentView); - - // setup view mode controls. - - // but the toolbar together - ToolBar toolbar = new ToolBar(); - toolbar.getItems().addAll( - new Label("Scale"), zoomInButton, zoomSlider, zoomOutButton, - new Separator(), - new Label("Rotation"), rotateLeftButton, rotationSlider, rotateRightButton); - borderPane.setTop(toolbar); - - mainStage.setScene(new Scene(borderPane)); - calculateStageLocation(mainStage); - mainStage.setOnCloseRequest(event -> { - prefs.putDouble(VIEWER_WINDOW_X, mainStage.getX()); - prefs.putDouble(VIEWER_WINDOW_Y, mainStage.getY()); - prefs.putDouble(VIEWER_WINDOW_WIDTH, mainStage.getWidth()); - prefs.putDouble(VIEWER_WINDOW_HEIGHT, mainStage.getHeight()); - }); - mainStage.show(); - mainStage.toFront(); - - return mainStage; - } - - public Stage newViewerWindow(Path path) { - return newViewerWindow(); - } - - public Stage newViewerWindow(URL url) { - return newViewerWindow(); - } - - public void disposeViewerWindow(Object controller, Scene scene) { - - } - - public void minimiseAllViewerWindows() { - - } - -// public void bringAllViewerWindowsToFront(Object frontMost){ -// -// } -// -// public void bringWindowToFront(int index); - -// public List getWindowDocumentOriginList(Controller giveIndex); - - public void quit(Object controller, Scene viewer) { - - } - - private void calculateStageLocation(Stage stage) { - final Rectangle2D bounds = Screen.getPrimary().getBounds(); - - double width = prefs.getDouble(VIEWER_WINDOW_WIDTH, 800); - double height = prefs.getDouble(VIEWER_WINDOW_HEIGHT, 600); - - // default center for width on primary screen. - double x = bounds.getMinX() + bounds.getWidth() / 2 - width / 2; - double y = bounds.getMinY() + bounds.getHeight() / 2 - height / 2; - - double previousX = prefs.getDouble(VIEWER_WINDOW_X, x); - double previousY = prefs.getDouble(VIEWER_WINDOW_Y, y); - - // quick check to make sure the viewer will be visable in at least one screen, if not we default to primary - ObservableList screens = Screen.getScreensForRectangle(previousX, previousY, width, height); - if (screens.size() == 0) { - previousX = x; - previousY = y; - } - - stage.setWidth(width); - stage.setHeight(height); - stage.setX(previousX); - stage.setY(previousY); - } - -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/ListViewTest.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/ListViewTest.java deleted file mode 100644 index 487b6c072..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/ListViewTest.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.icepdf.fx.viewer; - -import javafx.application.Application; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javafx.scene.Scene; -import javafx.scene.control.ListView; -import javafx.stage.Stage; - -/** - * Created by pcorl_000 on 2017-03-30. - */ -public class ListViewTest extends Application { - - public static void main(String[] args) throws Exception { - launch(args); - } - - @Override - public void start(Stage primaryStage) throws Exception { - ListView listView = new ListView<>(); - ObservableList list = FXCollections.observableArrayList(); - for (int i = 0; i < 3; i++) { - list.add("string cell " + (i + 1)); - } - - listView.setItems(list); - - Scene myScene = new Scene(listView); - primaryStage.setScene(myScene); - - primaryStage.setWidth(600); - primaryStage.setHeight(400); - primaryStage.show(); - } -} diff --git a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/Main.java b/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/Main.java deleted file mode 100644 index 458645311..000000000 --- a/viewer/viewer-fx/src/main/java/com/icepdf/fx/viewer/Main.java +++ /dev/null @@ -1,256 +0,0 @@ -package com.icepdf.fx.viewer; - -import com.icepdf.core.util.PropertiesManager; -import com.icepdf.fx.util.ImageLoader; -import com.icepdf.fx.util.SettingsLoaderTask; -import javafx.animation.FadeTransition; -import javafx.application.Application; -import javafx.concurrent.Task; -import javafx.concurrent.Worker; -import javafx.geometry.Rectangle2D; -import javafx.scene.Scene; -import javafx.scene.control.Alert; -import javafx.scene.control.Label; -import javafx.scene.control.ProgressBar; -import javafx.scene.effect.DropShadow; -import javafx.scene.image.ImageView; -import javafx.scene.layout.BorderPane; -import javafx.scene.layout.Pane; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; -import javafx.scene.paint.Color; -import javafx.stage.Screen; -import javafx.stage.Stage; -import javafx.stage.StageStyle; -import javafx.util.Duration; - -import java.net.HttpURLConnection; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.InvalidPathException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.text.MessageFormat; -import java.util.List; -import java.util.Map; -import java.util.ResourceBundle; -import java.util.logging.Logger; - -/** - * The main class is responsible for displaying the splash screen and doing any settings loading on a separate thread. - * When the viewer ri is launched for the first time the font manager will read the systems fonts which can take - * quite a bit of time at which a indeterminate progress bar is used to show that the scan is taking place. - * - * @since 6.5 - */ -public class Main extends Application { - - private static final Logger logger = Logger.getLogger(Main.class.toString()); - - private static final int SPLASH_WIDTH = 640; - private static final int SPLASH_HEIGHT = 400; - private static final String ARGS_FILE_PARAMTER = "-loadfile"; - private static final String ARGS_URL_PARAMTER = "-loadurl"; - private static final String JNLP_FILE_PARAMTER = "loadfile"; - private static final String JNLP_URL_PARAMTER = "Loadurl"; - - private static ResourceBundle messageBundle; - - private Pane splashPane; - private ProgressBar loadProgress; - private Label progressText; - private Stage initStage; - - public static void main(String[] args) throws Exception { - launch(args); - } - - @Override - public void init() { - // load message bundle - messageBundle = ResourceBundle.getBundle( - PropertiesManager.DEFAULT_MESSAGE_BUNDLE); - - splashPane = new StackPane(); - splashPane.getStylesheets().add( - getClass().getResource(PropertiesManager.DEFAULT_SPLASH_CSS).toExternalForm()); - ImageView splashImageView = new ImageView(); - splashImageView.setId("splash-image"); - loadProgress = new ProgressBar(); - loadProgress.setId("progress-bar"); - progressText = new Label("Will find friends for peanuts . . ."); - progressText.getStyleClass().add("progress-label"); - - BorderPane splashContentPane = new BorderPane(); - splashPane.getChildren().addAll(splashImageView, splashContentPane); - - // padding is used to move the progress bar to desired location - Pane progressPane = new VBox(); - progressPane.getStyleClass().add("progress-pane"); - progressPane.getChildren().addAll(progressText, loadProgress); - splashContentPane.setCenter(progressPane); - - // licence values. - Pane licencePane = new VBox(); - licencePane.getStyleClass().add("license-pane"); - Label copyrightLabel = new Label(messageBundle.getString("icepdf.fx.viewer.Main.copyRight.label")); - copyrightLabel.getStyleClass().add("copyright-label"); - licencePane.getChildren().addAll(copyrightLabel); - splashContentPane.setBottom(licencePane); - splashPane.getStyleClass().add("splash-layout"); - splashPane.setEffect(new DropShadow()); - } - - @Override - public void start(final Stage initStage) throws Exception { - this.initStage = initStage; - - initStage.getIcons().addAll( - ImageLoader.loadImage("app_icon_128x128.png"), - ImageLoader.loadImage("app_icon_64x64.png"), - ImageLoader.loadImage("app_icon_48x48.png"), - ImageLoader.loadImage("app_icon_32x32.png"), - ImageLoader.loadImage("app_icon_16x16.png")); - SettingsLoaderTask fontReaderTask = new SettingsLoaderTask(); - showSplash(initStage, fontReaderTask, - this::showMainStage); - new Thread(fontReaderTask).start(); - } - - private void showMainStage() { - - Launcher launcher = Launcher.getInstance(); - // check for -loadfile or -loadurl. - Map jnlpArgs = getParameters().getNamed(); - List args = getParameters().getUnnamed(); - if (jnlpArgs.size() > 0) { - processJNLPArguments(launcher, jnlpArgs); - } else if (args.size() > 0) { - processArguments(launcher, args); - } else { - launcher.newViewerWindow(); - } - } - - private void showSplash( - final Stage initStage, - Task task, - InitCompletionHandler initCompletionHandler) { - progressText.textProperty().bind(task.messageProperty()); - loadProgress.progressProperty().bind(task.progressProperty()); - task.stateProperty().addListener((observableValue, oldState, newState) -> { - if (newState == Worker.State.SUCCEEDED) { - loadProgress.progressProperty().unbind(); - loadProgress.setProgress(1); - initStage.toFront(); - FadeTransition fadeSplash = new FadeTransition(Duration.seconds(1.2), splashPane); - fadeSplash.setFromValue(1.0); - fadeSplash.setToValue(0.0); - fadeSplash.setOnFinished(actionEvent -> initStage.hide()); - fadeSplash.play(); - // setup callback to start the main application state - initCompletionHandler.complete(); - } - }); - // center the splash screen on the primary monitory - Scene splashScene = new Scene(splashPane, Color.TRANSPARENT); - initStage.initStyle(StageStyle.TRANSPARENT); -// initStage.setAlwaysOnTop(true); - initStage.setScene(splashScene); - final Rectangle2D bounds = Screen.getPrimary().getBounds(); - initStage.setX(bounds.getMinX() + bounds.getWidth() / 2 - SPLASH_WIDTH / 2); - initStage.setY(bounds.getMinY() + bounds.getHeight() / 2 - SPLASH_HEIGHT / 2); - initStage.initStyle(StageStyle.TRANSPARENT); - initStage.show(); - } - - public interface InitCompletionHandler { - void complete(); - } - - private void processJNLPArguments(Launcher launcher, Map jnlpArgs) { - String file = null; - String url = null; - if (jnlpArgs.containsKey(JNLP_FILE_PARAMTER)) { - file = jnlpArgs.get(JNLP_FILE_PARAMTER).trim(); - } else if (jnlpArgs.containsKey(JNLP_URL_PARAMTER)) { - url = jnlpArgs.get(JNLP_URL_PARAMTER).trim(); - } - // load default empty viewer. - if (file == null && url == null) { - launcher.newViewerWindow(); - } - if (file != null) { - checkAndLaunchFile(launcher, file); - } - if (url != null) { - checkAndLaunchUrl(launcher, url); - } - } - - private void processArguments(Launcher launcher, List args) { - if (args.size() == 2) { - String command = args.get(0); - String uri = args.get(1); - if (ARGS_FILE_PARAMTER.equals(command)) { - checkAndLaunchFile(launcher, uri); - } else if (ARGS_URL_PARAMTER.equals(command)) { - checkAndLaunchUrl(launcher, uri); - } - } else { - launcher.newViewerWindow(); - } - } - - private void checkAndLaunchFile(Launcher launcher, String file) { - try { - Path filePath = Paths.get(file); - boolean exists = Files.exists(filePath); - if (exists) { - launcher.newViewerWindow(filePath); - } else { - throw new InvalidPathException(file, "Not found. "); - } - } catch (InvalidPathException e) { - logger.warning("Could not load file path: " + file); - this.initStage = launcher.newViewerWindow(); - Alert alert = new Alert(Alert.AlertType.WARNING); - alert.setTitle(messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadError.title")); - alert.setHeaderText(messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadUriError.header")); - MessageFormat formatter = new MessageFormat( - messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadUriError.content")); - messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadUriError.content"); - alert.setContentText(formatter.format(new Object[]{file})); - alert.initOwner(initStage); - alert.showAndWait(); - } - } - - private void checkAndLaunchUrl(Launcher launcher, String url) { - try { - URL fileUrl = new URL(url); - HttpURLConnection.setFollowRedirects(false); - HttpURLConnection con = (HttpURLConnection) fileUrl.openConnection(); - con.setInstanceFollowRedirects(false); - con.setRequestMethod("HEAD"); - if (con.getResponseCode() == HttpURLConnection.HTTP_OK) { - launcher.newViewerWindow(fileUrl); - } else { - throw new IllegalStateException(); - } - con.disconnect(); - } catch (Exception e) { - logger.warning("Could not load url: " + url); - this.initStage = launcher.newViewerWindow(); - Alert alert = new Alert(Alert.AlertType.WARNING); - alert.setTitle(messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadError.title")); - alert.setHeaderText(messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadUriError.header")); - MessageFormat formatter = new MessageFormat( - messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadUriError.content")); - messageBundle.getString("icepdf.fx.viewer.Main.dialog.loadUriError.content"); - alert.setContentText(formatter.format(new Object[]{url})); - alert.initOwner(initStage); - alert.showAndWait(); - } - } -} \ No newline at end of file diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/FxController.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/FxController.java new file mode 100644 index 000000000..37c8519ca --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/FxController.java @@ -0,0 +1,28 @@ +package org.icepdf.fx.ri.viewer; + +import javafx.scene.layout.Region; +import org.icepdf.fx.ri.viewer.listeners.DocumentChangeListener; + +public class FxController { + + private final ViewerModel model; + private final Interactor interactor; + private final ViewBuilder viewBuilder; + + public FxController() { + this.model = new ViewerModel(); + this.interactor = new Interactor(model); // is this really a mediator? + this.viewBuilder = new ViewBuilder(model); + + // clean up this viewer if the document changes + this.model.document.addListener(new DocumentChangeListener(model)); + } + + public ViewerModel getModel() { + return model; + } + + public Region getView() { + return viewBuilder.build(); + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/Interactor.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/Interactor.java new file mode 100644 index 000000000..155232d1d --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/Interactor.java @@ -0,0 +1,34 @@ +package org.icepdf.fx.ri.viewer; + +public class Interactor { + + private final ViewerModel model; + private int changeCount = 0; +// private DomainObject domainObject; +// private Service service = new Service(); + + public Interactor(ViewerModel model) { + this.model = model; + createModelBindings(); + } + + private void createModelBindings() { +// model.bindProperty3(Bindings.createBooleanBinding(() -> !model.getProperty1().isEmpty(), model +// .property1Property())); + } + + public void updateModelAfterSave() { +// model.setProperty1(""); +// model.setProperty2(domainObject.getSomeValue()); +// changeCount = 0; + + } + + public void saveData() { +// domainObject = service.saveDataSomewhere(model.getProperty1() + " --> " + changeCount); + } + + public void updateChangeCount() { + changeCount++; + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/Launcher.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/Launcher.java new file mode 100644 index 000000000..b46ecddf0 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/Launcher.java @@ -0,0 +1,37 @@ +package org.icepdf.fx.ri.viewer; + +import javafx.application.Application; +import javafx.stage.Stage; + +import java.util.logging.Logger; + +public class Launcher extends Application { + + private static final Logger logger = Logger.getLogger(Launcher.class.toString()); + + + public static void main(String[] args) throws Exception { + Application.launch(args); + } + + + @Override + public void start(final Stage primaryStage) throws Exception { + + + // read stored system font properties. +// FontPropertiesManager.getInstance().loadOrReadSystemFonts(); + + // setup the viewer ri properties manager + + ViewerStageManager stageManager = ViewerStageManager.getInstance(); + stageManager.createViewer(primaryStage, null); + stageManager.setTitleAndIcons(primaryStage); + + primaryStage.show(); + + + } + + +} \ No newline at end of file diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewBuilder.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewBuilder.java new file mode 100644 index 000000000..be2d2e376 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewBuilder.java @@ -0,0 +1,56 @@ +package org.icepdf.fx.ri.viewer; + +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.ToolBar; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.Region; +import javafx.stage.Window; +import javafx.util.Builder; +import org.icepdf.fx.ri.viewer.commands.OpenFileCommand; +import org.icepdf.fx.ri.viewer.commands.ZoomInCommand; +import org.icepdf.fx.ri.views.DocumentViewPane; + +import java.util.function.Consumer; + +public class ViewBuilder implements Builder { + + private final ViewerModel model; + + private Consumer openDocumentActionHandler; + + DocumentViewPane documentViewPane = new DocumentViewPane(null); + + public ViewBuilder(ViewerModel model) { + this.model = model; + } + + @Override + public Region build() { + BorderPane borderPane = new BorderPane(); + borderPane.setTop(createToolbar()); + borderPane.setCenter(documentViewPane); + return borderPane; + } + + private ToolBar createToolbar() { + Button openDocument = new Button("Open Document"); + openDocument.setOnAction(event -> { + Node source = (Node) event.getSource(); + Window stage = source.getScene().getWindow(); + new OpenFileCommand(stage, model).execute(); + }); + + Button zoomOut = new Button("Zoom Out"); + zoomOut.setOnAction(event -> new ZoomInCommand(documentViewPane, model).execute()); + zoomOut.disableProperty().bind(model.toolbarDisabled); + + Button zoomIn = new Button("Zoom In"); + zoomIn.setOnAction(event -> new ZoomInCommand(documentViewPane, model).execute()); + zoomIn.disableProperty().bind(model.toolbarDisabled); + + ToolBar toolbar = new ToolBar(); + toolbar.getItems().addAll(openDocument, zoomOut, zoomIn); + return toolbar; + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewerModel.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewerModel.java new file mode 100644 index 000000000..bdf9c9e7e --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewerModel.java @@ -0,0 +1,27 @@ +package org.icepdf.fx.ri.viewer; + +import javafx.beans.property.*; +import org.icepdf.core.pobjects.Document; + +public class ViewerModel { + + public final BooleanProperty useSingleViewerStage = new SimpleBooleanProperty(false); + + // document + public final ObjectProperty document; + + // todo, pretty sure we don't need this property: file/url path + public final StringProperty filePath; + + // toolbar disabled state + public BooleanProperty toolbarDisabled; + + // zoom factor increment + public final FloatProperty zoomFactorIncrement = new SimpleFloatProperty(0.1f); + + public ViewerModel() { + document = new SimpleObjectProperty<>(null); + filePath = new SimpleStringProperty(null); + toolbarDisabled = new SimpleBooleanProperty(true); + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewerStageManager.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewerStageManager.java new file mode 100644 index 000000000..cb946b550 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/ViewerStageManager.java @@ -0,0 +1,48 @@ +package org.icepdf.fx.ri.viewer; + +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.stage.Stage; +import org.icepdf.core.pobjects.Document; +import org.icepdf.fx.ri.viewer.listeners.StageCloseRequestListener; + +public class ViewerStageManager { + + private static ViewerStageManager singleton; + + private ViewerStageManager() { + } + + // todo keep track of open stages + + public void createViewer(Stage stage, Document document) { + FxController controller = new FxController(); + controller.getModel().document.set(document); + Scene scene = new Scene(controller.getView(), 400, 200); + stage.setScene(scene); + stage.setOnCloseRequest(new StageCloseRequestListener(controller.getModel())); + } + + public Stage createViewerStage(Document document) { + Stage stage = new Stage(); + setTitleAndIcons(stage); + createViewer(stage, document); + return stage; + } + + public void setTitleAndIcons(Stage stage) { + stage.setTitle("Icepdf Viewer"); + stage.getIcons().addAll( + new Image(ViewerStageManager.class.getResourceAsStream( + "/org/icepdf/fx/images/icepdf-app-icon-32x32.png")), + new Image(ViewerStageManager.class.getResourceAsStream( + "/org/icepdf/fx/images/icepdf-app-icon-64x64.png"))); + } + + public static ViewerStageManager getInstance() { + if (singleton == null) { + singleton = new ViewerStageManager(); + } + return singleton; + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/WindowManager.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/WindowManager.java new file mode 100644 index 000000000..f77ac11a0 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/WindowManager.java @@ -0,0 +1,4 @@ +package org.icepdf.fx.ri.viewer; + +public class WindowManager { +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/Command.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/Command.java new file mode 100644 index 000000000..df14d2f05 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/Command.java @@ -0,0 +1,5 @@ +package org.icepdf.fx.ri.viewer.commands; + +public interface Command { + void execute(); +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/OpenFileCommand.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/OpenFileCommand.java new file mode 100644 index 000000000..52a3c377d --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/OpenFileCommand.java @@ -0,0 +1,54 @@ +package org.icepdf.fx.ri.viewer.commands; + +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import javafx.stage.Window; +import org.icepdf.core.exceptions.PDFSecurityException; +import org.icepdf.core.pobjects.Document; +import org.icepdf.fx.ri.viewer.ViewerModel; +import org.icepdf.fx.ri.viewer.ViewerStageManager; + +import java.io.File; +import java.io.IOException; + +public class OpenFileCommand implements Command { + + private final Window stage; + private final ViewerModel model; + + public OpenFileCommand(Window parentStage, ViewerModel model) { + this.model = model; + this.stage = parentStage; + } + + @Override + public void execute() { + // show file chooser + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Resource File"); + File file = fileChooser.showOpenDialog(stage); + if (file != null) { + // set document + try { + Document document = new Document(); + + if (model.useSingleViewerStage.get()) { + model.document.set(document); + model.filePath.set(file.getAbsolutePath()); + } else { + ViewerStageManager stageManager = ViewerStageManager.getInstance(); + Stage stage = stageManager.createViewerStage(document); + stageManager.setTitleAndIcons(stage); + stage.show(); + } + + // todo push of a thread to load the document, use Consumer to make sure UI is updated properly + document.setFile(file.getAbsolutePath()); + + } catch (PDFSecurityException | IOException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/ZoomInCommand.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/ZoomInCommand.java new file mode 100644 index 000000000..f113df4e4 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/ZoomInCommand.java @@ -0,0 +1,23 @@ +package org.icepdf.fx.ri.viewer.commands; + +import javafx.beans.property.FloatProperty; +import org.icepdf.fx.ri.viewer.ViewerModel; +import org.icepdf.fx.ri.views.DocumentViewPane; + +public class ZoomInCommand implements Command { + private final ViewerModel model; + private final DocumentViewPane documentViewPane; + + + public ZoomInCommand(DocumentViewPane documentViewPane, ViewerModel model) { + this.model = model; + this.documentViewPane = documentViewPane; + } + + @Override + public void execute() { + // get the current zoom level + FloatProperty scale = documentViewPane.scaleProperty(); + scale.set(scale.get() - model.zoomFactorIncrement.get()); + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/ZoomOutCommand.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/ZoomOutCommand.java new file mode 100644 index 000000000..ee67db322 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/commands/ZoomOutCommand.java @@ -0,0 +1,24 @@ +package org.icepdf.fx.ri.viewer.commands; + +import javafx.beans.property.FloatProperty; +import org.icepdf.fx.ri.viewer.ViewerModel; +import org.icepdf.fx.ri.views.DocumentViewPane; + +public class ZoomOutCommand implements Command { + private final ViewerModel model; + private final DocumentViewPane documentViewPane; + + + public ZoomOutCommand(DocumentViewPane documentViewPane, ViewerModel model) { + this.model = model; + this.documentViewPane = documentViewPane; + } + + @Override + public void execute() { + // get the current zoom level + FloatProperty scale = documentViewPane.scaleProperty(); + scale.set(scale.get() + model.zoomFactorIncrement.get()); + } +} + diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/listeners/DocumentChangeListener.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/listeners/DocumentChangeListener.java new file mode 100644 index 000000000..b6f689e51 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/listeners/DocumentChangeListener.java @@ -0,0 +1,27 @@ +package org.icepdf.fx.ri.viewer.listeners; + +import javafx.beans.value.ChangeListener; +import org.icepdf.core.pobjects.Document; +import org.icepdf.fx.ri.viewer.ViewerModel; + +public class DocumentChangeListener implements ChangeListener { + + private ViewerModel model; + + public DocumentChangeListener(ViewerModel model) { + this.model = model; + } + + @Override + public void changed(javafx.beans.value.ObservableValue observable, Document oldDocument, + Document newDocument) { + if (oldDocument != null) { + oldDocument.dispose(); + } + if (newDocument != null) { + model.toolbarDisabled.set(false); + } else { + model.toolbarDisabled.set(true); + } + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/listeners/StageCloseRequestListener.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/listeners/StageCloseRequestListener.java new file mode 100644 index 000000000..731423bfe --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/viewer/listeners/StageCloseRequestListener.java @@ -0,0 +1,26 @@ +package org.icepdf.fx.ri.viewer.listeners; + +import javafx.event.EventHandler; +import javafx.stage.WindowEvent; +import org.icepdf.core.pobjects.Document; +import org.icepdf.fx.ri.viewer.ViewerModel; + + +public class StageCloseRequestListener implements EventHandler { + + private final ViewerModel model; + + public StageCloseRequestListener(ViewerModel model) { + this.model = model; + } + + @Override + public void handle(WindowEvent event) { + if (event.getEventType() == WindowEvent.WINDOW_CLOSE_REQUEST) { + Document document = model.document.get(); + if (document != null) { + document.dispose(); + } + } + } +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/views/DocumentViewPane.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/views/DocumentViewPane.java new file mode 100644 index 000000000..aafc60243 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/views/DocumentViewPane.java @@ -0,0 +1,48 @@ +package org.icepdf.fx.ri.views; + +import javafx.beans.property.FloatProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.SimpleFloatProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.*; +import org.icepdf.core.pobjects.Document; + +public class DocumentViewPane extends Region { + + private FloatProperty scale = new SimpleFloatProperty(1.0f); + private FloatProperty rotation = new SimpleFloatProperty(0.0f); + private IntegerProperty currentPageIndex = new SimpleIntegerProperty(0); + + + public DocumentViewPane(Document document) { + createLayout(document); + } + + private void createLayout(Document document) { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setFitToWidth(true); + scrollPane.prefWidthProperty().bind(this.widthProperty()); + scrollPane.prefHeightProperty().bind(this.heightProperty()); + + TilePane tilePane = new TilePane(); + tilePane.setPrefColumns(1); + tilePane.setVgap(25); + tilePane.setHgap(25); + + // create a page view for each page + for (int i = 0; i < 250; i++) { + PageViewWidget pageViewPane = new PageViewWidget(i, scale, rotation); + pageViewPane.setBorder(new Border(new BorderStroke(null, BorderStrokeStyle.SOLID, null, + new BorderWidths(1)))); + tilePane.getChildren().add(pageViewPane); + } + scrollPane.setContent(tilePane); + getChildren().add(scrollPane); + } + + public FloatProperty scaleProperty() { + return scale; + } + +} diff --git a/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/views/PageViewWidget.java b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/views/PageViewWidget.java new file mode 100644 index 000000000..7073fe187 --- /dev/null +++ b/viewer/viewer-fx/src/main/java/org/icepdf/fx/ri/views/PageViewWidget.java @@ -0,0 +1,71 @@ +package org.icepdf.fx.ri.views; + +import javafx.beans.property.*; +import javafx.scene.control.Label; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; + +public class PageViewWidget extends Region { + + private FloatProperty scale; + private FloatProperty rotation; + private IntegerProperty pageIndex; + + private DoubleProperty width; + private DoubleProperty height; + + private Label pageIndexLabel; + private Label scaleLabel; + private Label rotationLabel; + + public PageViewWidget(int pageIndex, FloatProperty scale, FloatProperty rotation) { + this.pageIndex = new SimpleIntegerProperty(pageIndex); + this.scale = new SimpleFloatProperty(); + this.scale.bind(scale); + this.rotation = new SimpleFloatProperty(); + this.rotation.bind(rotation); + + width = new SimpleDoubleProperty(75); + height = new SimpleDoubleProperty(50); + + scale.addListener((observable, oldValue, newValue) -> { + width.set(75 * newValue.floatValue()); + height.set(50 * newValue.floatValue()); + }); + + pageIndexLabel = new Label("Page " + pageIndex); + scaleLabel = new Label(); + rotationLabel = new Label(); + createLayout(); + } + + private void createLayout() { + VBox vBox = new VBox(); + scaleLabel.textProperty().bind(scale.asString()); + rotationLabel.textProperty().bind(width.asString()); + vBox.getChildren().addAll(pageIndexLabel, scaleLabel, rotationLabel); + minWidthProperty().bind(width); + minHeightProperty().bind(height); + getChildren().add(vBox); + } + + public int getPageIndex() { + return pageIndex.get(); + } + + public float getScale() { + return scale.get(); + } + + public FloatProperty scaleProperty() { + return scale; + } + + public float getRotation() { + return rotation.get(); + } + + public FloatProperty rotationProperty() { + return rotation; + } +} diff --git a/viewer/viewer-fx/src/main/resources/org/icepdf/fx/images/icepdf-app-icon-32x32.png b/viewer/viewer-fx/src/main/resources/org/icepdf/fx/images/icepdf-app-icon-32x32.png new file mode 100644 index 000000000..f3c0774ba Binary files /dev/null and b/viewer/viewer-fx/src/main/resources/org/icepdf/fx/images/icepdf-app-icon-32x32.png differ diff --git a/viewer/viewer-fx/src/main/resources/org/icepdf/fx/images/icepdf-app-icon-64x64.png b/viewer/viewer-fx/src/main/resources/org/icepdf/fx/images/icepdf-app-icon-64x64.png new file mode 100644 index 000000000..a151c4e97 Binary files /dev/null and b/viewer/viewer-fx/src/main/resources/org/icepdf/fx/images/icepdf-app-icon-64x64.png differ