blob: 231538756a035039da76d1a7f567d63dce8fc84f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2013, 2018 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
*******************************************************************************/
package org.eclipse.e4.ui.workbench.renderers.swt;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Platform;
import org.eclipse.e4.ui.model.application.ui.MGenericTile;
import org.eclipse.e4.ui.model.application.ui.MUIElement;
import org.eclipse.e4.ui.workbench.IPresentationEngine;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Shell;
public class SashLayout extends Layout {
// The minimum value (as a percentage) that a sash can be dragged to
int minSashPercent = 10;
int marginLeft = 0;
int marginRight = 0;
int marginTop = 0;
int marginBottom = 0;
int sashWidth = 4;
MUIElement root;
private Composite host;
static class SashRect {
Rectangle rect;
MGenericTile<?> container;
MUIElement left;
MUIElement right;
public SashRect(Rectangle rect, MGenericTile<?> container,
MUIElement left, MUIElement right) {
this.container = container;
this.rect = rect;
this.left = left;
this.right = right;
}
}
List<SashRect> sashes = new ArrayList<>();
boolean draggingSashes = false;
List<SashRect> sashesToDrag;
public boolean layoutUpdateInProgress = false;
/**
* Remember last cursor set on the sash to prevent repeated setCursor calls.
* Value can be <code>0</code> which means default cursor was set or one of the
* SWT.CURSOR_* constants.
*/
int lastCursor = 0;
public SashLayout(final Composite host, MUIElement root) {
this.root = root;
this.host = host;
host.addMouseTrackListener(MouseTrackListener.mouseExitAdapter(e -> {
host.setCursor(null);
lastCursor = 0;
}));
host.addMouseMoveListener(this::onMouseMove);
host.addMouseListener(new MouseAdapter() {
@Override
public void mouseUp(MouseEvent e) {
host.setCapture(false);
draggingSashes = false;
}
@Override
public void mouseDown(MouseEvent e) {
if (e.button != 1) {
return;
}
sashesToDrag = getSashRects(e.x, e.y);
if (sashesToDrag.size() > 0) {
draggingSashes = true;
host.setCapture(true);
}
}
});
}
/**
* Used to update the layout while dragging or to update the cursor to show
* possible drag directions otherwise.
*
* @param e the mouse event
*/
private void onMouseMove(MouseEvent e) {
if (!draggingSashes) {
// Set the cursor feedback
List<SashRect> sashList = getSashRects(e.x, e.y);
final int newCursor;
if (sashList.isEmpty()) {
newCursor = SWT.CURSOR_ARROW;
} else if (sashList.size() == 1) {
if (sashList.get(0).container.isHorizontal()) {
newCursor = SWT.CURSOR_SIZEWE;
} else {
newCursor = SWT.CURSOR_SIZENS;
}
} else {
newCursor = SWT.CURSOR_SIZEALL;
}
if (lastCursor != newCursor) {
host.setCursor(host.getDisplay().getSystemCursor(newCursor));
lastCursor = newCursor;
}
} else {
try {
layoutUpdateInProgress = true;
adjustWeights(sashesToDrag, e.x, e.y);
// FIXME SWT Win requires a synchronous layout call to update the UI
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=558392
// once this is fixed, the requestLayout call should be sufficient
if (Platform.getOS().equals(Platform.OS_WIN32)) {
try {
host.setRedraw(false);
host.layout();
} finally {
host.setRedraw(true);
}
host.update();
} else {
host.requestLayout();
}
} finally {
layoutUpdateInProgress = false;
}
}
}
@Override
protected void layout(Composite composite, boolean flushCache) {
if (root == null)
return;
Rectangle bounds = composite.getBounds();
if (composite instanceof Shell)
bounds = ((Shell) composite).getClientArea();
else {
bounds.x = 0;
bounds.y = 0;
}
bounds.width -= (marginLeft + marginRight);
bounds.height -= (marginTop + marginBottom);
bounds.x += marginLeft;
bounds.y += marginTop;
sashes.clear();
tileSubNodes(bounds, root);
}
protected void adjustWeights(List<SashRect> sashes, int curX, int curY) {
for (SashRect sr : sashes) {
int totalWeight = getWeight(sr.left) + getWeight(sr.right);
int minSashValue = (int) (((totalWeight / 100.0) * minSashPercent) + 0.5);
Rectangle leftRect = getRectangle(sr.left);
Rectangle rightRect = getRectangle(sr.right);
if (leftRect == null || rightRect == null)
continue;
int leftWeight;
int rightWeight;
if (sr.container.isHorizontal()) {
double left = leftRect.x;
double right = rightRect.x + rightRect.width;
double pct = (curX - left) / (right - left);
leftWeight = (int) ((totalWeight * pct) + 0.5);
if (leftWeight < minSashValue)
leftWeight = minSashValue;
if (leftWeight > (totalWeight - minSashValue))
leftWeight = totalWeight - minSashValue;
rightWeight = totalWeight - leftWeight;
} else {
double top = leftRect.y;
double bottom = rightRect.y + rightRect.height;
double pct = (curY - top) / (bottom - top);
leftWeight = (int) ((totalWeight * pct) + 0.5);
if (leftWeight < minSashValue)
leftWeight = minSashValue;
if (leftWeight > (totalWeight - minSashValue))
leftWeight = totalWeight - minSashValue;
rightWeight = totalWeight - leftWeight;
}
setWeight(sr.left, leftWeight);
setWeight(sr.right, rightWeight);
}
}
private void setWeight(MUIElement element, int weight) {
element.setContainerData(Integer.toString(weight));
}
private Rectangle getRectangle(MUIElement element) {
if (element.getWidget() instanceof Rectangle)
return (Rectangle) element.getWidget();
else if (element.getWidget() instanceof Control)
return ((Control) (element.getWidget())).getBounds();
return null;
}
protected List<SashRect> getSashRects(int x, int y) {
List<SashRect> srs = new ArrayList<>();
Rectangle target = new Rectangle(x - 5, y - 5, 10, 10);
for (SashRect sr : sashes) {
if (sr.rect.intersects(target) && !sr.container.getTags().contains(IPresentationEngine.NO_MOVE))
srs.add(sr);
}
return srs;
}
@Override
protected Point computeSize(Composite composite, int wHint, int hHint,
boolean flushCache) {
return new Point(600, 400);
}
private int totalWeight(MGenericTile<?> node) {
int total = 0;
for (MUIElement subNode : node.getChildren()) {
if (subNode.isToBeRendered() && subNode.isVisible())
total += getWeight(subNode);
}
return total;
}
private void tileSubNodes(Rectangle bounds, MUIElement node) {
if (node != root)
setRectangle(node, bounds);
if (!(node instanceof MGenericTile<?>))
return;
MGenericTile<?> sashContainer = (MGenericTile<?>) node;
List<MUIElement> visibleChildren = getVisibleChildren(sashContainer);
int childCount = visibleChildren.size();
// How many pixels do we have?
int availableWidth = sashContainer.isHorizontal() ? bounds.width
: bounds.height;
// Subtract off the room for the sashes
availableWidth -= ((childCount - 1) * sashWidth);
// Get the total of the weights
double totalWeight = totalWeight(sashContainer);
int tilePos = sashContainer.isHorizontal() ? bounds.x : bounds.y;
MUIElement prev = null;
for (MUIElement subNode : visibleChildren) {
// Add a 'sash' between this node and the 'prev'
if (prev != null) {
Rectangle sashRect = sashContainer.isHorizontal() ? new Rectangle(
tilePos, bounds.y, sashWidth, bounds.height)
: new Rectangle(bounds.x, tilePos, bounds.width,
sashWidth);
sashes.add(new SashRect(sashRect, sashContainer, prev, subNode));
host.redraw(sashRect.x, sashRect.y, sashRect.width,
sashRect.height, false);
tilePos += sashWidth;
}
// Calc the new size as a %'age of the total
double ratio = getWeight(subNode) / totalWeight;
int newSize = (int) ((availableWidth * ratio) + 0.5);
Rectangle subBounds = sashContainer.isHorizontal() ? new Rectangle(
tilePos, bounds.y, newSize, bounds.height) : new Rectangle(
bounds.x, tilePos, bounds.width, newSize);
tilePos += newSize;
tileSubNodes(subBounds, subNode);
prev = subNode;
}
}
/**
* @param node
* @param bounds
*/
private void setRectangle(MUIElement node, Rectangle bounds) {
if (node.getWidget() instanceof Control) {
Control ctrl = (Control) node.getWidget();
ctrl.setBounds(bounds);
} else if (node.getWidget() instanceof Rectangle) {
Rectangle theRect = (Rectangle) node.getWidget();
theRect.x = bounds.x;
theRect.y = bounds.y;
theRect.width = bounds.width;
theRect.height = bounds.height;
}
}
private List<MUIElement> getVisibleChildren(MGenericTile<?> sashContainer) {
List<MUIElement> visKids = new ArrayList<>();
for (MUIElement child : sashContainer.getChildren()) {
if (child.isToBeRendered() && child.isVisible())
visKids.add(child);
}
return visKids;
}
private static int getWeight(MUIElement element) {
String info = element.getContainerData();
if (info == null || info.length() == 0) {
return 0;
}
try {
return Integer.parseInt(info);
} catch (NumberFormatException e) {
return 0;
}
}
}