package com.peterhi.ui;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
final class SplitPane extends Canvas implements Listener {
private Rectangle cachedOldClientArea;
private int gap = 10;
public SplitPane(Composite parent, int style) {
super(parent, style);
addListener(SWT.Resize, this);
}
public TabPane[] getTabPanes() {
List<TabPane> paneList = new ArrayList<TabPane>();
Control[] controls = getChildren();
for (Control control : controls) {
if (control instanceof TabPane) {
TabPane pane = (TabPane )control;
paneList.add(pane);
}
}
RectComparator compare = RectComparator.getHorizontal();
Collections.sort(paneList, compare);
TabPane[] panes = paneList.toArray(new TabPane[paneList.size()]);
return panes;
}
public View[] getViews() {
List<View> viewList = new ArrayList<View>();
TabPane[] tabViewContainers = getTabPanes();
for (TabPane tabViewContainer : tabViewContainers) {
View[] tabbedViews = tabViewContainer.getViews();
for (View tabbedView : tabbedViews) {
viewList.add(tabbedView);
}
}
// TODO: add other non-tab views when they are implemented
return viewList.toArray(new View[viewList.size()]);
}
public Window getWindow() {
return (Window )getShell();
}
public int getGap() {
return gap;
}
public void setGap(int value) {
if (value < 0) {
throw new IllegalArgumentException();
}
if (value == 0) {
throw new IllegalArgumentException();
}
if (value % 2 != 0) {
throw new IllegalArgumentException();
}
gap = value;
layout();
}
@Override
public void handleEvent(Event event) {
if (equals(event.widget)) {
if (event.type == SWT.Resize) {
onResize(event);
}
}
}
@Override
public void setLayout(Layout layout) {
}
@Override
public Point computeSize(int widthHint, int heightHint, boolean changed) {
Point size = super.computeSize(widthHint, heightHint, changed);
View[] views = getViews();
if (views != null) {
Rectangle clientArea = getClientArea();
if (widthHint == SWT.DEFAULT) {
widthHint = clientArea.width;
}
if (heightHint == SWT.DEFAULT) {
heightHint = clientArea.height;
}
int insets = gap / 2;
float xFactor = 0.0f;
float yFactor = 0.0f;
for (View view : views) {
Composite parent = view.getParent();
Rectangle parentBounds = parent.getBounds();
Point viewMinimumSize = view.getMinimumSize();
if (parentBounds.x > clientArea.x) {
parentBounds.x -= insets;
parentBounds.width += insets;
viewMinimumSize.x += insets;
}
if (parentBounds.y > clientArea.y) {
parentBounds.y -= insets;
parentBounds.height += insets;
viewMinimumSize.y += insets;
}
if (parentBounds.x + parentBounds.width < clientArea.x + clientArea.width) {
parentBounds.width += insets;
viewMinimumSize.x += insets;
}
if (parentBounds.y + parentBounds.height < clientArea.y + clientArea.height) {
parentBounds.height += insets;
viewMinimumSize.y += insets;
}
Rectangle parentTrim = parent.computeTrim(0, 0, 0, 0);
viewMinimumSize.x += parentTrim.width;
viewMinimumSize.y += parentTrim.height;
float curXFactor = ((float )parentBounds.width) / ((float )viewMinimumSize.x);
float curYFactor = ((float )parentBounds.height) / ((float )viewMinimumSize.y);
if (xFactor == 0.0f || xFactor > curXFactor) {
xFactor = curXFactor;
}
if (yFactor == 0.0f || yFactor > curYFactor) {
yFactor = curYFactor;
}
}
if (xFactor > 0.0f) {
size.x = Math.round(((float )widthHint) / xFactor);
}
if (yFactor > 0.0f) {
size.y = Math.round(((float )heightHint) / yFactor);
}
}
return size;
}
@Override
public void layout(boolean changed) {
super.layout(changed);
Rectangle newClientArea = getClientArea();
if (newClientArea.width <= 0 || newClientArea.height <= 0) {
return;
}
TabPane[] tabViewContainers = getTabPanes();
SortedMap<TabPane, SortedSet<TabPane>> xFollowingTabViewContainerMap = generateFollowingTabViewContainerMap(SWT.HORIZONTAL, tabViewContainers);
SortedMap<TabPane, SortedSet<TabPane>> yFollowingTabViewContainerMap = generateFollowingTabViewContainerMap(SWT.VERTICAL, tabViewContainers);
SortedSet<TabPane> xTrailingTabViewContainerSet = generateTrailingTabViewContainerSet(SWT.HORIZONTAL, tabViewContainers, cachedOldClientArea);
SortedSet<TabPane> yTrailingTabViewContainerSet = generateTrailingTabViewContainerSet(SWT.VERTICAL, tabViewContainers, cachedOldClientArea);
Map<TabPane, Rectangle> cachedBoundsMap = generateCachedBoundsMap(tabViewContainers);
inflateCachedBounds(cachedBoundsMap, cachedOldClientArea, gap / 2);
scaleCachedBounds(tabViewContainers, cachedBoundsMap, cachedOldClientArea, newClientArea);
TabPane initialTabViewContainer = getUpperLeftTabViewContainer();
lineUpTabViewContainers(initialTabViewContainer, cachedBoundsMap, xFollowingTabViewContainerMap, yFollowingTabViewContainerMap);
truncateTrailingViewContainers(cachedBoundsMap, newClientArea, xTrailingTabViewContainerSet, yTrailingTabViewContainerSet);
inflateCachedBounds(cachedBoundsMap, newClientArea, -gap / 2);
applyCachedBounds(cachedBoundsMap);
Window window = getWindow();
Point size = computeSize(SWT.DEFAULT, SWT.DEFAULT);
Rectangle trim = window.computeTrim(0, 0, size.x, size.y);
window.setMinimumSize(trim.width, trim.height);
}
/*public Point computeMinimumSize() {
Point minimumSize = new Point(0, 0);
View[] views = getViews();
if (views != null) {
Rectangle clientArea = getClientArea();
int insets = viewSpacing / 2;
float xFactor = 0.0f;
float yFactor = 0.0f;
for (View view : views) {
Composite parent = view.getParent();
Rectangle parentBounds = parent.getBounds();
Point viewMinimumSize = view.getMinimumSize();
if (parentBounds.x > clientArea.x) {
parentBounds.x -= insets;
parentBounds.width += insets;
viewMinimumSize.x += insets;
}
if (parentBounds.y > clientArea.y) {
parentBounds.y -= insets;
parentBounds.height += insets;
viewMinimumSize.y += insets;
}
if (parentBounds.x + parentBounds.width < clientArea.x + clientArea.width) {
parentBounds.width += insets;
viewMinimumSize.x += insets;
}
if (parentBounds.y + parentBounds.height < clientArea.y + clientArea.height) {
parentBounds.height += insets;
viewMinimumSize.y += insets;
}
Rectangle parentTrim = parent.computeTrim(0, 0, 0, 0);
viewMinimumSize.x += parentTrim.width;
viewMinimumSize.y += parentTrim.height;
float curXFactor = ((float )parentBounds.width) / ((float )viewMinimumSize.x);
float curYFactor = ((float )parentBounds.height) / ((float )viewMinimumSize.y);
if (xFactor == 0.0f || xFactor > curXFactor) {
xFactor = curXFactor;
}
if (yFactor == 0.0f || yFactor > curYFactor) {
yFactor = curYFactor;
}
}
if (xFactor > 0.0f) {
minimumSize.x = Math.round(((float )clientArea.width) / xFactor);
}
if (yFactor > 0.0f) {
minimumSize.y = Math.round(((float )clientArea.height) / yFactor);
}
}
return minimumSize;
}
private void updateMinimumSize() {
View[] views = getViews();
if (views == null || views.length == 0) {
return;
}
Window window = (Window )getShell();
Rectangle clientArea = getClientArea();
Point trim = new Point(0, 0);
trim.x = getShell().getBounds().width - getClientArea().width;
trim.y = getShell().getBounds().height - getClientArea().height;
float xFactor = 0.0f;
float yFactor = 0.0f;
for (View view : views) {
TabViewContainer tab = (TabViewContainer )view.getParent();
Rectangle tabBounds = tab.getBounds();
Rectangle tabTrim = tab.computeTrim(0, 0, 0, 0);
Point min = view.getMinimumSize();
min.x += tabTrim.width;
min.y += tabTrim.height;
if (tabBounds.x != clientArea.x) {
tabBounds.x -= viewSpacing / 2;
tabBounds.width += viewSpacing / 2;
min.x += viewSpacing / 2;
}
if (tabBounds.y != clientArea.y) {
tabBounds.y -= viewSpacing / 2;
tabBounds.height += viewSpacing / 2;
min.y += viewSpacing / 2;
}
if (tabBounds.x + tabBounds.width != clientArea.x + clientArea.width) {
tabBounds.width += viewSpacing / 2;
min.x += viewSpacing / 2;
}
if (tabBounds.y + tabBounds.height != clientArea.y + clientArea.height) {
tabBounds.height += viewSpacing / 2;
min.y += viewSpacing / 2;
}
float curXFactor = (float )(tabBounds.width) / (float )min.x;
float curYFactor = (float )(tabBounds.height) / (float )min.y;
if (xFactor == 0.0f) {
xFactor = curXFactor;
} else if (curXFactor < xFactor) {
xFactor = curXFactor;
}
if (yFactor == 0.0f) {
yFactor = curYFactor;
} else if (curYFactor < yFactor) {
yFactor = curYFactor;
}
}
Point size = window.getSize();
if (xFactor > 0.0f) {
size.x -= trim.x;
size.x = Math.round((float )size.x / xFactor);
size.x += trim.x;
}
if (yFactor > 0.0f) {
size.y -= trim.y;
size.y = Math.round((float )size.y / yFactor);
size.y += trim.y;
}
window.setMinimumSize(size);
}*/
protected void onResize(Event event) {
if (cachedOldClientArea == null) {
cachedOldClientArea = getClientArea();
}
layout();
cachedOldClientArea = getClientArea();
}
private Map<TabPane, Rectangle> generateCachedBoundsMap(TabPane[] tabViewContainers) {
Map<TabPane, Rectangle> cachedBoundsMap = new HashMap<TabPane, Rectangle>();
for (TabPane tabViewContainer : tabViewContainers) {
Rectangle containerBounds = tabViewContainer.getBounds();
cachedBoundsMap.put(tabViewContainer, containerBounds);
}
return cachedBoundsMap;
}
private void inflateCachedBounds(Map<TabPane, Rectangle> cachedBoundsMap, Rectangle clientArea, int amount) {
int cLeft = clientArea.x;
int cTop = clientArea.y;
int cRight = cLeft + clientArea.width;
int cBottom = cTop + clientArea.height;
for (Map.Entry<TabPane, Rectangle> entry : cachedBoundsMap.entrySet()) {
TabPane tabContainer = entry.getKey();
Rectangle containerBounds = entry.getValue();
int left = containerBounds.x;
int top = containerBounds.y;
int right = left + containerBounds.width;
int bottom = top + containerBounds.height;
if (left > cLeft) {
containerBounds.x -= amount;
containerBounds.width += amount;
}
if (top > cTop) {
containerBounds.y -= amount;
containerBounds.height += amount;
}
if (right < cRight) {
containerBounds.width += amount;
}
if (bottom < cBottom) {
containerBounds.height += amount;
}
updateCachedBounds(cachedBoundsMap, tabContainer, containerBounds);
}
}
private SortedMap<TabPane, SortedSet<TabPane>> generateFollowingTabViewContainerMap(
int orientation, TabPane[] tabViewContainers) {
if (tabViewContainers == null) {
throw new NullPointerException();
}
if (orientation != SWT.HORIZONTAL && orientation != SWT.VERTICAL) {
throw new IllegalArgumentException();
}
RectComparator primary = RectComparator.getInstance(orientation);
Arrays.sort(tabViewContainers, primary);
RectComparator secondary;
if (orientation == SWT.HORIZONTAL) {
secondary = RectComparator.getVertical();
} else if (orientation == SWT.VERTICAL) {
secondary = RectComparator.getHorizontal();
} else {
throw new IllegalStateException();
}
SortedMap<TabPane, SortedSet<TabPane>> followingTabViewContainerMap =
new TreeMap<TabPane, SortedSet<TabPane>>(secondary);
for (TabPane leadingTabViewContainer : tabViewContainers) {
SortedSet<TabPane> followingTabViewContainerSet = followingTabViewContainerMap.get(leadingTabViewContainer);
if (followingTabViewContainerSet == null) {
followingTabViewContainerSet = new TreeSet<TabPane>(secondary);
followingTabViewContainerMap.put(leadingTabViewContainer, followingTabViewContainerSet);
}
Rectangle lBounds = leadingTabViewContainer.getBounds();
int lLeft = lBounds.x;
int lTop = lBounds.y;
int lRight = lLeft + lBounds.width;
int lBottom = lTop + lBounds.height;
for (TabPane possibleFollowingTabViewContainer : tabViewContainers) {
if (leadingTabViewContainer.equals(possibleFollowingTabViewContainer)) {
continue;
}
Rectangle fBounds = possibleFollowingTabViewContainer.getBounds();
int fLeft = fBounds.x;
int fTop = fBounds.y;
int fRight = fLeft + fBounds.width;
int fBottom = fTop + fBounds.height;
if (orientation == SWT.HORIZONTAL) {
if (lTop > fBottom || lBottom < fTop) {
continue;
}
if (lRight + gap != fLeft) {
continue;
}
followingTabViewContainerSet.add(possibleFollowingTabViewContainer);
} else if (orientation == SWT.VERTICAL) {
if (lLeft > fRight || lRight < fLeft) {
continue;
}
if (lBottom + gap != fTop) {
continue;
}
followingTabViewContainerSet.add(possibleFollowingTabViewContainer);
} else {
throw new IllegalStateException();
}
}
}
return followingTabViewContainerMap;
}
private SortedSet<TabPane> generateTrailingTabViewContainerSet(
int orientation, TabPane[] tabViewContainers, Rectangle clientArea) {
if (tabViewContainers == null) {
throw new NullPointerException();
}
if (orientation != SWT.HORIZONTAL && orientation != SWT.VERTICAL) {
throw new IllegalArgumentException();
}
RectComparator primaryComparator = RectComparator.getInstance(orientation);
Arrays.sort(tabViewContainers, primaryComparator);
RectComparator secondaryComparator;
if (orientation == SWT.HORIZONTAL) {
secondaryComparator = RectComparator.getVertical();
} else if (orientation == SWT.VERTICAL) {
secondaryComparator = RectComparator.getHorizontal();
} else {
throw new IllegalStateException();
}
SortedSet<TabPane> trailingTabViewContainerSet = new TreeSet<TabPane>(secondaryComparator);
int cLeft = clientArea.x;
int cTop = clientArea.y;
int cRight = cLeft + clientArea.width;
int cBottom = cTop + clientArea.height;
for (TabPane possibleTrailingTabViewContainer : tabViewContainers) {
Rectangle tBounds = possibleTrailingTabViewContainer.getBounds();
int tLeft = tBounds.x;
int tTop = tBounds.y;
int tRight = tLeft + tBounds.width;
int tBottom = tTop + tBounds.height;
if (orientation == SWT.HORIZONTAL) {
if (tRight == cRight) {
trailingTabViewContainerSet.add(possibleTrailingTabViewContainer);
}
} else if (orientation == SWT.VERTICAL) {
if (tBottom == cBottom) {
trailingTabViewContainerSet.add(possibleTrailingTabViewContainer);
}
} else {
throw new IllegalStateException();
}
}
return trailingTabViewContainerSet;
}
private void scaleCachedBounds(TabPane[] tabViewContainers, Map<TabPane, Rectangle> cachedBoundsMap, Rectangle oldClientArea, Rectangle newClientArea) {
float xScaleFactor = (float )newClientArea.width / (float )oldClientArea.width;
float yScaleFactor = (float )newClientArea.height / (float )oldClientArea.height;
for (TabPane tabViewContainer : tabViewContainers) {
Rectangle cachedBounds = cachedBoundsMap.get(tabViewContainer);
float scaledWidth = (float )cachedBounds.width * xScaleFactor;
float scaledHeight = (float )cachedBounds.height * yScaleFactor;
cachedBounds.width = Math.round(scaledWidth);
cachedBounds.height = Math.round(scaledHeight);
updateCachedBounds(cachedBoundsMap, tabViewContainer, cachedBounds);
}
}
private TabPane getUpperLeftTabViewContainer() {
TabPane[] tabViewContainers = getTabPanes();
for (TabPane tabViewContainer : tabViewContainers) {
Rectangle countainerBounds = tabViewContainer.getBounds();
if (countainerBounds.x == 0 && countainerBounds.y == 0) {
return tabViewContainer;
}
}
return null;
}
private void lineUpTabViewContainers(TabPane currentTabViewContainer, Map<TabPane, Rectangle> cachedBoundsMap,
SortedMap<TabPane, SortedSet<TabPane>> xFollowingTabViewContainerMap,
SortedMap<TabPane, SortedSet<TabPane>> yFollowingTabViewContainerMap) {
{
SortedSet<TabPane> xCurrentTabViewContainerFollowerSet = xFollowingTabViewContainerMap.get(currentTabViewContainer);
if (xCurrentTabViewContainerFollowerSet != null) {
Rectangle cBounds = cachedBoundsMap.get(currentTabViewContainer);
for (TabPane xCurrentTabViewContainerFollower : xCurrentTabViewContainerFollowerSet) {
Rectangle fBounds = cachedBoundsMap.get(xCurrentTabViewContainerFollower);
fBounds.x = cBounds.x + cBounds.width;
updateCachedBounds(cachedBoundsMap, xCurrentTabViewContainerFollower, fBounds);
lineUpTabViewContainers(xCurrentTabViewContainerFollower, cachedBoundsMap, xFollowingTabViewContainerMap, yFollowingTabViewContainerMap);
}
}
}
{
SortedSet<TabPane> yCurrentTabViewContainerFollowerSet = yFollowingTabViewContainerMap.get(currentTabViewContainer);
if (yCurrentTabViewContainerFollowerSet != null) {
Rectangle cBounds = cachedBoundsMap.get(currentTabViewContainer);
for (TabPane yCurrentTabViewContainerFollower : yCurrentTabViewContainerFollowerSet) {
Rectangle fBounds = cachedBoundsMap.get(yCurrentTabViewContainerFollower);
fBounds.y = cBounds.y + cBounds.height;
updateCachedBounds(cachedBoundsMap, yCurrentTabViewContainerFollower, fBounds);
lineUpTabViewContainers(yCurrentTabViewContainerFollower, cachedBoundsMap, xFollowingTabViewContainerMap, yFollowingTabViewContainerMap);
}
}
}
}
private void truncateTrailingViewContainers(Map<TabPane, Rectangle> cachedBoundsMap, Rectangle clientArea,
SortedSet<TabPane> xTrailingTabViewContainerSet, SortedSet<TabPane> yTrailingTabViewContainerSet) {
int cLeft = clientArea.x;
int cTop = clientArea.y;
int cRight = cLeft + clientArea.width;
int cBottom = cTop + clientArea.height;
for (TabPane xTrailingTabViewContainer : xTrailingTabViewContainerSet) {
Rectangle tBounds = cachedBoundsMap.get(xTrailingTabViewContainer);
int tLeft = tBounds.x;
int tRight = tLeft + tBounds.width;
tBounds.width -= tRight - cRight;
updateCachedBounds(cachedBoundsMap, xTrailingTabViewContainer, tBounds);
}
for (TabPane yTrailingTabViewContainer : yTrailingTabViewContainerSet) {
Rectangle tBounds = cachedBoundsMap.get(yTrailingTabViewContainer);
int tTop = tBounds.y;
int tBottom = tTop + tBounds.height;
tBounds.height -= tBottom - cBottom;
updateCachedBounds(cachedBoundsMap, yTrailingTabViewContainer, tBounds);
}
}
private void applyCachedBounds(Map<TabPane, Rectangle> cachedBoundsMap) {
for (Map.Entry<TabPane, Rectangle> entry : cachedBoundsMap.entrySet()) {
TabPane tab = entry.getKey();
Rectangle bounds = entry.getValue();
tab.setBounds(bounds);
}
}
private void updateCachedBounds(Map<TabPane, Rectangle> cachedBoundsMap, TabPane tabViewContainer, Rectangle bounds) {
cachedBoundsMap.put(tabViewContainer, bounds);
}
}