blob: fb1892de5e4b148e37b9c98f0cb18048a83aa3c7 [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.Color;
import java.awt.Graphics2D;
import java.awt.Toolkit;
import org.apache.pivot.collections.Dictionary;
import org.apache.pivot.util.Vote;
import org.apache.pivot.wtk.Bounds;
import org.apache.pivot.wtk.Component;
import org.apache.pivot.wtk.ComponentListener;
import org.apache.pivot.wtk.Container;
import org.apache.pivot.wtk.ContainerMouseListener;
import org.apache.pivot.wtk.Cursor;
import org.apache.pivot.wtk.Dimensions;
import org.apache.pivot.wtk.Display;
import org.apache.pivot.wtk.GraphicsUtilities;
import org.apache.pivot.wtk.ImageView;
import org.apache.pivot.wtk.Insets;
import org.apache.pivot.wtk.Keyboard;
import org.apache.pivot.wtk.Mouse;
import org.apache.pivot.wtk.Orientation;
import org.apache.pivot.wtk.Point;
import org.apache.pivot.wtk.Sheet;
import org.apache.pivot.wtk.SheetStateListener;
import org.apache.pivot.wtk.Theme;
import org.apache.pivot.wtk.Window;
import org.apache.pivot.wtk.Keyboard.KeyCode;
import org.apache.pivot.wtk.Mouse.Button;
import org.apache.pivot.wtk.effects.DropShadowDecorator;
import org.apache.pivot.wtk.effects.Transition;
import org.apache.pivot.wtk.effects.TransitionListener;
import org.apache.pivot.wtk.effects.TranslationDecorator;
import org.apache.pivot.wtk.effects.easing.Quadratic;
import org.apache.pivot.wtk.media.Image;
import org.apache.pivot.wtk.skin.WindowSkin;
/**
* Sheet skin class.
*/
public class TerraSheetSkin extends WindowSkin implements SheetStateListener {
public class OpenTransition extends Transition {
private int dy = 0;
public OpenTransition(boolean reversed) {
super(stateTransitionDuration, stateTransitionRate, false, reversed);
}
@Override
public void start(TransitionListener transitionListener) {
Sheet sheet = (Sheet)getComponent();
sheet.getDecorators().add(translationDecorator);
dy = 0;
super.start(transitionListener);
}
@Override
public void stop() {
Sheet sheet = (Sheet)getComponent();
sheet.getDecorators().remove(translationDecorator);
super.stop();
}
@Override
public void update() {
Sheet sheet = (Sheet)getComponent();
float scale;
if (isReversed()) {
scale = easing.easeIn(getElapsedTime(), 1, -1, getDuration());
} else {
scale = easing.easeOut(getElapsedTime(), 1, -1, getDuration());
}
Display display = sheet.getDisplay();
if (display != null) {
Bounds decoratedBounds = sheet.getDecoratedBounds();
display.repaint(decoratedBounds.x, decoratedBounds.y,
decoratedBounds.width, decoratedBounds.height + dy);
Dimensions size = sheet.getPreferredSize();
dy = -(int)(size.height * scale);
translationDecorator.setY(dy);
display.repaint(decoratedBounds.x, decoratedBounds.y,
decoratedBounds.width, decoratedBounds.height + dy);
}
}
}
/**
* Resize button image.
*/
protected class ResizeImage extends Image {
public static final int ALPHA = 64;
@Override
public int getWidth() {
return 5;
}
@Override
public int getHeight() {
return 5;
}
@Override
public void paint(Graphics2D graphics) {
graphics.setPaint(new Color(0, 0, 0, ALPHA));
graphics.fillRect(3, 0, 2, 1);
graphics.fillRect(0, 3, 2, 1);
graphics.fillRect(3, 3, 2, 1);
graphics.setPaint(new Color(borderColor.getRed(),
borderColor.getGreen(), borderColor.getBlue(),
ALPHA));
graphics.fillRect(3, 1, 2, 1);
graphics.fillRect(0, 4, 2, 1);
graphics.fillRect(3, 4, 2, 1);
}
}
private Image resizeImage = new ResizeImage();
private ImageView resizeHandle = new ImageView(resizeImage);
private Point resizeOffset = null;
private Color borderColor;
private Insets padding;
private boolean resizable;
private int stateTransitionDuration = DEFAULT_STATE_TRANSITION_DURATION;
private int stateTransitionRate = DEFAULT_STATE_TRANSITION_RATE;
private Color bevelColor;
private OpenTransition openTransition = null;
private Quadratic easing = new Quadratic();
private TranslationDecorator translationDecorator = new TranslationDecorator(true);
private ComponentListener ownerListener = new ComponentListener.Adapter() {
@Override
public void locationChanged(Component component, int previousX, int previousY) {
alignToOwner();
}
@Override
public void sizeChanged(Component component, int previousWidth, int previousHeight) {
alignToOwner();
}
};
private ContainerMouseListener displayMouseListener = new ContainerMouseListener() {
@Override
public boolean mouseMove(Container display, int x, int y) {
return isMouseOverOwnerClientArea(display, x, y);
}
@Override
public boolean mouseDown(Container display, Mouse.Button button, int x, int y) {
boolean consumed = false;
Sheet sheet = (Sheet)getComponent();
if (isMouseOverOwnerClientArea(display, x, y)) {
Window rootOwner = sheet.getRootOwner();
rootOwner.moveToFront();
consumed = true;
Toolkit.getDefaultToolkit().beep();
}
return consumed;
}
@Override
public boolean mouseUp(Container display, Mouse.Button button, int x, int y) {
return isMouseOverOwnerClientArea(display, x, y);
}
@Override
public boolean mouseWheel(Container display, Mouse.ScrollType scrollType,
int scrollAmount, int wheelRotation, int x, int y) {
return isMouseOverOwnerClientArea(display, x, y);
}
private boolean isMouseOverOwnerClientArea(Container display, int x, int y) {
boolean mouseOverOwnerClientArea = false;
Sheet sheet = (Sheet)getComponent();
Component descendant = display.getDescendantAt(x, y);
if (descendant != display) {
Window window = descendant.getWindow();
if (sheet.getOwner() == window) {
Bounds clientArea = window.getClientArea();
Point location = window.mapPointFromAncestor(display, x, y);
mouseOverOwnerClientArea = clientArea.contains(location);
}
}
return mouseOverOwnerClientArea;
}
};
private DropShadowDecorator dropShadowDecorator = null;
private static final int DEFAULT_STATE_TRANSITION_DURATION = 300;
private static final int DEFAULT_STATE_TRANSITION_RATE = 30;
public TerraSheetSkin() {
TerraTheme theme = (TerraTheme)Theme.getTheme();
Color backgroundColor = theme.getColor(11);
backgroundColor = new Color(backgroundColor.getRed(), backgroundColor.getGreen(),
backgroundColor.getBlue(), 235);
setBackgroundColor(backgroundColor);
borderColor = theme.getColor(7);
padding = new Insets(8);
resizable = false;
// Set the derived colors
bevelColor = TerraTheme.darken(backgroundColor);
}
@Override
public void install(Component component) {
super.install(component);
Sheet sheet = (Sheet)component;
sheet.getSheetStateListeners().add(this);
// Attach the drop-shadow decorator
dropShadowDecorator = new DropShadowDecorator(3, 3, 3);
sheet.getDecorators().add(dropShadowDecorator);
sheet.add(resizeHandle);
}
@Override
public int getPreferredWidth(int height) {
int preferredWidth = 0;
Sheet sheet = (Sheet)getComponent();
Component content = sheet.getContent();
if (content != null) {
if (height != -1) {
height = Math.max(height - (padding.top + padding.bottom + 2), 0);
}
preferredWidth = content.getPreferredWidth(height);
}
preferredWidth += (padding.left + padding.right + 2);
return preferredWidth;
}
@Override
public int getPreferredHeight(int width) {
int preferredHeight = 0;
Sheet sheet = (Sheet)getComponent();
Component content = sheet.getContent();
if (content != null) {
if (width != -1) {
width = Math.max(width - (padding.left + padding.right + 2), 0);
}
preferredHeight = content.getPreferredHeight(width);
}
preferredHeight += (padding.top + padding.bottom + 2);
return preferredHeight;
}
@Override
public Dimensions getPreferredSize() {
int preferredWidth = 0;
int preferredHeight = 0;
Sheet sheet = (Sheet)getComponent();
Component content = sheet.getContent();
if (content != null) {
Dimensions preferredContentSize = content.getPreferredSize();
preferredWidth = preferredContentSize.width;
preferredHeight = preferredContentSize.height;
}
preferredWidth += (padding.left + padding.right + 2);
preferredHeight += (padding.top + padding.bottom + 2);
Dimensions preferredSize = new Dimensions(preferredWidth, preferredHeight);
return preferredSize;
}
@Override
public void layout() {
int width = getWidth();
int height = getHeight();
Sheet sheet = (Sheet)getComponent();
// Size/position resize handle
resizeHandle.setSize(resizeHandle.getPreferredSize());
resizeHandle.setLocation(width - resizeHandle.getWidth() - 2,
height - resizeHandle.getHeight() - 2);
resizeHandle.setVisible(resizable
&& !sheet.isMaximized()
&& (sheet.isPreferredWidthSet()
|| sheet.isPreferredHeightSet()));
Component content = sheet.getContent();
if (content != null) {
content.setLocation(padding.left + 1, padding.top + 1);
int contentWidth = Math.max(width - (padding.left + padding.right + 2), 0);
int contentHeight = Math.max(height - (padding.top + padding.bottom + 2), 0);
content.setSize(contentWidth, contentHeight);
}
}
@Override
public void paint(Graphics2D graphics) {
super.paint(graphics);
int width = getWidth();
int height = getHeight();
graphics.setPaint(borderColor);
GraphicsUtilities.drawRect(graphics, 0, 0, width, height);
graphics.setPaint(bevelColor);
GraphicsUtilities.drawLine(graphics, 1, height - 2, width - 2, Orientation.HORIZONTAL);
}
@Override
public void sizeChanged(Component component, int previousWidth, int previousHeight) {
super.sizeChanged(component, previousWidth, previousHeight);
alignToOwner();
}
@Override
public boolean mouseMove(Component component, int x, int y) {
boolean consumed = super.mouseMove(component, x, y);
if (Mouse.getCapturer() == component) {
Sheet sheet = (Sheet)getComponent();
Display display = sheet.getDisplay();
Point location = sheet.mapPointToAncestor(display, x, y);
// Pretend that the mouse can't move off screen (off the display)
location = new Point(Math.min(Math.max(location.x, 0), display.getWidth() - 1),
Math.min(Math.max(location.y, 0), display.getHeight() - 1));
if (resizeOffset != null) {
// Resize the frame
int preferredWidth = -1;
int preferredHeight = -1;
boolean preferredWidthSet = component.isPreferredWidthSet();
boolean preferredHeightSet = component.isPreferredHeightSet();
boolean noPreferredSet = !(preferredWidthSet || preferredHeightSet);
if (preferredWidthSet || noPreferredSet) {
preferredWidth = Math.max(location.x - sheet.getX() + resizeOffset.x, 2);
preferredWidth = Math.min(preferredWidth, sheet.getMaximumWidth());
preferredWidth = Math.max(preferredWidth, sheet.getMinimumWidth());
}
if (preferredHeightSet || noPreferredSet) {
preferredHeight = Math.max(location.y - sheet.getY() + resizeOffset.y,
resizeHandle.getHeight() + 7);
preferredHeight = Math.min(preferredHeight, sheet.getMaximumHeight());
preferredHeight = Math.max(preferredHeight, sheet.getMinimumHeight());
}
sheet.setPreferredSize(preferredWidth, preferredHeight);
}
} else {
Cursor cursor = null;
Bounds resizeHandleBounds = resizeHandle.getBounds();
if (resizable && resizeHandleBounds.contains(x, y)) {
boolean preferredWidthSet = component.isPreferredWidthSet();
boolean preferredHeightSet = component.isPreferredHeightSet();
if (preferredWidthSet
&& preferredHeightSet) {
cursor = Cursor.RESIZE_SOUTH_EAST;
} else if (preferredWidthSet) {
cursor = Cursor.RESIZE_EAST;
} else if (preferredHeightSet) {
cursor = Cursor.RESIZE_SOUTH;
} else {
cursor = Cursor.RESIZE_SOUTH_EAST;
}
}
component.setCursor(cursor);
}
return consumed;
}
@Override
public boolean mouseDown(Container container, Mouse.Button button, int x, int y) {
Sheet sheet = (Sheet)container;
if (!sheet.isTopMost()) {
Window owner = sheet.getOwner();
owner.moveToFront();
}
boolean consumed = super.mouseDown(container, button, x, y);
if (resizable && button == Mouse.Button.LEFT) {
Bounds resizeHandleBounds = resizeHandle.getBounds();
if (resizeHandleBounds.contains(x, y)) {
resizeOffset = new Point(getWidth() - x, getHeight() - y);
Mouse.capture(container);
}
}
return consumed;
}
@Override
public boolean mouseUp(Component component, Button button, int x, int y) {
boolean consumed = super.mouseUp(component, button, x, y);
if (Mouse.getCapturer() == component) {
resizeOffset = null;
Mouse.release();
}
return consumed;
}
/**
* {@link KeyCode#ENTER ENTER} Close the sheet with a 'result' of true.<br>
* {@link KeyCode#ESCAPE ESCAPE} Close the sheet with a 'result' of false.
*/
@Override
public boolean keyPressed(Component component, int keyCode, Keyboard.KeyLocation keyLocation) {
boolean consumed = false;
Sheet sheet = (Sheet)getComponent();
if (keyCode == Keyboard.KeyCode.ENTER) {
sheet.close(true);
consumed = true;
} else if (keyCode == Keyboard.KeyCode.ESCAPE) {
sheet.close(false);
consumed = true;
} else {
consumed = super.keyPressed(component, keyCode, keyLocation);
}
return consumed;
}
@Override
public void setBackgroundColor(Color backgroundColor) {
super.setBackgroundColor(backgroundColor);
bevelColor = TerraTheme.darken(backgroundColor);
}
public Color getBorderColor() {
return borderColor;
}
public void setBorderColor(Color borderColor) {
if (borderColor == null) {
throw new IllegalArgumentException("borderColor is null.");
}
this.borderColor = borderColor;
repaintComponent();
}
public final void setBorderColor(String borderColor) {
if (borderColor == null) {
throw new IllegalArgumentException("borderColor is null.");
}
setBorderColor(GraphicsUtilities.decodeColor(borderColor));
}
public Insets getPadding() {
return padding;
}
public void setPadding(Insets padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
this.padding = padding;
invalidateComponent();
}
public final void setPadding(Dictionary<String, ?> padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
setPadding(new Insets(padding));
}
public final void setPadding(int padding) {
setPadding(new Insets(padding));
}
public final void setPadding(Number padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
setPadding(padding.intValue());
}
public final void setPadding(String padding) {
if (padding == null) {
throw new IllegalArgumentException("padding is null.");
}
setPadding(Insets.decode(padding));
}
public boolean isResizable() {
return resizable;
}
public void setResizable(boolean resizable) {
this.resizable = resizable;
invalidateComponent();
}
public int getStateTransitionDuration() {
return stateTransitionDuration;
}
public void setStateTransitionDuration(int stateTransitionDuration) {
this.stateTransitionDuration = stateTransitionDuration;
}
public int getStateTransitionRate() {
return stateTransitionRate;
}
public void setStateTransitionRate(int stateTransitionRate) {
this.stateTransitionRate = stateTransitionRate;
}
@Override
public void windowOpened(Window window) {
super.windowOpened(window);
Display display = window.getDisplay();
display.getContainerMouseListeners().add(displayMouseListener);
display.reenterMouse();
dropShadowDecorator.setShadowOpacity(DropShadowDecorator.DEFAULT_SHADOW_OPACITY);
alignToOwner();
Window owner = window.getOwner();
owner.getComponentListeners().add(ownerListener);
openTransition = new OpenTransition(false);
openTransition.start(new TransitionListener() {
@Override
public void transitionCompleted(Transition transition) {
openTransition = null;
}
});
if (!window.requestFocus()) {
Component.clearFocus();
}
}
@Override
public void windowClosed(Window window, Display display, Window owner) {
super.windowClosed(window, display, owner);
display.getContainerMouseListeners().remove(displayMouseListener);
owner.getComponentListeners().remove(ownerListener);
}
@Override
public Vote previewSheetClose(final Sheet sheet, final boolean result) {
// Start a close transition, return false, and close the window
// when the transition is complete
Vote vote = Vote.APPROVE;
// Don't start the transition if the sheet is being closed as a result
// of the owner closing
Window owner = sheet.getOwner();
if (!(owner.isClosing()
|| owner.isClosed())) {
TransitionListener transitionListener = new TransitionListener() {
@Override
public void transitionCompleted(Transition transition) {
sheet.close(result);
openTransition = null;
}
};
if (openTransition == null) {
// Start the close transition
openTransition = new OpenTransition(true);
openTransition.start(transitionListener);
} else {
// Reverse the open transition
if (!openTransition.isReversed()
&& openTransition.isRunning()) {
openTransition.reverse(transitionListener);
}
}
vote = (openTransition != null
&& openTransition.isRunning()) ? Vote.DEFER : Vote.APPROVE;
}
return vote;
}
@Override
public void sheetCloseVetoed(Sheet sheet, Vote reason) {
if (reason == Vote.DENY
&& openTransition != null) {
openTransition.stop();
openTransition = null;
}
}
@Override
public void sheetClosed(Sheet sheet) {
// No-op
}
public void alignToOwner() {
Sheet sheet = (Sheet)getComponent();
Window owner = sheet.getOwner();
Bounds clientArea = owner.getClientArea();
Point location = owner.mapPointToAncestor(owner.getDisplay(), clientArea.x, clientArea.y);
sheet.setLocation(location.x + (clientArea.width - getWidth()) / 2, location.y);
}
}