| /******************************************************************************* |
| * 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; |
| |
| /** |
| * A child decorator that supports mouse selection |
| * |
| * @author cbateman |
| * |
| */ |
| class MouseSelectableChildDecorator extends NonVisualChildDecorator |
| { |
| private static final String PIN_UP_IMAGE_FILE = "pin_up.gif"; //$NON-NLS-1$ |
| |
| private static final String PIN_DOWN_IMAGE_FILE = "pin_down.gif"; //$NON-NLS-1$ |
| |
| // 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; |
| |
| /** |
| * An event indicating the host received hover |
| */ |
| public static final int EVENT_HOST_HOVER_RECEIVED = 31; |
| /** |
| * An event indicating the host lost hover |
| */ |
| public static final int EVENT_HOST_HOVER_LOST = 32; |
| /** |
| * An event indicating the host received selection |
| */ |
| public static final int EVENT_HOST_SELECTION_RECEIVED = 33; |
| /** |
| * An event indicating the host lost selection |
| */ |
| 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_IMAGE_FILE); |
| } |
| else |
| { |
| arrowImage = PDPlugin.getDefault().getImage(PIN_UP_IMAGE_FILE); |
| } |
| |
| 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 event |
| */ |
| public void updateState(int event) |
| { |
| int oldState = _stateMachine.doTransition(event); |
| updateVisual(oldState); |
| } |
| |
| /** |
| * @param 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; |
| |
| /** |
| * @param event |
| * @return execute a state machine transition on event |
| */ |
| 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; |
| } |
| |
| /** |
| * @return true if the menu should be showing in the current state |
| */ |
| public boolean isMenuShowing() |
| { |
| return isMenuShowing(_curState); |
| } |
| |
| /** |
| * @param state |
| * @return true if state is one in which the menu should be showing |
| */ |
| 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); |
| } |
| } |
| }); |
| } |
| } |
| } |
| |