blob: 216c6765ea0f10040e3aa505f5c7e8f7cc96b5ff [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2007 Oracle Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Oracle Corporation - initial API and implementation
*******************************************************************************/
/**
*
*/
package org.eclipse.jst.pagedesigner.editpolicies;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Locator;
import org.eclipse.draw2d.MouseEvent;
import org.eclipse.draw2d.MouseListener;
import org.eclipse.draw2d.MouseMotionListener;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PrecisionRectangle;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jst.pagedesigner.PDPlugin;
import org.eclipse.jst.pagedesigner.parts.ElementEditPart;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
class MouseSelectableChildDecorator extends NonVisualChildDecorator
{
// no visual or affordance showing
private static final int STATE_START = 0;
// the host is showing hover feedback, but is not selected
private static final int STATE_HOST_HOVER = 1;
// the host has primary selection
private static final int STATE_HOST_SELECTED = 2;
// the selection handle for the decorator has mouse hover
private static final int STATE_HANDLE_HOVER = 3;
// the selection handle has been selected (is showing)
private static final int STATE_HANDLE_MENU_BAR_SHOWING = 4;
// the menu bar has hover
private static final int STATE_HANDLE_MENU_BAR_HOVER = 5;
// the menu bar has primary selection
private static final int STATE_HANDLE_MENU_BAR_SELECTED = 6;
public static final int EVENT_HOST_HOVER_RECEIVED = 31;
public static final int EVENT_HOST_HOVER_LOST = 32;
public static final int EVENT_HOST_SELECTION_RECEIVED = 33;
public static final int EVENT_HOST_SELECTION_LOST = 34;
private static final int EVENT_HANDLE_HOVER_RECEIVED = 35;
private static final int EVENT_HANDLE_HOVER_LOST = 36;
private static final int EVENT_HANDLE_SELECTED = 37;
private static final int EVENT_ALL_SELECTION_LOST = 38;
private static final int EVENT_MENU_BAR_SELECTION_RECEIVED = 39;
private MouseMotionListener _motionListener;
private MouseListener _mouseListener;
private boolean _isMouseOver = false;
private ElementMenuBar _elementMenuBar;
private DisplayStateMachine _stateMachine;
private VerticalMenuLocator _menuLocator;
private AnimatedHideLocator _hideLocator;
private IFigure _hoverParent;
private IFigure _selectionParent;
private ISelectionChangedListener _menuSelectionListener;
MouseSelectableChildDecorator(final GraphicalEditPart hostPart, int location,
IFigure hoverParent, IFigure selectionParent) {
super(hostPart, location);
_menuLocator = new VerticalMenuLocator(hostPart, this);
_hideLocator = new AnimatedHideLocator();
_elementMenuBar = ((ElementEditPart)hostPart).getElementMenuBar();
_stateMachine = new DisplayStateMachine();
_hoverParent = hoverParent;
_selectionParent = selectionParent;
_motionListener = new MouseMotionListener.Stub()
{
public void mouseEntered(MouseEvent me) {
_isMouseOver = true;
updateState(EVENT_HANDLE_HOVER_RECEIVED);
}
public void mouseExited(MouseEvent me) {
_isMouseOver = false;
updateState(EVENT_HANDLE_HOVER_LOST);
}
};
addMouseMotionListener(_motionListener);
_mouseListener = new MouseListener.Stub()
{
public void mousePressed(MouseEvent me) {
updateState(EVENT_HANDLE_SELECTED);
}
};
addMouseListener(_mouseListener);
_menuSelectionListener = new ISelectionChangedListener()
{
public void selectionChanged(SelectionChangedEvent event) {
IStructuredSelection selection = (IStructuredSelection) event.getSelection();
if (selection.size() == 0)
{
// if the host part has been given back selection, then
// we have a host selection event
if (getOwner().getSelected() == EditPart.SELECTED_PRIMARY)
{
updateState(EVENT_HOST_SELECTION_RECEIVED);
}
// otherwise, both the host and the non-visual children are lost,
// so fire all selection lost
else
{
updateState(EVENT_ALL_SELECTION_LOST);
}
}
// otherwise, one or more non-visual children have selection
else
{
updateState(EVENT_MENU_BAR_SELECTION_RECEIVED);
}
}
};
_elementMenuBar.addSelectionChangedListener(_menuSelectionListener);
}
public void paintFigure(Graphics g) {
// TODO: could we use an image label toggle button here instead?
Image arrowImage = null;
if (_stateMachine.isMenuShowing())
{
arrowImage = PDPlugin.getDefault().getImage("pin_down.gif");
}
else
{
arrowImage = PDPlugin.getDefault().getImage("pin_up.gif");
}
Rectangle r = getBounds();
g.setAlpha(75);
g.setBackgroundColor(getFillColor());
g.fillRectangle(r.x, r.y, r.width, r.height);
g.setAlpha(getAlpha());
g.drawImage(arrowImage, r.x+1, r.y+1);
g.setForegroundColor(getBorderColor());
g.drawRectangle(r.x, r.y, r.width-1, r.height-1);
}
/**
* @param newState
*/
public void updateState(int event)
{
int oldState = _stateMachine.doTransition(event);
updateVisual(oldState);
}
protected void updateVisual(int oldState)
{
// overriding all other considerations is whether the menu bar even has
// any items to show. If not don't show anything
if (!_elementMenuBar.hasChildParts())
{
if (getParent() != null)
{
getParent().remove(this);
}
return;
}
switch (_stateMachine._curState)
{
case STATE_START:
hide(_elementMenuBar, false);
IFigure parent = getParent();
if (parent != null)
{
parent.remove(this);
}
break;
case STATE_HOST_HOVER:
if (_hoverParent != null)
{
_hoverParent.add(this);
validate();
}
show(_elementMenuBar, false);
setVisible(false);
break;
case STATE_HOST_SELECTED:
if (_selectionParent != null)
{
_selectionParent.add(this);
validate();
}
setVisible(true);
if (oldState != STATE_HOST_SELECTED
&& oldState != STATE_HANDLE_HOVER)
{
show(_elementMenuBar, true);
hide(_elementMenuBar, true);
}
else
{
if (!_hideLocator._isAnimating)
{
hide(_elementMenuBar, false);
}
}
repaint();
break;
case STATE_HANDLE_HOVER:
if (_stateMachine.isMenuShowing(oldState))
{
hide(_elementMenuBar, false);
}
else
{
show(_elementMenuBar, false);
}
repaint();
break;
case STATE_HANDLE_MENU_BAR_SHOWING:
show(_elementMenuBar, true);
repaint();
break;
case STATE_HANDLE_MENU_BAR_HOVER:
case STATE_HANDLE_MENU_BAR_SELECTED:
//revalidate();
break;
default:
}
}
protected void init() {
setPreferredSize(new Dimension(12, 12));
}
public void dispose()
{
hide(_elementMenuBar, false);
if (_motionListener != null)
{
removeMouseMotionListener(_motionListener);
_motionListener = null;
}
if (_mouseListener != null)
{
removeMouseListener(_mouseListener);
_mouseListener = null;
}
if (_menuSelectionListener != null)
{
_elementMenuBar.removeSelectionChangedListener(_menuSelectionListener);
_menuSelectionListener = null;
}
}
private void hide(ElementMenuBar menuBar, boolean animate)
{
if (animate)
{
final Point endPoint = this.getLocation().getCopy();
//TODO: don't understand when translation is necessary...
//this.translateToAbsolute(endPoint);
endPoint.x += this.getBounds().width / 2;
endPoint.y += this.getBounds().height / 2;
_hideLocator.setHideEndPoint(endPoint);
_hideLocator.relocate(menuBar);
}
else
{
if (menuBar.getParent() != null)
{
getParent().remove(menuBar);
}
}
}
private void show(ElementMenuBar menuBar, boolean enabled)
{
menuBar.setEnabled(enabled);
getParent().add(menuBar);
_menuLocator.relocate(menuBar);
}
protected int getAlpha()
{
return (_isMouseOver || _stateMachine.isMenuShowing()) ? 255 : 75;
}
private class DisplayStateMachine
{
private int _curState = STATE_START;
public int doTransition(int event)
{
final int oldState = _curState;
switch(_curState)
{
case STATE_START:
// can only transition from start state
// on a host event
if (event == EVENT_HOST_HOVER_RECEIVED)
{
_curState = STATE_HOST_HOVER;
}
else if (event == EVENT_HOST_SELECTION_RECEIVED)
{
_curState = STATE_HOST_SELECTED;
}
break;
case STATE_HOST_HOVER:
if (event == EVENT_HOST_SELECTION_RECEIVED)
{
_curState = STATE_HOST_SELECTED;
}
else if (event == EVENT_HOST_SELECTION_LOST
|| event == EVENT_HOST_HOVER_LOST)
{
_curState = STATE_START;
}
else if (event == EVENT_HOST_HOVER_RECEIVED)
{
// preserve state in this case
}
break;
case STATE_HOST_SELECTED:
// once the host is selected,the only host event that
// that can change state is selection lost
if (event == EVENT_HOST_SELECTION_LOST)
{
_curState = STATE_START;
}
else if (event == EVENT_HANDLE_HOVER_RECEIVED)
{
_curState = STATE_HANDLE_HOVER;
}
else if (event == EVENT_HANDLE_SELECTED)
{
_curState = STATE_HANDLE_MENU_BAR_SHOWING;
}
else if (event == EVENT_ALL_SELECTION_LOST)
{
_curState = STATE_START;
}
break;
case STATE_HANDLE_HOVER:
if (event == EVENT_HANDLE_HOVER_LOST)
{
_curState = STATE_HOST_SELECTED;
}
else if (event == EVENT_HANDLE_SELECTED)
{
_curState = STATE_HANDLE_MENU_BAR_SHOWING;
}
else if (event == EVENT_HOST_SELECTION_LOST)
{
_curState = STATE_START;
}
break;
case STATE_HANDLE_MENU_BAR_SHOWING:
if (event == EVENT_HANDLE_SELECTED)
{
_curState = STATE_HANDLE_HOVER;
}
else if (event == EVENT_MENU_BAR_SELECTION_RECEIVED)
{
_curState = STATE_HANDLE_MENU_BAR_SELECTED;
}
else if (event == EVENT_ALL_SELECTION_LOST)
{
_curState = STATE_START;
}
break;
case STATE_HANDLE_MENU_BAR_HOVER:
break;
case STATE_HANDLE_MENU_BAR_SELECTED:
if (event == EVENT_ALL_SELECTION_LOST)
{
_curState = STATE_START;
}
else if (event == EVENT_HANDLE_SELECTED)
{
_curState = STATE_HANDLE_HOVER;
}
break;
}
return oldState;
}
public boolean isMenuShowing()
{
return isMenuShowing(_curState);
}
public boolean isMenuShowing(int state)
{
return _curState == STATE_HANDLE_MENU_BAR_SHOWING
|| _curState == STATE_HANDLE_MENU_BAR_HOVER
|| _curState == STATE_HANDLE_MENU_BAR_SELECTED;
}
}
private static class VerticalMenuLocator implements Locator
{
private IFigure _referenceFigure;
VerticalMenuLocator(GraphicalEditPart owner, IFigure reference)
{
_referenceFigure = reference;
}
public void relocate(IFigure target)
{
final Rectangle finalBounds = getFinalMenuBounds(target);
target.setBounds(finalBounds);
}
private Rectangle getInitialMenuBounds(final IFigure target)
{
Rectangle targetBounds =
new PrecisionRectangle(_referenceFigure.getBounds().getResized(-1, -1));
_referenceFigure.translateToAbsolute(targetBounds);
target.translateToRelative(targetBounds);
return targetBounds;
}
private Rectangle getFinalMenuBounds(final IFigure target)
{
final IFigure referenceFigure = _referenceFigure;
Rectangle targetBounds = getInitialMenuBounds(target);
Dimension targetSize = target.getPreferredSize();
// copied from super.relocate because relativeX/Y are private in super
// changed from super to remove div by 2 that centers target; we want
// it to be corner-to-corner
targetBounds.x
+= targetBounds.width+4;
targetBounds.y
-= (targetSize.height / 2) - referenceFigure.getBounds().height/2;
targetBounds.setSize(targetSize);
//target.setBounds(targetBounds);
// final Rectangle viewPortRect =
// ((IHTMLGraphicalViewer)_owner.getViewer()).getViewport().getBounds();
// final Rectangle targetRect = targetBounds.getCopy();
//
// targetRect.intersect(viewPortRect);
// int width = targetBounds.width - targetRect.width;
// int height = targetBounds.height - targetRect.height;
// if (width != 0)
// {
// targetBounds.x -= width;
// }
//
// if (height != 0)
// {
// targetBounds.y += height;
// }
return targetBounds;
}
}
private static class AnimatedHideLocator implements Locator
{
private Point _endPoint;
private boolean _isAnimating;
/**
* @param endPoint -- must be absolute coordinate
*/
public void setHideEndPoint(Point endPoint)
{
_endPoint = endPoint;
}
public void relocate(IFigure target)
{
final Point newEndPoint = _endPoint.getCopy();
target.translateToRelative(_endPoint);
Rectangle startBounds = target.getBounds().getCopy();
animateBoundsChange(target, startBounds, newEndPoint);
}
private void animateBoundsChange(final IFigure target,
final Rectangle startBounds,
final Point endPoint)
{
final int numSteps = 5;
final int numMs = 500;
final int timeSteps = numMs/numSteps;
int xDelta = endPoint.x - startBounds.x;
int yDelta = endPoint.y - startBounds.y;
final int widthIncrement = -1 * startBounds.width / numSteps;
final int heightIncrement = -1 * startBounds.height / numSteps;
int xIncrement = xDelta / numSteps;
int yIncrement = yDelta / numSteps;
target.setBounds(startBounds);
if (widthIncrement != 0 || heightIncrement != 0)
{
_isAnimating = true;
doAnimation(numMs, timeSteps, widthIncrement, heightIncrement, xIncrement, yIncrement, endPoint, target);
}
}
private void doAnimation(final int remainingTime,
final int timeIncrement,
final int widthIncrement, final int heightIncrement
, final int xIncrement, final int yIncrement
, final Point endPoint
, final IFigure target)
{
Display.getCurrent().timerExec(timeIncrement,
new Runnable()
{
public void run()
{
if (remainingTime <= 0)
{
if (target.getParent() != null)
{
target.getParent().remove(target);
}
_isAnimating = false;
}
else
{
final Rectangle curBounds = target.getBounds().getCopy();
curBounds.width += widthIncrement;
curBounds.height += heightIncrement;
curBounds.x += xIncrement;
curBounds.y += yIncrement;
target.setBounds(curBounds);
target.revalidate();
doAnimation(remainingTime-timeIncrement, timeIncrement, widthIncrement, heightIncrement, xIncrement, yIncrement, endPoint, target);
}
}
});
}
}
}