All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.drombler.commons.docking.fx.impl.DockingSplitPane Maven / Gradle / Ivy

There is a newer version: 1.0
Show newest version
/*
 *         COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Notice
 *
 * The contents of this file are subject to the COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL)
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.opensource.org/licenses/cddl1.txt
 *
 * The Original Code is Drombler.org. The Initial Developer of the
 * Original Code is Florian Brunner (Sourceforge.net user: puce).
 * Copyright 2012 Drombler.org. All Rights Reserved.
 *
 * Contributor(s): .
 */
package org.drombler.commons.docking.fx.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyObjectPropertyBase;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.Orientation;
import org.drombler.commons.docking.LayoutConstraintsDescriptor;
import org.drombler.commons.docking.fx.impl.skin.Stylesheets;
import org.drombler.commons.docking.spi.ShortPathPart;
import org.drombler.commons.docking.spi.SplitLevel;
import org.drombler.commons.fx.geometry.OrientationUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.softsmithy.lib.util.Positionable;
import org.softsmithy.lib.util.PositionableAdapter;
import org.softsmithy.lib.util.PositionableComparator;
import org.softsmithy.lib.util.Positionables;

/**
 *
 * @author puce
 */
public class DockingSplitPane extends DockingSplitPaneChildBase {

    private static final Logger LOG = LoggerFactory.getLogger(DockingSplitPane.class);

    private static final String DEFAULT_STYLE_CLASS = "docking-split-pane";
    private final int position;

    private final ObservableList dockingSplitPaneChildren = FXCollections.
            observableArrayList();
    private final ObservableList unmodifiableDockingSplitPaneChildren = FXCollections.
            unmodifiableObservableList(dockingSplitPaneChildren);

//    private final ChangeListener layoutConstraintsListener
//            = (observable, oldValue, newValue) -> recalculateLayoutConstraints();
    private final DockingSplitPaneManager manager;

    public DockingSplitPane(int position, SplitLevel logicalLevel) {
        super(true);
        this.position = position;
        this.manager = new DockingSplitPaneManager(logicalLevel);

        getStyleClass().setAll(DEFAULT_STYLE_CLASS);

        // TODO: correct?
//        dockingSplitPaneChildren.addListener(
//                (ListChangeListener.Change change) -> {
//                    while (change.next()) {
//                        if (change.wasAdded()) {
//                            change.getAddedSubList().forEach(child -> addChildListeners(child));
//                        }
//                        if (change.wasRemoved()) {
//                            change.getRemoved().forEach(child -> removeChildListeners(child));
//                        }
//                    }
//                    recalculateLayoutConstraints();
//                });
        dockingSplitPaneChildren.addListener(
                (ListChangeListener.Change c) -> recalculateLayoutConstraints());
        recalculateLayoutConstraints();

    }

    @Override
    public String getUserAgentStylesheet() {
        return Stylesheets.getDefaultStylesheet();
    }

//    @Override
//    public void requestLayout() {
//        LOG.debug("{}: requestLayout...", toString());
//        super.requestLayout(); //To change body of generated methods, choose Tools | Templates.
//    }
    public final Orientation getOrientation() {
        return orientationProperty().get();
    }

    public ReadOnlyObjectProperty orientationProperty() {
        return manager.orientationProperty();
    }

    public boolean isHorizontal() {
        return getOrientation().equals(Orientation.HORIZONTAL);
    }

    public boolean isVertical() {
        return getOrientation().equals(Orientation.VERTICAL);
    }

    /**
     * @return the position
     */
    public int getPosition() {
        return position;
    }

    public int getLevel() {
        return manager.getLevel();
    }

    /**
     * @return the logicalLevel
     */
    public int getLogicalLevel() {
        return manager.getLogicalLevel();
    }

    /**
     * @return the children
     */
    public ObservableList getDockingSplitPaneChildren() {
        return unmodifiableDockingSplitPaneChildren;
    }
//
//    private void addChildListeners(DockingSplitPaneChildBase child) {
//        if (child instanceof DockingSplitPane) {
//            ((DockingSplitPane) child).layoutConstraintsProperty().addListener(layoutConstraintsListener);
//        }
//    }
//
//    private void removeChildListeners(DockingSplitPaneChildBase child) {
//        if (child instanceof DockingSplitPane) {
//            ((DockingSplitPane) child).layoutConstraintsProperty().removeListener(layoutConstraintsListener);
//        }
//    }

    public void addDockingArea(DockingAreaPane dockingArea) {
        if (dockingArea.isVisualizable()) {
            manager.addDockingArea(dockingArea);
            if (LOG.isDebugEnabled()) {
                logShortPaths();
            }
        }
    }

