blob: c8231934f40f1ef985ded07cf29883a94788716d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.pivot.wtk.skin.terra;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import org.apache.pivot.util.Utils;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.Cursor;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.GraphicsUtilities;
import org.apache.pivot.wtk.Mouse;
import org.apache.pivot.wtk.Orientation;
import org.apache.pivot.wtk.SplitPane;
import org.apache.pivot.wtk.SplitPaneListener;
import org.apache.pivot.wtk.Theme;
import org.apache.pivot.wtk.skin.ComponentSkin;
import org.apache.pivot.wtk.skin.ContainerSkin;
/**
* Split pane skin.
*/
public class TerraSplitPaneSkin extends ContainerSkin implements SplitPaneListener {
/**
* Split pane splitter component.
*/
protected class Splitter extends Component {
public Splitter() {
setSkin(new SplitterSkin());
}
}
/**
* Split pane splitter component skin.
*/
protected class SplitterSkin extends ComponentSkin {
private int dragOffset;
private SplitterShadow shadow = null;
@Override
public boolean isFocusable() {
return false;
}
@Override
public int getPreferredWidth(int height) {
// This will never get called since the size of the splitter is set
// automatically by SplitPaneSkin using the the size of the
// SplitPane and the split thickness
return 0;
}
@Override
public int getPreferredHeight(int width) {
// This will never get called since the size of the splitter is set
// automatically by SplitPaneSkin using the the size of the
// SplitPane and the split thickness
return 0;
}
@Override
public void layout() {
// No-op
}
@Override
public void paint(Graphics2D graphics) {
SplitPane splitPane = (SplitPane) TerraSplitPaneSkin.this.getComponent();
Orientation orientation = splitPane.getOrientation();
int width = getWidth();
int height = getHeight();
int imageWidth, imageHeight;
if (orientation == Orientation.HORIZONTAL) {
imageWidth = width - 4;
imageHeight = Math.min(height - 4, 8);
} else {
imageWidth = Math.min(width - 4, 8);
imageHeight = height - 4;
}
if (imageWidth > 0 && imageHeight > 0) {
int translateX = (width - imageWidth) / 2;
int translateY = (height - imageHeight) / 2;
graphics.translate(translateX, translateY);
Color dark = splitterHandlePrimaryColor;
Color light = splitterHandleSecondaryColor;
if (orientation == Orientation.HORIZONTAL) {
graphics.setStroke(new BasicStroke());
graphics.setPaint(dark);
graphics.drawLine(0, 0, imageWidth - 1, 0);
graphics.drawLine(0, 3, imageWidth - 1, 3);
graphics.drawLine(0, 6, imageWidth - 1, 6);
graphics.setPaint(light);
graphics.drawLine(0, 1, imageWidth - 1, 1);
graphics.drawLine(0, 4, imageWidth - 1, 4);
graphics.drawLine(0, 7, imageWidth - 1, 7);
} else {
int half = imageHeight / 2;
graphics.setPaint(dark);
graphics.fillRect(0, 0, 2, half);
graphics.fillRect(3, 0, 2, half);
graphics.fillRect(6, 0, 2, half);
graphics.setPaint(light);
graphics.fillRect(0, half, 2, half);
graphics.fillRect(3, half, 2, half);
graphics.fillRect(6, half, 2, half);
}
}
}
@Override
public boolean mouseMove(Component component, int x, int y) {
boolean consumed = super.mouseMove(component, x, y);
if (Mouse.getCapturer() == component) {
SplitPane splitPane = (SplitPane) TerraSplitPaneSkin.this.getComponent();
Orientation orientation = splitPane.getOrientation();
// Calculate the would-be new split location
int splitLocation;
float splitRatio;
if (orientation == Orientation.HORIZONTAL) {
splitLocation = limitSplitLocation(component.getX() + x - dragOffset);
splitRatio = (float) splitLocation / splitPane.getWidth();
} else {
splitLocation = limitSplitLocation(component.getY() + y - dragOffset);
splitRatio = (float) splitLocation / splitPane.getHeight();
}
if (shadow == null) {
// Update the split location immediately
splitPane.setSplitRatio(splitRatio);
} else {
// Move the shadow to the split location
if (orientation == Orientation.HORIZONTAL) {
shadow.setLocation(splitLocation, 0);
} else {
shadow.setLocation(0, splitLocation);
}
}
}
return consumed;
}
@Override
public boolean mouseDown(Component component, Mouse.Button button, int x, int y) {
boolean consumed = super.mouseDown(component, button, x, y);
SplitPane splitPane = (SplitPane) TerraSplitPaneSkin.this.getComponent();
if (button == Mouse.Button.LEFT && !splitPane.isLocked()) {
Orientation orientation = splitPane.getOrientation();
if (useShadow) {
// Add the shadow to the split pane and lay it out
shadow = new SplitterShadow();
splitPane.add(shadow);
if (orientation == Orientation.HORIZONTAL) {
shadow.setLocation(component.getX(), 0);
} else {
shadow.setLocation(0, component.getY());
}
shadow.setSize(getWidth(), getHeight());
}
dragOffset = (orientation == Orientation.HORIZONTAL ? x : y);
Mouse.capture(component);
consumed = true;
}
return consumed;
}
@Override
public boolean mouseUp(Component component, Mouse.Button button, int x, int y) {
boolean consumed = super.mouseUp(component, button, x, y);
if (button == Mouse.Button.LEFT && Mouse.getCapturer() == component) {
if (shadow != null) {
SplitPane splitPane = (SplitPane) TerraSplitPaneSkin.this.getComponent();
// Update the split location and remove the shadow
int splitLocation;
float splitRatio;
if (splitPane.getOrientation() == Orientation.HORIZONTAL) {
splitLocation = shadow.getX();
splitRatio = (float) splitLocation / splitPane.getWidth();
} else {
splitLocation = shadow.getY();
splitRatio = (float) splitLocation / splitPane.getHeight();
}
splitPane.setSplitRatio(splitRatio);
splitPane.remove(shadow);
shadow = null;
}
Mouse.release();
}
return consumed;
}
}
/**
* Split pane splitter shadow component.
*/
protected class SplitterShadow extends Component {
public SplitterShadow() {
setSkin(new SplitterShadowSkin());
}
}
/**
* Split pane splitter shadow component skin.
*/
protected class SplitterShadowSkin extends ComponentSkin {
@Override
public int getPreferredWidth(int height) {
// This will never get called since the splitter will always just
// set the size of its shadow to match its own size
return 0;
}
@Override
public int getPreferredHeight(int width) {
// This will never get called since the splitter will always just
// set the size of its shadow to match its own size
return 0;
}
@Override
public void layout() {
// No-op
}
@Override
public void paint(Graphics2D graphics) {
graphics.setPaint(new Color(0, 0, 0, 64));
graphics.fillRect(0, 0, getWidth(), getHeight());
}
}
private Splitter splitter = new Splitter();
private Color splitterHandlePrimaryColor;
private Color splitterHandleSecondaryColor;
private int splitterThickness;
private boolean useShadow;
public TerraSplitPaneSkin() {
Theme theme = currentTheme();
splitterHandlePrimaryColor = theme.getColor(9);
splitterHandleSecondaryColor = theme.getColor(10);
splitterThickness = 6;
useShadow = false;
}
@Override
public void install(Component component) {
super.install(component);
SplitPane splitPane = (SplitPane) component;
splitPane.getSplitPaneListeners().add(this);
splitPane.add(splitter);
updateSplitterCursor();
}
@Override
public void setSize(int width, int height) {
int previousWidth = getWidth();
int previousHeight = getHeight();
super.setSize(width, height);
SplitPane splitPane = (SplitPane) getComponent();
Orientation orientation = splitPane.getOrientation();
if (splitPane.getResizeMode() == SplitPane.ResizeMode.PRIMARY_REGION
&& ((previousWidth != width && orientation == Orientation.HORIZONTAL)
|| (previousHeight != height && orientation == Orientation.VERTICAL))) {
SplitPane.Region primaryRegion = splitPane.getPrimaryRegion();
float splitRatio = splitPane.getSplitRatio();
if (orientation == Orientation.HORIZONTAL) {
int splitLocation = (int) (splitRatio * previousWidth);
if (primaryRegion == SplitPane.Region.BOTTOM_RIGHT) {
// Move the split location to maintain size on the right
splitLocation += (width - previousWidth);
}
splitRatio = (float) limitSplitLocation(splitLocation) / width;
} else {
int splitLocation = (int) (splitRatio * previousHeight);
if (primaryRegion == SplitPane.Region.BOTTOM_RIGHT) {
// Move the split location to maintain size on the bottom
splitLocation += (height - previousHeight);
}
splitRatio = (float) limitSplitLocation(splitLocation) / height;
}
splitPane.setSplitRatio(splitRatio);
}
}
@Override
public int getPreferredWidth(int height) {
int preferredWidth = splitterThickness;
SplitPane splitPane = (SplitPane) getComponent();
Component topLeft = splitPane.getTopLeft();
if (topLeft != null) {
preferredWidth += topLeft.getPreferredWidth(height);
}
Component bottomRight = splitPane.getBottomRight();
if (bottomRight != null) {
preferredWidth += bottomRight.getPreferredWidth(height);
}
return preferredWidth;
}
@Override
public int getPreferredHeight(int width) {
int preferredHeight = splitterThickness;
SplitPane splitPane = (SplitPane) getComponent();
Component topLeft = splitPane.getTopLeft();
if (topLeft != null) {
preferredHeight += topLeft.getPreferredHeight(width);
}
Component bottomRight = splitPane.getBottomRight();
if (bottomRight != null) {
preferredHeight += bottomRight.getPreferredHeight(width);
}
return preferredHeight;
}
@Override
public Dimensions getPreferredSize() {
int preferredWidth = splitterThickness;
int preferredHeight = splitterThickness;
SplitPane splitPane = (SplitPane) getComponent();
Component topLeft = splitPane.getTopLeft();
if (topLeft != null) {
Dimensions size = topLeft.getPreferredSize();
preferredWidth += size.width;
preferredHeight += size.height;
}
Component bottomRight = splitPane.getBottomRight();
if (bottomRight != null) {
Dimensions size = bottomRight.getPreferredSize();
preferredWidth += size.width;
preferredHeight += size.height;
}
return new Dimensions(preferredWidth, preferredHeight);
}
@Override
public void layout() {
int width = getWidth();
int height = getHeight();
SplitPane splitPane = (SplitPane) getComponent();
float splitRatio = splitPane.getSplitRatio();
Component topLeft = splitPane.getTopLeft();
Component bottomRight = splitPane.getBottomRight();
if (splitPane.getOrientation() == Orientation.HORIZONTAL) {
int splitLocation = limitSplitLocation((int) (splitRatio * width));
int rightStart = splitLocation + splitterThickness;
splitter.setLocation(splitLocation, 0);
splitter.setSize(splitterThickness, height);
if (topLeft != null) {
topLeft.setLocation(0, 0);
topLeft.setSize(splitLocation, height);
}
if (bottomRight != null) {
bottomRight.setLocation(rightStart, 0);
bottomRight.setSize(Math.max(width - rightStart, 0), height);
}
} else {
int splitLocation = limitSplitLocation((int) (splitRatio * height));
int bottomStart = splitLocation + splitterThickness;
splitter.setLocation(0, splitLocation);
splitter.setSize(width, splitterThickness);
if (topLeft != null) {
topLeft.setLocation(0, 0);
topLeft.setSize(width, splitLocation);
}
if (bottomRight != null) {
bottomRight.setLocation(0, bottomStart);
bottomRight.setSize(width, Math.max(height - bottomStart, 0));
}
}
}
public Color getSplitterHandlePrimaryColor() {
return splitterHandlePrimaryColor;
}
public void setSplitterHandlePrimaryColor(Color splitterHandlePrimaryColor) {
Utils.checkNull(splitterHandlePrimaryColor, "splitterHandlePrimaryColor");
this.splitterHandlePrimaryColor = splitterHandlePrimaryColor;
splitter.repaint();
}
public final void setSplitterHandlePrimaryColor(String splitterHandlePrimaryColor) {
setSplitterHandlePrimaryColor(
GraphicsUtilities.decodeColor(splitterHandlePrimaryColor, "splitterHandlePrimaryColor"));
}
public final void setSplitterHandlePrimaryColor(int splitterHandlePrimaryColor) {
Theme theme = currentTheme();
setSplitterHandlePrimaryColor(theme.getColor(splitterHandlePrimaryColor));
}
public Color getSplitterHandleSecondaryColor() {
return splitterHandleSecondaryColor;
}
public void setSplitterHandleSecondaryColor(Color splitterHandleSecondaryColor) {
Utils.checkNull(splitterHandleSecondaryColor, "splitterHandleSecondaryColor");
this.splitterHandleSecondaryColor = splitterHandleSecondaryColor;
splitter.repaint();
}
public final void setSplitterHandleSecondaryColor(String splitterHandleSecondaryColor) {
setSplitterHandleSecondaryColor(
GraphicsUtilities.decodeColor(splitterHandleSecondaryColor, "splitterHandleSecondaryColor"));
}
public final void setSplitterHandleSecondaryColor(int splitterHandleSecondaryColor) {
Theme theme = currentTheme();
setSplitterHandleSecondaryColor(theme.getColor(splitterHandleSecondaryColor));
}
public int getSplitterThickness() {
return splitterThickness;
}
public void setSplitterThickness(int splitterThickness) {
Utils.checkNonNegative(splitterThickness, "splitterThickness");
this.splitterThickness = splitterThickness;
invalidateComponent();
}
public boolean getUseShadow() {
return useShadow;
}
public void setUseShadow(boolean useShadow) {
if (Mouse.getCapturer() == getComponent()) {
throw new IllegalStateException(
"Cannot set useShadow while the splitter is being dragged.");
}
this.useShadow = useShadow;
}
@Override
public void topLeftChanged(SplitPane splitPane, Component previousTopLeft) {
invalidateComponent();
}
@Override
public void bottomRightChanged(SplitPane splitPane, Component previousBottomRight) {
invalidateComponent();
}
@Override
public void orientationChanged(SplitPane splitPane) {
updateSplitterCursor();
invalidateComponent();
}
@Override
public void primaryRegionChanged(SplitPane splitPane) {
updateSplitterCursor();
}
@Override
public void splitRatioChanged(SplitPane splitPane, float previousSplitLocation) {
invalidateComponent();
}
@Override
public void lockedChanged(SplitPane splitPane) {
updateSplitterCursor();
}
@Override
public void resizeModeChanged(SplitPane splitPane, SplitPane.ResizeMode previousResizeMode) {
// No-op
}
private void updateSplitterCursor() {
Cursor cursor = Cursor.DEFAULT;
SplitPane splitPane = (SplitPane) getComponent();
if (!splitPane.isLocked()) {
switch (splitPane.getOrientation()) {
case HORIZONTAL:
switch (splitPane.getPrimaryRegion()) {
case TOP_LEFT:
cursor = Cursor.RESIZE_EAST;
break;
case BOTTOM_RIGHT:
cursor = Cursor.RESIZE_WEST;
break;
default:
break;
}
break;
case VERTICAL:
switch (splitPane.getPrimaryRegion()) {
case TOP_LEFT:
cursor = Cursor.RESIZE_SOUTH;
break;
case BOTTOM_RIGHT:
cursor = Cursor.RESIZE_NORTH;
break;
default:
break;
}
break;
default:
break;
}
}
splitter.setCursor(cursor);
}
private int limitSplitLocation(int splitLocation) {
SplitPane splitPane = (SplitPane) getComponent();
Component topLeft = splitPane.getTopLeft();
Component bottomRight = splitPane.getBottomRight();
int lower, upper;
if (splitPane.getOrientation() == Orientation.HORIZONTAL) {
lower = 0;
upper = Math.max(getWidth() - splitterThickness, 0);
if (topLeft != null) {
int leftLimit = topLeft.getMinimumWidth();
if (leftLimit >= 0) {
lower = Math.min(leftLimit, upper);
}
}
if (bottomRight != null) {
int rightLimit = bottomRight.getMinimumWidth();
if (rightLimit >= 0) {
upper = Math.max(upper - rightLimit, lower);
}
}
} else {
lower = 0;
upper = Math.max(getHeight() - splitterThickness, 0);
if (topLeft != null) {
int topLimit = topLeft.getMinimumHeight();
if (topLimit >= 0) {
lower = Math.min(topLimit, upper);
}
}
if (bottomRight != null) {
int bottomLimit = bottomRight.getMinimumHeight();
if (bottomLimit >= 0) {
upper = Math.max(upper - bottomLimit, lower);
}
}
}
if (splitLocation < lower) {
return lower;
} else if (splitLocation > upper) {
return upper;
}
return splitLocation;
}
}