blob: 3ce75ee0080b51f65c4846adbd7a0124d8893314 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2019 CEA LIST.
*
*
* All rights reserved. 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:
* Xavier Le Pallec (for CEA LIST) xlepallec@lilo.org - Bug 558456
*
*****************************************************************************/
package org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.RootEditPart;
import org.eclipse.gmf.runtime.notation.BasicCompartment;
import org.eclipse.papyrus.infra.gmfdiag.css.CSSShapeImpl;
import org.eclipse.papyrus.uml.diagram.clazz.edit.parts.ClassAttributeCompartmentEditPart;
import org.eclipse.papyrus.uml.diagram.clazz.edit.parts.ClassAttributeCompartmentEditPartCN;
import org.eclipse.papyrus.uml.diagram.clazz.edit.parts.ClassNestedClassifierCompartmentEditPart;
import org.eclipse.papyrus.uml.diagram.clazz.edit.parts.ClassNestedClassifierCompartmentEditPartCN;
import org.eclipse.papyrus.uml.diagram.clazz.edit.parts.ClassOperationCompartmentEditPart;
import org.eclipse.papyrus.uml.diagram.clazz.edit.parts.ClassOperationCompartmentEditPartCN;
import org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.edit.policies.utils.ClassExpanderCollapser;
import org.eclipse.papyrus.uml.diagram.common.editparts.ClassEditPart;
/**
* This class is the heart of the interaction for the expand/collapse mechanism.
* As the name indicates it, the implementation of the interactions are done
* through a state machine. This state machine has a state that changes
* according the event the machine receives (through
* {@link StateMachine#fireEvent(Event)} method). <BR>
* To improve the code readability, the implementation of this method is
* distributed through different methods fireEvent_State_*: each one is
* responsible to treating any type of high-level events for a given state (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.States}).<BR>
* All these methods do generally the same thing: memorize the current hovered
* class, expand or collapse classes and add/remove listeners to some elements
* depending whether or not a new request begins.
*
* The state machine is defined like this <BR>
* <img alt="state machine diagram" src=
* "https://www.cristal.univ-lille.fr/miny/papyrus/stateMachine.jpg">
*
*/
public class StateMachine {
// To determine whether we are in the same diagram or not
private RootEditPart rootEditPart;
// To detect whether we hovering the same class or not
private ClassEditPart currentClassEditPart;
// To remember what compartments were visible before hovering (empty or not)
private Map<BasicCompartment, Boolean> compartmentsVisibilityBeforeHovering;
// Edit policies need to access to the lowEventManager of the state machine
// particularly to specify that the user is mouse-hovering an element
private LowEventManager lowEventManager;
// State of the state machine
private int state;
// Listener dedicated to the palette: when an palette action is over (ESC
// pressed or action performed), this listener will know it
private EndOfRequestListener endOfRequestListener;
/**
* A state machine is associated to the edit part associated to the class
* diagram (root edit part). This is the only one parameter of the constructor.
* The constructor creates a low event manager and set the state at START.
*
* @param rootEditPart root edit part of the class diagram.
*/
public StateMachine(RootEditPart rootEditPart) {
super();
this.rootEditPart = rootEditPart;
this.lowEventManager = StateMachineElementsFactory.getInstance().createLowEventManager(this);
setState(States.START);
}
/**
* This method is used to received event from the low event manager. The events
* are dispatched/distributed to methods fireEvent_State_*: each one is
* responsible to treating any type of high-level events for a given state.
*
* @param event the fired event
*/
public void fireEvent(Event event) {
switch (getState()) {
case States.START:
fireEvent_State_start(event);
break;
case States.WAITING_FROM_START:
fireEvent_State_waitingFromStart(event);
break;
case States.A_CLASS_IS_HOVERED:
fireEvent_State_aClassIsHovered(event);
break;
case States.ANOTHER_CLASS_IS_HOVERED:
fireEvent_State_anotherClassIsHovered(event);
break;
case States.NO_CLASS_IS_HOVERED:
fireEvent_State_noClassIsHovered(event);
break;
case States.CREATE_DROP_REQUEST_ENDED:
fireEvent_State_createDropRequestEnded(event);
break;
case States.END:
fireEvent_State_end(event);
break;
}
}
// **************************
//
// IMPLEMENTATION EVENTS
//
// **************************
// Events when state =
//
// START
//
/**
* This method treats events when being in the state START
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_start(Event event) {
switch (event.getEventCode()) {
case Events.NEW_CREATE_REQUEST:
case Events.NEW_DROP_REQUEST:
listenEndOfRequest();
setState(States.WAITING_FROM_START);
break;
}
}
// Events when state =
//
// WAITING FROM THE START
//
/**
* This method treats events when being in the state WAITING_FROM_START
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_waitingFromStart(Event event) {
switch (event.getEventCode()) {
case Events.HOVERING_A_CLASS:
ClassEditPart hoveredClass = getCorrespondingClassEditPart(event.getEditPart());
memorizeTheHoveredClass(hoveredClass);
expandClassIfPossible();
setState(States.A_CLASS_IS_HOVERED);
break;
case Events.HOVERING_SOMETHING_BUT_A_CLASS:
// Nothing to do
break;
case Events.ACTIVE_TOOL_CHANGED:
doNotListenEndOfRequest();
setState(States.END);
noMoreMemorizedRequest();
break;
}
}
// Events when state =
//
// A CLASS IS HOVERED
//
/**
* This method treats events when being in the state A_CLASS_IS_HOVERED
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_aClassIsHovered(Event event) {
switch (event.getEventCode()) {
case Events.HOVERING_A_CLASS:
// It shouldn't normally happen
break;
case Events.HOVERING_ANOTHER_CLASS:
ClassEditPart hoveredClass = getCorrespondingClassEditPart(event.getEditPart());
collapseClassIfPossible();
memorizeTheHoveredClass(hoveredClass);
expandClassIfPossible();
setState(States.ANOTHER_CLASS_IS_HOVERED);
break;
case Events.HOVERING_SOMETHING_BUT_A_CLASS:
collapseClassIfPossible();
noMoreMemorizedClass();
setState(States.NO_CLASS_IS_HOVERED);
break;
case Events.ACTIVE_TOOL_CHANGED:
collapseClassIfPossible();
noMoreMemorizedClass();
doNotListenEndOfRequest();
setState(States.CREATE_DROP_REQUEST_ENDED);
setState(States.END);
noMoreMemorizedRequest();
break;
}
}
// Events when state =
//
// ANOTHER CLASS IS HOVERED
//
/**
* This method treats events when being in the state ANOTHER_CLASS_IS_HOVERED
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_anotherClassIsHovered(Event event) {
// all possible events will perform the same operations
// as if we were in the AClassIsHovered state
fireEvent_State_aClassIsHovered(event);
}
// Events when state =
//
// NO CLASS IS HOVERED
//
/**
* This method treats events when being in the state NO_CLASS_IS_HOVERED
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_noClassIsHovered(Event event) {
switch (event.getEventCode()) {
case Events.HOVERING_A_CLASS:
ClassEditPart hoveredClass = getCorrespondingClassEditPart(event.getEditPart());
memorizeTheHoveredClass(hoveredClass);
expandClassIfPossible();
setState(States.A_CLASS_IS_HOVERED);
break;
case Events.HOVERING_ANOTHER_CLASS:
// It shouldn't normally happen
break;
case Events.HOVERING_SOMETHING_BUT_A_CLASS:
// Nothing to do
break;
case Events.ACTIVE_TOOL_CHANGED:
doNotListenEndOfRequest();
setState(States.END);
noMoreMemorizedRequest();
break;
}
}
// Events when state =
//
// NO CLASS IS CREATE DROP REQUEST ENDED
//
/**
* This method treats events when being in the state CREATE_DROP_REQUEST_ENDED
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_createDropRequestEnded(Event event) {
// normally, this states doesn't last
}
/**
* This method treats events when being in the state END
*
* @param event event (see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.Events})
*/
protected void fireEvent_State_end(Event event) {
switch (event.getEventCode()) {
case Events.NEW_CREATE_REQUEST:
case Events.NEW_DROP_REQUEST:
listenEndOfRequest();
setState(States.WAITING_FROM_START);
break;
}
}
// **************************
//
// OPERATIONS USED BY STATE MACHINE
//
// **************************
// listen palette in order to detect a change of tool
private void listenEndOfRequest() {
setEndOfRequestListener(StateMachineElementsFactory.getInstance().createEndOfRequestListener(this));
rootEditPart.getViewer().getEditDomain().getPaletteViewer().addPaletteListener(getEndOfRequestListener());
}
// do not listen palette (and so do not detect a change of tool anymore)
private void doNotListenEndOfRequest() {
rootEditPart.getViewer().getEditDomain().getPaletteViewer().removePaletteListener(getEndOfRequestListener());
setEndOfRequestListener(null);
}
// get the container / class edit part if the edit part parameter is not a class
// edit part (and so it would seem to be a component of a class)
private ClassEditPart getCorrespondingClassEditPart(EditPart editPart) {
while (!(editPart instanceof ClassEditPart)) {
editPart = editPart.getParent();
}
return (ClassEditPart) editPart;
}
// do not memorize a class anymore (not hovering a class)
private void noMoreMemorizedClass() {
setCurrentClassEditPart(null);
setCompartmentsVisibilityBeforeHoovering(null);
}
// get the memorized class
private ClassEditPart memorizedClass() {
return getCurrentClassEditPart();
}
// memorize the current hovered class.
private void memorizeTheHoveredClass(ClassEditPart hoveredClass) {
setCurrentClassEditPart(hoveredClass);
setCompartmentsVisibilityBeforeHoovering(getCompartmentsVisibility());
}
// collapse if there are compartments to hide
// use an instance of ExpandingCollapsing
private void collapseClassIfPossible() {
new ClassExpanderCollapser(getCurrentClassEditPart(), getLowEventManager().getConcernedCompartment(),
getCompartmentsVisibilityBeforeHoovering()).collapseIfPossible();
}
// expand if there are compartments to hide
// use an instance of ExpandingCollapsing
private void expandClassIfPossible() {
new ClassExpanderCollapser(getCurrentClassEditPart(), getLowEventManager().getConcernedCompartment(),
getCompartmentsVisibilityBeforeHoovering()).expandIfPossible();
}
private void noMoreMemorizedRequest() {
getLowEventManager().setCurrentRequest(null);
}
// get the notation-level class from the GEF-level one
private CSSShapeImpl getCorrespondingGraphicalClass(ClassEditPart classEditPart) {
return (CSSShapeImpl) classEditPart.getModel();
}
private Map<BasicCompartment, Boolean> getCompartmentsVisibility() {
Map<BasicCompartment, Boolean> compartmentList = new HashMap<BasicCompartment, Boolean>();
for (Object child : memorizedClass().getChildren()) {
if (child instanceof EditPart) {
EditPart editPart = EditPart.class.cast(child);
if (editPart.getModel() instanceof BasicCompartment) {
BasicCompartment compartment = BasicCompartment.class.cast(editPart.getModel());
if (compartment.getType().equals(ClassAttributeCompartmentEditPart.VISUAL_ID)
|| compartment.getType().equals(ClassAttributeCompartmentEditPartCN.VISUAL_ID)
|| compartment.getType().equals(ClassOperationCompartmentEditPart.VISUAL_ID)
|| compartment.getType().equals(ClassOperationCompartmentEditPartCN.VISUAL_ID)
|| compartment.getType().equals(ClassNestedClassifierCompartmentEditPart.VISUAL_ID)
|| compartment.getType().equals(ClassNestedClassifierCompartmentEditPartCN.VISUAL_ID)) {
compartmentList.put(compartment, compartment.isVisible());
}
}
}
}
return compartmentList;
}
// **************************
//
// GETTERS / SETTERS
//
// **************************
/**
* @return current hovered class
*/
public ClassEditPart getCurrentClassEditPart() {
return currentClassEditPart;
}
/**
* set the current hovered class
*
* @param currentClassEditPart
*/
public void setCurrentClassEditPart(ClassEditPart currentClassEditPart) {
this.currentClassEditPart = currentClassEditPart;
}
/**
* get the root edit part (which the state machine is associated to).
*
* @return the root edit part
*/
public RootEditPart getRootEditPart() {
return rootEditPart;
}
/**
* get the associated low event manager
*
* @return associated low event manager
*/
public LowEventManager getLowEventManager() {
return lowEventManager;
}
/**
* Get the current state. For more details, see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.States}
*
* @return current state
*/
public int getState() {
return state;
}
/**
* Set the current stage. For more details, see
* {@link org.eclipse.papyrus.uml.diagram.clazz.lf.autosizeableclasses.statemachine.States}
*
* @param state the new current state
*/
public void setState(int state) {
this.state = state;
}
/**
* Get the listener of the palette (for detecting change of tool)
*
* @return the listener
*/
public EndOfRequestListener getEndOfRequestListener() {
return endOfRequestListener;
}
/**
* Set the listener of the palette (for detecting change of tool)
*
* @param endOfRequestListener the listener
*/
public void setEndOfRequestListener(EndOfRequestListener endOfRequestListener) {
this.endOfRequestListener = endOfRequestListener;
}
public void setCompartmentsVisibilityBeforeHoovering(
Map<BasicCompartment, Boolean> compartmentsVisibilityBeforeHoovering) {
this.compartmentsVisibilityBeforeHovering = compartmentsVisibilityBeforeHoovering;
}
public Map<BasicCompartment, Boolean> getCompartmentsVisibilityBeforeHoovering() {
return compartmentsVisibilityBeforeHovering;
}
}