    private void logShortPaths() {
        dockingSplitPaneChildren.stream().
                forEach((child) -> {
                    if (child instanceof DockingAreaPane) {
                        DockingAreaPane dockingArea = (DockingAreaPane) child;
                        LOG.debug("{} short path: {}", dockingArea, dockingArea.getShortPath());
                    } else {
                        if (child instanceof DockingSplitPane) {
                            DockingSplitPane dockingSplitPane = (DockingSplitPane) child;
                            // recursion
                            dockingSplitPane.logShortPaths();
                        } else {
                            LOG.debug("Unsupported child type '{}'", child.getClass());
                        }
                    }
                });
    }

    public void removeDockingArea(DockingAreaPane dockingArea) {
        if (dockingArea.isVisualized()) {
            manager.removeDockingArea(dockingArea);
            if (LOG.isDebugEnabled()) {
                logShortPaths();
            }
        }
    }

    private void recalculateLayoutConstraints() {
        double prefWidth = 0;
        double prefHeight = 0;
        for (DockingSplitPaneChildBase child : dockingSplitPaneChildren) {
            LayoutConstraintsDescriptor childLayoutConstraints = child.getLayoutConstraints();
            if (LayoutConstraintsDescriptor.isPreferred(prefWidth)
                    && (LayoutConstraintsDescriptor.isFlexible(childLayoutConstraints.getPrefWidth())
                    || childLayoutConstraints.getPrefWidth() > prefWidth)) {
                prefWidth = childLayoutConstraints.getPrefWidth();
            }
            if (LayoutConstraintsDescriptor.isPreferred(prefHeight)
                    && (LayoutConstraintsDescriptor.isFlexible(childLayoutConstraints.getPrefHeight())
                    || childLayoutConstraints.getPrefHeight() > prefHeight)) {
                prefHeight = childLayoutConstraints.getPrefHeight();

            }
            if (LayoutConstraintsDescriptor.isFlexible(prefWidth) && LayoutConstraintsDescriptor.isFlexible(prefHeight)) {
                break;
            }
        }
        final LayoutConstraintsDescriptor layoutConstraintsDescriptor
                = LayoutConstraintsDescriptor.getLayoutConstraints(prefWidth, prefHeight);
        setLayoutConstraints(layoutConstraintsDescriptor);
    }

    @Override
    public String toString() {
        return "DockingSplitPane[position=" + position + ", level=" + getLevel() + ", logicalLevel=" + getLogicalLevel() + ", orientation=" + getOrientation() + "]";
    }

    @Override
    public void updateLayoutConstraints() {
        dockingSplitPaneChildren.forEach(dockingSplitPaneChild -> dockingSplitPaneChild.updateLayoutConstraints());
        recalculateLayoutConstraints();
    }

    public class DockingSplitPaneManager {

        private final Map areaIdsInSplitPane = new HashMap<>();
        private final Map splitPanes = new HashMap<>();
        private final Map> areaIdsToAreaPaneMap = new HashMap<>();
        private final Map> areaPanes = new HashMap<>(); // TODO: PositionableAdapter needed?
        private final List positionableChildren = new ArrayList<>();
        private final ObservableList dockingSplitPaneChildren = FXCollections.
                observableArrayList();
        private final OrientationProperty orientation = new OrientationProperty();
        private int logicalLevel;

        public DockingSplitPaneManager(SplitLevel logicalLevel) {
            adjust(logicalLevel);
        }

        public void addDockingArea(DockingAreaPane dockingArea) {
            //            System.out.println(DockingSplitPane.class.getName() + ": adding docking area: " + dockingArea.getAreaId());
            List removedDockingAreas = new ArrayList<>();
            addDockingArea(dockingArea.getShortPath(), dockingArea, removedDockingAreas);

            reAddDockingAreas(removedDockingAreas);

            updateSplitPaneChildren();
        }

        private void addDockingArea(List path, DockingAreaPane dockingAreaPane,
                List removedDockingAreas) {
            int level = getLevel();
            if (level >= path.size()) {
                throw new IllegalStateException("Level is too high! Level=" + level + ", areaId="
                        + dockingAreaPane.getAreaId() + ", path=" + path);
            }
            ShortPathPart pathPart = path.get(level);
            adjust(pathPart.getInLogicalLevel(), removedDockingAreas);
            if (!isLastPathPart(path)) {
                int childLevel = level + 1;
                DockingSplitPane splitPane = getSplitPane(pathPart.getPosition(),
                        path.get(childLevel).getInLogicalLevel(), removedDockingAreas);
                // recursion
                splitPane.manager.addDockingArea(path, dockingAreaPane, removedDockingAreas);
                areaIdsInSplitPane.put(dockingAreaPane.getAreaId(), splitPane);
            } else {
                addDockingArea(pathPart.getPosition(), dockingAreaPane);
            }
        }

