| /***************************************************************************** |
| * 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; |
| } |
| |
| } |