com.javadocking.dock.CompositeGridDock Maven / Gradle / Ivy
Show all versions of javadocking Show documentation
package com.javadocking.dock;
import com.javadocking.dock.factory.DockFactory;
import com.javadocking.dock.factory.SingleDockFactory;
import com.javadocking.dockable.CompositeDockable;
import com.javadocking.dockable.Dockable;
import com.javadocking.dockable.DockingMode;
import com.javadocking.event.ChildDockEvent;
import com.javadocking.event.DockingEventSupport;
import com.javadocking.event.DockingListener;
import com.javadocking.util.PropertiesUtil;
import com.javadocking.util.SwingUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.List;
/**
*
* This is a composite dock that has child docks that are organized in a grid.
* This dock can not contain dockables. When dockables are added, child docks are created and the
* dockables are added to the child docks.
*
*
* Information on using composite grid docks is in
* How to Use Composite Docks in
* The Sanaware Developer Guide.
*
*
* The positions for child docks of this dock are one-dimensional.
* The first position value of a child dock is between 0 and the number of child docks minus 1,
* it is the position in the grid.
*
*
* A dockable can be added to this dock if:
* - it has as possible docking mode {@link DockingMode#GRID}.
* - the dock factory can create a child dock for the given dockable.
* If the dockable is a {@link CompositeDockable}, but a child dock could not be created for the composite,
* the dockable can be added, if a child dock can be created for every child dockable of the composite.
*
*
*
* When a dockable is added, a child dock is created with the 'childDockFactory'. The dockable is added to
* the child dock.
*
*
* This kind of dock is never full. It is empty when there are 0 child docks.
*
*
* @author Heidi Rakels.
*/
public class CompositeGridDock extends JPanel implements CompositeDock {
// Static fields.
/**
* With this fill mode the number of rows and columns are equal or there is one more column.
*/
public static final int FILL_SQUARE_HORIZONTAL = 0;
/**
* With this fill mode the number of rows and columns are equal or there is one more row.
*/
public static final int FILL_SQUARE_VERTICAL = 1;
/**
* With this fill mode the number of columns and rows is calculated to fill the panel of the grid optimally.
* It takes the size of the panel and the preferred sizes of the child docks into account. There is a preference
* for first adding columns.
*/
public static final int FILL_FLOW_HORIZONTAL = 2;
/**
* With this fill mode the number of columns and rows is calculated to fill the panel of the grid optimally.
* It takes the size of the panel and the preferred sizes of the child docks into account. There is a preference
* for first adding rows.
*/
public static final int FILL_FLOW_VERTICAL = 3;
/**
* The relative offset of the center priority rectangle when there are no child docks.
*/
private static final double centerPriorityRectangleRelativeOffset = 2.0 / 8.0;
/**
* The relative offset of the center priority rectangle when there are no child docks.
*/
private static final double leftPriorityRectangleRelativeOffset = 1.0 / 8.0;
/**
* The relative offset of the center priority rectangle when there are no child docks.
*/
private static final double rightPriorityRectangleRelativeOffset = 1.0 / 8.0;
// Fields.
/**
* The parent of this dock.
*/
private CompositeDock parentDock;
/**
* The child docks of this dock.
*/
@NotNull
private List childDocks = new ArrayList();
/**
* This factory creates the child docks.
*/
@Nullable
private DockFactory childDockFactory;
/**
* The number of columns in the grid.
*/
private int columnCount = 1;
/**
* The fill mode determines the strategy for organizing the child docks in rows and columns.
* The default fill mode is FILL_FLOW_HORIZONTAL.
*/
private int fillMode = FILL_FLOW_HORIZONTAL;
/**
* This is the rectangle in which a dockable can be docked with priority.
* We keep it as field because we don't want to create every time a new rectangle.
*/
@NotNull
private Rectangle priorityRectangle = new Rectangle();
/**
* This is a rectangle for doing calculations.
* We keep it as field because we don't want to create every time a new rectangle.
*/
@NotNull
private Rectangle helpRectangle = new Rectangle();
/**
* This is the deepest panel that contains the child docks.
*/
private JPanel dockPanel;
/**
* The support for handling the docking events.
*/
@NotNull
private DockingEventSupport dockingEventSupport = new DockingEventSupport();
// Ghosts.
/**
* This is an old dockPanel
that has to be removed later. It is already made invisible.
* It cannot be removed now because it contains an old dock that still has listeners for dragging that are busy.
* We only want to lose the listeners when dragging is finished. This is only used with dynamic dragging.
*/
@Nullable
private JPanel ghostDockPanel;
// Constructors.
/**
* Constructs a composite grid dock with a {@link SingleDockFactory}
* as factory for creating the child docks.
*/
public CompositeGridDock() {
this(new SingleDockFactory());
}
/**
* Constructs a composite grid dock with the given factory for the creating child docks.
*
* @param childDockFactory The factory for creating the child docks.
*/
public CompositeGridDock(DockFactory childDockFactory) {
// Set the layout.
super(new BorderLayout());
// Set the properties.
this.childDockFactory = childDockFactory;
// Create and add the panel with the docks.
initializeUi(1);
}
// Implementations of Dock.
/**
*
* Determines if the dockable can be added.
*
*
* A dockable can be added in if:
*
* - the method {@link #checkDockingModes(Dockable)} returns
true
.
* - the dock factory can create a child dock for the given dockable.
* If the dockable is a {@link CompositeDockable}, but a child dock could not be created for the composite,
* the dockable can be added, if a child dock can be created for every child dockable of the composite.
*
*/
public int getDockPriority(@NotNull Dockable dockable, @NotNull Point relativeLocation) {
// Check if the dockable may be docked in a grid dock.
if (!checkDockingModes(dockable)) {
return Priority.CANNOT_DOCK;
}
// We can dock if the dock factory can create a dock.
if (childDockFactory.createDock(dockable, getDockingMode()) != null) {
// Can we dock with priority?
if (canAddDockableWithPriority(dockable, relativeLocation)) {
return Priority.CAN_DOCK_WITH_PRIORITY;
}
// We can dock, but not with priority.
return Priority.CAN_DOCK;
}
// Do we have a composite dockable?
if (dockable instanceof CompositeDockable) {
CompositeDockable compositeDockable = (CompositeDockable) dockable;
// We can dock if we can dock all the children.
for (int index = 0; index < compositeDockable.getDockableCount(); index++) {
Dockable childDockable = compositeDockable.getDockable(index);
// Can we create a dock for this dockable?
if (childDockFactory.createDock(childDockable, getDockingMode()) == null) {
return Priority.CANNOT_DOCK;
}
// Can this dockable be added in a line dock?
if (!checkDockingModes(childDockable)) {
return Priority.CANNOT_DOCK;
}
// Is the component of the dockable not null?
if (childDockable.getContent() == null) {
// We don't want deeper nested dockables.
return Priority.CANNOT_DOCK;
}
index++;
}
// If we are here, we can dock.
// Can we dock with priority?
if (canAddDockableWithPriority(dockable, relativeLocation)) {
return Priority.CAN_DOCK_WITH_PRIORITY;
}
// We can dock, but not with priority.
return Priority.CAN_DOCK;
}
return Priority.CANNOT_DOCK;
}
public int retrieveDockingRectangle(@NotNull Dockable dockable, @NotNull Point relativeLocation,
Point dockableOffset, @NotNull Rectangle rectangle) {
// Can we dock in this dock?
int priority = getDockPriority(dockable, relativeLocation);
if (priority != Priority.CANNOT_DOCK) {
// Are there no child docks already?
if (childDocks.size() == 0) {
// The docking rectangle is the rectangle defined by this dock panel.
rectangle.setBounds(0, 0, getSize().width, getSize().height);
} else {
// Get the position for the dockable.
int dockPosition = getDockPosition(dockable, relativeLocation);
if (dockPosition == childDocks.size()) {
Component childDock = (Component) childDocks.get(childDocks.size() - 1);
rectangle.setSize(childDock.getWidth(), childDock.getHeight());
rectangle.setLocation(childDock.getLocation().x + childDock.getWidth(), childDock.getLocation().y);
//TODO if it is not visible.
} else if (dockPosition == 0) {
Component childDock = (Component) childDocks.get(0);
rectangle.setSize(childDock.getWidth() / 2, childDock.getHeight());
rectangle.setLocation(childDock.getLocation().x, childDock.getLocation().y);
} else {
Component childDockBefore = (Component) childDocks.get(dockPosition - 1);
Component childDockAfter = (Component) childDocks.get(dockPosition);
if (childDockBefore.getLocation().x < childDockAfter.getLocation().x) {
rectangle.setSize(childDockBefore.getWidth() / 2 + childDockAfter.getWidth() / 2, childDockBefore.getHeight());
rectangle.setLocation(childDockBefore.getLocation().x + childDockBefore.getWidth() / 2, childDockBefore.getLocation().y);
} else {
helpRectangle.setBounds(childDockBefore.getLocation().x,
childDockBefore.getLocation().y,
childDockBefore.getSize().width,
childDockBefore.getSize().height);
if (helpRectangle.contains(relativeLocation)) {
rectangle.setSize(childDockBefore.getWidth() / 2, childDockBefore.getHeight());
rectangle.setLocation(childDockBefore.getLocation().x + childDockBefore.getWidth() / 2, childDockBefore.getLocation().y);
} else {
rectangle.setSize(childDockAfter.getWidth() / 2, childDockAfter.getHeight());
rectangle.setLocation(childDockAfter.getLocation().x, childDockAfter.getLocation().y);
}
}
}
}
}
return priority;
}
public boolean addDockable(@NotNull Dockable dockableToAdd, @NotNull Point relativeLocation, Point dockableOffset) {
// Verify the conditions for adding the dockable.
if (getDockPriority(dockableToAdd, relativeLocation) == Priority.CANNOT_DOCK) {
// We are not allowed to dock the dockable in this dock.
return false;
}
// Get the position for the new dockable.
int position = getDockPosition(dockableToAdd, relativeLocation);
// Create the new child dock.
Dock childDock = childDockFactory.createDock(dockableToAdd, getDockingMode());
// Could we create a child dock?
if (childDock != null) {
childDock.setParentDock(this);
childDock.addDockable(dockableToAdd, new Point(0, 0), new Point(0, 0));
// Add the dock.
addChildDock(childDock, new Position(position));
// Repaint.
SwingUtil.repaintParent(this);
return true;
}
// Do we have a composite dockable?
if (dockableToAdd instanceof CompositeDockable) {
// Add every child dockable.
CompositeDockable compositeDockable = (CompositeDockable) dockableToAdd;
for (int index = 0; index < compositeDockable.getDockableCount(); index++) {
// Create the new child dock.
Dock oneChildDock = childDockFactory.createDock(compositeDockable.getDockable(index), getDockingMode());
if (oneChildDock != null) {
oneChildDock.setParentDock(this);
// Add the dock.
oneChildDock.addDockable(compositeDockable.getDockable(index), new Point(0, 0), new Point(0, 0));
addChildDock(oneChildDock, new Position(position));
position++;
}
}
// Repaint.
SwingUtil.repaintParent(this);
return true;
}
return false;
}
public boolean isEmpty() {
return childDocks.size() == 0;
}
public boolean isFull() {
return false;
}
public CompositeDock getParentDock() {
return parentDock;
}
public void setParentDock(CompositeDock parentDock) {
this.parentDock = parentDock;
}
public void saveProperties(String prefix, @NotNull Properties properties, @NotNull Map childDockIds) {
// Save the fill mode and the column count.
PropertiesUtil.setInteger(properties, prefix + "fillMode", fillMode);
PropertiesUtil.setInteger(properties, prefix + "columnCount", columnCount);
// Save the class of the child dock factory and its properties.
String className = childDockFactory.getClass().getName();
PropertiesUtil.setString(properties, prefix + "childDockFactory", className);
childDockFactory.saveProperties(prefix + "childDockFactory.", properties);
// Iterate over the child docks.
for (Object childDock : childDocks) {
// Get the child dock.
Dock dock = (Dock) childDock;
// Get the ID of the childDock.
String childDockId = (String) childDockIds.get(dock);
// Save the position.
Position.setPositionProperty(properties, prefix + CHILD_DOCK_PREFIX + childDockId + "." + Position.PROPERTY_POSITION, getChildDockPosition(dock));
}
}
public void loadProperties(String prefix, @NotNull Properties properties, @NotNull Map newChildDocks, Map dockables, Window owner) throws IOException {
// Set the fill mode.
int fillMode = FILL_FLOW_HORIZONTAL;
fillMode = PropertiesUtil.getInteger(properties, prefix + "fillMode", fillMode);
setFillMode(fillMode);
// Load the class and properties of the child dock factory.
try {
String className = SingleDockFactory.class.getName();
className = PropertiesUtil.getString(properties, prefix + "childDockFactory", className);
Class extends DockFactory> clazz = Class.forName(className).asSubclass(DockFactory.class);
childDockFactory = clazz.getDeclaredConstructor().newInstance();
childDockFactory.loadProperties(prefix + "childDockFactory.", properties);
} catch (@NotNull ClassNotFoundException | InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException exception) {
System.out.println("Could not create the child dock factory.");
exception.printStackTrace();
childDockFactory = new SingleDockFactory();
}
// Create an array with the child docks ids in the right order.
String[] childDockIdsArray = new String[newChildDocks.keySet().size()];
for (Object o : newChildDocks.keySet()) {
// Get the ID of the child dock.
String childDockId = (String) o;
// Get the position of this child.
Position position = null;
position = Position.getPositionProperty(properties, prefix + CHILD_DOCK_PREFIX + childDockId + "." + Position.PROPERTY_POSITION, position);
childDockIdsArray[position.getPosition(0)] = childDockId;
//TODO what if a child dock is not there?
}
// Add the child docks.
int position = 0;
for (final String aChildDockIdsArray : childDockIdsArray) {
// Get the child dock.
Dock dock = (Dock) newChildDocks.get(aChildDockIdsArray);
// Add only if the child is not empty.
if (!dock.isEmpty()) {
addChildDock(dock, new Position(position));
position++;
}
}
// Get the saved column count and make it as small as possible.
int savedColumnCount = columnCount;
savedColumnCount = PropertiesUtil.getInteger(properties, prefix + "columnCount", savedColumnCount);
int savedRowCount = (int) Math.ceil(childDocks.size() / (double) savedColumnCount);
while (childDocks.size() <= savedRowCount * (savedColumnCount - 1)) {
savedColumnCount--;
}
if (savedColumnCount <= 0) {
savedColumnCount = 1;
}
if (savedColumnCount != columnCount) {
rebuildUI(savedColumnCount);
}
}
public void addDockingListener(DockingListener listener) {
dockingEventSupport.addDockingListener(listener);
}
public void removeDockingListener(DockingListener listener) {
dockingEventSupport.removeDockingListener(listener);
}
// Implementations of CompositeDock.
public void addChildDock(@NotNull Dock dock, @NotNull Position position) throws IllegalStateException {
// Get the position in the grid.
int gridPosition = getChildDockCount();
if (position.getDimensions() == 1) {
if ((position.getPosition(0) >= 0) && (position.getPosition(0) <= getChildDockCount())) {
gridPosition = position.getPosition(0);
}
}
// Inform the listeners.
dockingEventSupport.fireDockingWillChange(new ChildDockEvent(this, null, this, dock));
// Add the dock to the list of child docks.
childDocks.add(gridPosition, dock);
dock.setParentDock(this);
// Remove and add all the childdocks from the dock panel.
rebuildUI(calculateColumnCount());
// Inform the listeners.
dockingEventSupport.fireDockingChanged(new ChildDockEvent(this, null, this, dock));
// Repaint.
SwingUtil.repaintParent(this);
}
public int getChildDockCount() {
return childDocks.size();
}
@NotNull
public Dock getChildDock(int index) throws IndexOutOfBoundsException {
// Check if the index is in the bounds.
if ((index < 0) || (index >= getChildDockCount())) {
throw new IndexOutOfBoundsException("Index " + index);
}
return (Dock) childDocks.get(index);
}
@NotNull
public Position getChildDockPosition(Dock childDock) throws IllegalArgumentException {
int position = childDocks.indexOf(childDock);
if (position >= 0) {
return new Position(position);
}
throw new IllegalArgumentException("The dock is not docked in this composite dock.");
}
public void emptyChild(Dock emptyChildDock) {
// Search the empty child dock.
if (childDocks.contains(emptyChildDock)) {
// Inform the listeners about the removal.
dockingEventSupport.fireDockingWillChange(new ChildDockEvent(this, this, null, emptyChildDock));
// Remove the empty dock.
//dockPanel.remove((Component) emptyChildDock);
childDocks.remove(emptyChildDock);
// Rebuild.
rebuildUI(calculateColumnCount());
// Inform the listeners about the removal.
dockingEventSupport.fireDockingChanged(new ChildDockEvent(this, this, null, emptyChildDock));
// Are we empty and there aren't any ghosts?
if ((isEmpty()) &&
(ghostDockPanel == null) &&
(getParentDock() != null)) {
getParentDock().emptyChild(this);
}
// Repaint.
SwingUtil.repaintParent(this);
}
}
public void ghostChild(Dock emptyChildDock) {
// Search the empty child dock.
if (childDocks.contains(emptyChildDock)) {
// Inform the listeners about the removal.
dockingEventSupport.fireDockingWillChange(new ChildDockEvent(this, this, null, emptyChildDock));
// Remove the empty child from the list of child docks.
childDocks.remove(emptyChildDock);
// The old panel becomes the ghost.
ghostDockPanel = dockPanel;
ghostDockPanel.setVisible(false);
// Create and add the panel for the docks.
initializeUi(calculateColumnCount());
// Iterate over the remaining child docks.
for (Object childDock1 : childDocks) {
Dock childDock = (Dock) childDock1;
// Remove the dock from the ghost panel.
ghostDockPanel.remove((Component) childDock);
// Add the dock to the new panel.
dockPanel.add((Component) childDock);
}
// Inform the listeners about the removal.
dockingEventSupport.fireDockingChanged(new ChildDockEvent(this, this, null, emptyChildDock));
// Repaint.
SwingUtil.repaintParent(this);
}
}
public void clearGhosts() {
if (ghostDockPanel != null) {
this.remove(ghostDockPanel);
ghostDockPanel = null;
// Are we empty?
if ((isEmpty()) && (getParentDock() != null)) {
getParentDock().emptyChild(this);
}
}
}
@Nullable
public DockFactory getChildDockFactory() {
return childDockFactory;
}
public void setChildDockFactory(@Nullable DockFactory childDockFactory) {
if (childDockFactory == null) {
throw new IllegalArgumentException("The child dock factory cannot be null.");
}
this.childDockFactory = childDockFactory;
}
// Getters / Setters.
/**
* Determines the strategy for organizing the dockables in rows and columns.
* The default fill mode is FILL_FLOW_HORIZONTAL.
*
* @return The strategy for organizing the dockables in rows and columns.
*/
public int getFillMode() {
return fillMode;
}
/**
* Sets the strategy for organizing the dockables in rows and columns.
*
* @param newFillMode The strategy for organizing the dockables in rows and columns.
*/
public void setFillMode(int newFillMode) {
// Do we have a new value?
if (newFillMode != fillMode) {
// Set the new fill mode.
this.fillMode = newFillMode;
// Rebuild the UI.
rebuildUI(calculateColumnCount());
}
}
// Protected metods.
/**
* Gets the position where the dockable should be docked in the dock given the mouse position.
* The position is the future index of the dockable in the grid. The first position is 0.
*
* @param newDockable The dockable to add.
* @param relativePosition The relative mouse location in this dock.
* @return The position where the dockable should be docked in the dock.
* The position is the future index of the dockable in the grid.
*/
protected int getDockPosition(Dockable newDockable, @NotNull Point relativePosition) {
// Are there no child docks already?
if (childDocks.size() == 0) {
return 0;
}
// When we are here, there are already dockables in this dock.
// Iterate over the dockables.
for (int index = 0; index < childDocks.size(); index++) {
// Get the child.
Component childDock = (Component) childDocks.get(index);
helpRectangle.setBounds(childDock.getLocation().x, childDock.getLocation().y,
childDock.getSize().width, childDock.getSize().height);
// Is the mouse above this recangle?
if (helpRectangle.contains(relativePosition)) {
// Set the rectangle on the first half of the dock.
helpRectangle.setSize(childDock.getSize().width / 2, childDock.getSize().height);
if (helpRectangle.contains(relativePosition)) {
return index;
} else {
return index + 1;
}
}
}
return childDocks.size();
}
/**
* Determines if the given dockable can be added to this dock with priority. When there are no dockables already,
* it can be added with priority if the mouse is above the middle of the rectangle.
* If there are already dockables, the dockable can be added with priority if the mouse is near
* the left or right border of the child docks.
*
* @param dockable The dockable that may be added to this dock.
* @param relativeLocation The location of the mouse relative to this dock.
* @return True if the given dockable can be added to this dock with priority,
* false otherwise.
*/
protected boolean canAddDockableWithPriority(Dockable dockable, @NotNull Point relativeLocation) {
// Are there no dockables already?
if (childDocks.size() == 0) {
// There is priority if we are not near the border of the dock.
Dimension size = getSize();
priorityRectangle.setBounds((int) (size.width * centerPriorityRectangleRelativeOffset),
(int) (size.height * centerPriorityRectangleRelativeOffset),
(int) (size.width * (1 - 2 * centerPriorityRectangleRelativeOffset)),
(int) (size.height * (1 - 2 * centerPriorityRectangleRelativeOffset)));
// Inside the priority rectangle we can dock with priority.
return priorityRectangle.contains(relativeLocation);
// Outside the priority rectangle.
}
// When we are here, there are already dockables in this dock.
// Iterate over the dockables.
for (Object childDock1 : childDocks) {
// Get the child.
Component childDock = (Component) childDock1;
helpRectangle.setBounds(childDock.getLocation().x,
childDock.getLocation().y,
childDock.getSize().width,
childDock.getSize().height);
// Is the mouse above this recangle?
if (helpRectangle.contains(relativeLocation)) {
// Set the rectangle on the left side of the child dock.
priorityRectangle.setBounds(childDock.getLocation().x,
childDock.getLocation().y,
(int) (childDock.getSize().width * leftPriorityRectangleRelativeOffset),
childDock.getSize().height);
if (priorityRectangle.contains(relativeLocation)) {
return true;
}
// Set the rectangle on the right side of the child dock.
priorityRectangle.setBounds(childDock.getLocation().x + (int) (childDock.getSize().width * (1 - rightPriorityRectangleRelativeOffset)),
childDock.getLocation().y,
(int) (childDock.getSize().width * rightPriorityRectangleRelativeOffset),
childDock.getSize().height);
return priorityRectangle.contains(relativeLocation);
}
}
// We can't dock with priority.
return false;
}
/**
* Calculates the number of columns there will be in the grid. The calculation depends on the fillMode
.
* Valid fill modes are FILL_SQUARE_HORIZONTAL, FILL_SQUARE_VERTICAL, FILL_FLOW_HORIZONTAL or FILL_FLOW_VERTICAL.
*
* @return The number of columns there will be in the grid.
* @throws IllegalStateException when the fillMode
is not FILL_SQUARE_HORIZONTAL,
* FILL_SQUARE_VERTICAL, FILL_FLOW_HORIZONTAL or FILL_FLOW_VERTICAL.
*/
protected int calculateColumnCount() {
// The number of dockables that should be docked.
int dockCount = childDocks.size();
// Return 1 when there are no dockables.
if (dockCount == 0) {
return 1;
}
if ((getSize().width == 0) ||
(getSize().height == 0) ||
(fillMode == FILL_SQUARE_HORIZONTAL) ||
(fillMode == FILL_SQUARE_VERTICAL)) {
// Calculate the number of columns.
int newColumnCount = (int) Math.ceil(Math.sqrt((double) (dockCount)));
if (fillMode == FILL_SQUARE_VERTICAL) {
if (((newColumnCount - 1) * newColumnCount) >= dockCount) {
newColumnCount--;
}
}
if (newColumnCount <= 0) {
newColumnCount = 1;
}
return newColumnCount;
} else {
// There are 2 equations that define the rowCount and columnCount:
//
// width / (columnCount * preferredWidth) = height / (rowCount * preferredHeight)
// childDockablesCount = columnCount * rowCount
// ||
// \/
// columnCount = (width * preferredHeight) * rowCount / (height * preferredWidth)
// rowCount = childDockablesCount / columnCount
// ||
// \/
// columnCount * columnCount = (width * preferredHeight) * childDockablesCount / (height * preferredWidth)
// rowCount = childDockablesCount / columnCount
// ||
// \/
// columnCount = sqrt((width * preferredHeight) * childDockablesCount / (height * preferredWidth))
// rowCount = childDockablesCount / columnCount
// Calculate the maximum component size of the children.
Dimension maxPreferredSize = new Dimension(0, 0);
for (Object childDock : childDocks) {
// Get the preferred size of the child.
Dimension childSize = ((Component) childDock).getPreferredSize();
// Adjust the union size.
maxPreferredSize.setSize(Math.max(maxPreferredSize.width, childSize.width), Math.max(maxPreferredSize.height, childSize.height));
}
if (maxPreferredSize.width <= 0) {
maxPreferredSize.width = 1;
}
if (maxPreferredSize.height <= 0) {
maxPreferredSize.height = 1;
}
if (fillMode == FILL_FLOW_HORIZONTAL) {
// Calculate the number of columns.
int newColumnCount = (int) Math.ceil(Math.sqrt(((double) ((getSize().width * maxPreferredSize.height) * dockCount)) / (getSize().height * maxPreferredSize.width)));
if (newColumnCount <= 0) {
newColumnCount = 1;
}
// Get the number of rows for this column count.
int newRowCount = (int) Math.ceil(dockCount / (double) newColumnCount);
// Try to make the column count smaller.
while (dockCount <= newRowCount * (newColumnCount - 1)) {
newColumnCount--;
}
if (newColumnCount <= 0) {
newColumnCount = 1;
}
return newColumnCount;
} else if (fillMode == FILL_FLOW_VERTICAL) {
// Calculate the number of rows.
int newRowCount = (int) Math.ceil(Math.sqrt(((double) ((getSize().height * maxPreferredSize.width) * dockCount)) / (getSize().width * maxPreferredSize.height)));
if (newRowCount <= 0) {
newRowCount = 1;
}
// Get the number of columns for this row count.
return (int) Math.ceil(dockCount / (double) newRowCount);
}
}
throw new IllegalStateException("The fill mode [" + fillMode + "] is unknown.");
}
/**
* Checks the docking modes of the dockable. True is returned if the dockable has {@link DockingMode#GRID} as possible docking mode.
*
* @return True is returned if the dockable has DockingMode.GRID
* as possible docking mode.
* @param dockable The dockable to add.
*/
protected boolean checkDockingModes(@NotNull Dockable dockable) {
int dockPositions = dockable.getDockingModes();
return (dockPositions & DockingMode.GRID) != 0;
}
/**
* Gets the docking mode for a dockable that is docked in this dock. This is always {@link DockingMode#GRID}.
*
* @return The docking mode for a dockable that is docked in this dock. This is always DockingMode.GRID.
*/
protected int getDockingMode() {
return DockingMode.GRID;
}
// Private metods.
/**
* Creates the panels for the child docks and adds them to this dock.
*/
private void initializeUi(int newColumnCount) {
this.columnCount = newColumnCount;
// Set the layout.
this.setLayout(new BorderLayout());
// Create the panel that will contain the docks.
dockPanel = new JPanel();
dockPanel.setLayout(new GridLayout(0, columnCount));
// Add it.
this.add(dockPanel, BorderLayout.CENTER);
}
/**
* Rebuilds the whole dock again with the existing child docks.
*/
private void rebuildUI(int newColumnCount) {
// Remove everything, except the ghostpanel.
for (int index = 0; index < getComponentCount(); index++) {
Component component = getComponent(index);
if (!component.equals(ghostDockPanel)) {
remove(component);
}
}
// Create and add the panel for the docks.
initializeUi(newColumnCount);
// Add all the docks.
for (Object childDock1 : childDocks) {
// Add the child to the panel.
Dock childDock = (Dock) childDock1;
dockPanel.add((Component) childDock);
}
}
}