        private void adjust(SplitLevel splitLevel, List removedDockingAreas) {
            if (logicalLevel != splitLevel.getLevel()) {
                if (isEmpty()) {
                    adjust(splitLevel);
                } else {
                    removeAllDockingAreas(removedDockingAreas);
                    adjust(splitLevel);
                }
            }
        }

        private void adjust(SplitLevel splitLevel) {
            logicalLevel = splitLevel.getLevel();
            setOrientation(OrientationUtils.getOrientation(splitLevel.getOrientation()));
        }

        private DockingSplitPane getSplitPane(int position, SplitLevel inLogicalLevel,
                List removedDockingAreas) {
            if (!splitPanes.containsKey(position)) {
                if (areaPanes.containsKey(position)) {
                    removeDockingArea(position, removedDockingAreas);
                }
                DockingSplitPane splitPane = new DockingSplitPane(position, inLogicalLevel);
                addSplitPane(position, splitPane);
            }
            return splitPanes.get(position);
        }

        private void removeDockingArea(int position, List removedDockingAreas) {
            PositionableAdapter areaPane = areaPanes.get(position);
            removeDockingArea(areaPane, removedDockingAreas);
        }

        private void removeDockingArea(PositionableAdapter dockingArea,
                List removedAreaPanes) {
            removeDockingAreaOnly(dockingArea);
            removedAreaPanes.add(dockingArea.getAdapted());
        }

        private void addDockingArea(int position, DockingAreaPane dockingArea) {
            PositionableAdapter dockingAreaAdapter = new PositionableAdapter<>(dockingArea, position);
            // TODO: handle situatation if another child has the same position
            areaPanes.put(position, dockingAreaAdapter);
            areaIdsToAreaPaneMap.put(dockingArea.getAreaId(), dockingAreaAdapter);
            addChild(position, dockingArea);
            dockingArea.setVisualized(true);
//        System.out.println(DockingSplitPane.class.getName() + ": added docking area: " + dockingArea.getAreaId());
        }

        private void addSplitPane(int position, DockingSplitPane splitPane) {
            splitPanes.put(position, splitPane);
            addChild(position, splitPane);
        }

        private void addChild(int position, DockingSplitPaneChildBase child) {
            child.setParentSplitPane(DockingSplitPane.this);
            PositionableAdapter positionableChild
                    = new PositionableAdapter<>(child, position);
            int insertionPoint = Positionables.getInsertionPoint(positionableChildren, positionableChild);
            positionableChildren.add(insertionPoint, positionableChild);
            dockingSplitPaneChildren.add(insertionPoint, child);
        }

        private void reAddDockingAreas(List removedDockingAreas) throws IllegalStateException {
            for (DockingAreaPane removedDockingArea : removedDockingAreas) {
                List areas = new ArrayList<>();
                addDockingArea(removedDockingArea.getShortPath(), removedDockingArea, areas);
                if (!areas.isEmpty()) {
                    // TODO: should not happen (?) -> log?
                    throw new IllegalStateException();
                }
            }
            removeEmptySplitPanes(); // TODO needed?
        }

        private void removeEmptySplitPanes() {
            List dockingSplitPanes = new ArrayList<>(splitPanes.values()); // avoid ConcurrentModificationException
            dockingSplitPanes.stream().
                    map(splitPane -> {
                        // recursion
                        splitPane.manager.removeEmptySplitPanes();
                        return splitPane;
                    }).
                    filter(splitPane -> !splitPane.manager.containsAnyDockingAreas()).
                    forEach(splitPane -> removeSplitPane(splitPane));
        }

        public void removeDockingArea(DockingAreaPane dockingArea) {
            LOG.debug("{}: Start removing DockingArea: {}", DockingSplitPane.this, dockingArea);

            List removedAreaPanes = new ArrayList<>();
            removeDockingArea(dockingArea, removedAreaPanes);

            reAddDockingAreas(removedAreaPanes);

            updateSplitPaneChildren();

            LOG.debug("{}: End removing DockingArea: {}", DockingSplitPane.this, dockingArea);
        }

        private void removeDockingArea(DockingAreaPane dockingArea, List allRemovedAreaPanes) {
            LOG.debug("{}: remove DockingArea: {}", DockingSplitPane.this, dockingArea);
            if (areaIdsInSplitPane.containsKey(dockingArea.getAreaId())) {
                DockingSplitPane splitPane = areaIdsInSplitPane.remove(dockingArea.getAreaId());
                List removedAreaPanes = new ArrayList<>();
                // recursion
                splitPane.manager.removeDockingArea(dockingArea, removedAreaPanes);
                allRemovedAreaPanes.addAll(removedAreaPanes);

                removedAreaPanes.forEach((removedAreaPane) -> areaIdsInSplitPane.remove(removedAreaPane.getAreaId()));

                if (!splitPane.manager.containsAnyDockingAreas()) {
                    removeSplitPane(splitPane);
                }
            } else {
                if (!areaIdsToAreaPaneMap.containsKey(dockingArea.getAreaId())) {
                    throw new IllegalStateException("No area pane with areaId: " + dockingArea.getAreaId());
                }

                PositionableAdapter dockingAreaAdapter = areaIdsToAreaPaneMap.get(dockingArea.
                        getAreaId());
                removeDockingAreaOnly(dockingAreaAdapter);

                if (dockingSplitPaneChildren.size() == 1) {
                    // short paths might have changed -> re-add
                    removeAllDockingAreas(allRemovedAreaPanes);
                }
            }

            if (isRoot() && isEmpty()) {
                adjust(SplitLevel.ROOT);
            }
        }

        private void removeDockingAreaOnly(PositionableAdapter dockingArea) {
            areaPanes.remove(dockingArea.getPosition());
            areaIdsToAreaPaneMap.remove(dockingArea.getAdapted().getAreaId());
            removeChild(dockingArea);
            dockingArea.getAdapted().setVisualized(false);
        }

        private void removeSplitPane(DockingSplitPane splitPane) {
            splitPanes.remove(splitPane.getPosition());
            removeChild(new PositionableAdapter<>(splitPane, splitPane.getPosition()));
        }

        private void removeChild(PositionableAdapter dockingSplitPaneChild) {
            dockingSplitPaneChild.getAdapted().setParentSplitPane(null);
            int index = Collections.binarySearch(positionableChildren, dockingSplitPaneChild,
                    new PositionableComparator());
            positionableChildren.remove(index);
            dockingSplitPaneChildren.remove(index);
        }

        private void removeAllDockingAreas(List removedDockingAreas) {
            new ArrayList<>(areaPanes.values()).
                    forEach((dockingArea) -> removeDockingArea(dockingArea, removedDockingAreas));
            new ArrayList<>(splitPanes.values()).forEach((splitPane) -> {
                // recursion
                splitPane.manager.removeAllDockingAreas(removedDockingAreas);
            });
            areaIdsInSplitPane.clear();
        }

        private boolean isRoot() {
            return getParentSplitPane() == null;
        }

        private boolean isLastPathPart(List path) {
            return getLevel() == path.size() - 1;
        }

        /**
         * Checks if this DockingSplitPane contains any children.
         *
         * TODO: needed as public?
         *
         * @return true, if it contains no children, else false.
         */
        public boolean isEmpty() {
            return dockingSplitPaneChildren.isEmpty();
        }

        public final Orientation getOrientation() {
            return orientationProperty().get();
        }

        private void setOrientation(Orientation parentSplitPane) {
            orientation.set(parentSplitPane);
        }

        public ReadOnlyObjectProperty orientationProperty() {
            return orientation;
        }

        public int getLevel() {
            return isRoot() ? SplitLevel.ROOT.getLevel() : getParentSplitPane().getLevel() + 1;
        }

        public int getLogicalLevel() {
            return logicalLevel;
        }

        /**
         * Checks if this DockingSplitPane contains a DockingAreaPane or a child DockingSplitPane, which contains any docking area.
         *
         *
         * @return if this DockingSplitPane directly or indirectly contains any DockingAreaPane, else false.
         */
        private boolean containsAnyDockingAreas() {
            boolean contains = !areaPanes.isEmpty();
            if (!contains) {
                for (DockingSplitPane splitPane : splitPanes.values()) {
                    // recursion
                    contains = splitPane.manager.containsAnyDockingAreas();
                    if (contains) {
                        break;
                    }
                }
            }
            return contains;
        }

        private void updateSplitPaneChildren() {
            LOG.debug("{}: update SplitPane children...", DockingSplitPane.this);
            splitPanes.values().forEach(splitPane -> splitPane.manager.updateSplitPaneChildren());
            DockingSplitPane.this.dockingSplitPaneChildren.setAll(dockingSplitPaneChildren);
        }

        @Override
        public String toString() {
            return String.format("%s[dockingSplitPane=%s]", DockingSplitPaneManager.class.getSimpleName(),
                    DockingSplitPane.this);
        }

        private class OrientationProperty extends ReadOnlyObjectPropertyBase {

            private Orientation orientation = null;

            @Override
            public final Orientation get() {
                return orientation;
            }

            private void set(Orientation newValue) {
                if (!Objects.equals(orientation, newValue)) {
                    orientation = newValue;
                    fireValueChangedEvent();
                }
            }

            @Override
            public Object getBean() {
                return DockingSplitPaneManager.this;
            }

            @Override
            public String getName() {
                return "orientation";
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy