/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.fge.view.listener;
import java.awt.Component;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import org.openflexo.fge.ConnectorGraphicalRepresentation;
import org.openflexo.fge.GraphicalRepresentation;
import org.openflexo.fge.ShapeGraphicalRepresentation;
import org.openflexo.fge.controller.DrawingController;
import org.openflexo.fge.controller.DrawingController.EditorTool;
import org.openflexo.fge.controller.MouseClickControl;
import org.openflexo.fge.controller.MouseDragControl;
import org.openflexo.fge.cp.ControlArea;
import org.openflexo.fge.geom.FGEPoint;
import org.openflexo.fge.view.FGEPaintManager;
import org.openflexo.fge.view.FGEView;
import org.openflexo.fge.view.LabelView;
import org.openflexo.toolbox.ToolBox;
public class FGEViewMouseListener implements MouseListener, MouseMotionListener {
private static final Logger logger = Logger.getLogger(FGEViewMouseListener.class.getPackage().getName());
private GraphicalRepresentation<?> graphicalRepresentation;
protected FGEView<?> view;
public FGEViewMouseListener(GraphicalRepresentation<?> aGraphicalRepresentation, FGEView<?> aView) {
graphicalRepresentation = aGraphicalRepresentation;
view = aView;
}
private MouseEvent previousEvent;
@Override
public void mouseClicked(MouseEvent e) {
if (view.isDeleted()) {
return;
}
if (ToolBox.getPLATFORM() == ToolBox.MACOS) {
if (e.getClickCount() == 2 && previousEvent != null) {
if (previousEvent.getClickCount() == 1 && previousEvent.getComponent() == e.getComponent()
&& previousEvent.getButton() != e.getButton()) {
e = new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), 1,
e.isPopupTrigger());
}
}
}
previousEvent = e;
boolean editable = getController().getDrawing().isEditable();
switch (getController().getCurrentTool()) {
case SelectionTool:
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
if (focusedObject == null) {
focusedObject = graphicalRepresentation.getDrawing().getDrawingGraphicalRepresentation();
}
editable &= focusedObject != null && !focusedObject.getIsReadOnly();
if (editable) {
if (getController().hasEditedLabel()) {
if (handleEventForEditedLabel(e, focusedObject)) {
return;
// return;
}
}
if (focusedObject != null && getFocusRetriever().focusOnFloatingLabel(focusedObject, e) && getController().hasEditedLabel()
&& getController().getEditedLabel().getGraphicalRepresentation() == focusedObject) {
// Special case, do nothing, since we let the label live its life !!!
e.consume();
return;
}
if (focusedObject != null && e.getClickCount() == 2 && getFocusRetriever().focusOnFloatingLabel(focusedObject, e)) {
if (focusedObject instanceof ShapeGraphicalRepresentation) {
view.getDrawingView().shapeViewForObject((ShapeGraphicalRepresentation<?>) focusedObject).getLabelView()
.startEdition();
e.consume();
return;
} else if (focusedObject instanceof ConnectorGraphicalRepresentation) {
view.getDrawingView().connectorViewForObject((ConnectorGraphicalRepresentation<?>) focusedObject).getLabelView()
.startEdition();
e.consume();
return;
}
}
if (focusedObject != null) {
ControlArea<?> ca = getFocusRetriever().getFocusedControlAreaForDrawable(focusedObject, e);
if (ca != null && ca.isClickable()) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Click on control area " + ca);
}
Point clickedLocationInDrawingView = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(),
view.getDrawingView());
FGEPoint clickedPoint = ca.getGraphicalRepresentation().convertRemoteViewCoordinatesToLocalNormalizedPoint(
clickedLocationInDrawingView, view.getDrawingView().getGraphicalRepresentation(),
view.getDrawingView().getScale());
if (ca.clickOnPoint(clickedPoint, e.getClickCount())) {
// Event was successfully handled
e.consume();
return;
}
}
}
}
if (view.isDeleted()) {
return;
}
// We have now performed all low-level possible actions, let's go for the registered mouse controls
for (MouseClickControl mouseClickControl : focusedObject.getMouseClickControls()) {
if ((editable || !mouseClickControl.isModelEditionAction())
&& mouseClickControl.isApplicable(focusedObject, getController(), e)) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Applying " + mouseClickControl);
}
mouseClickControl.handleClick(focusedObject, getController(), e);
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Ignoring " + mouseClickControl);
}
}
}
break;
case DrawShapeTool:
if (editable) {
getController().getDrawShapeToolController().mouseClicked(e);
}
default:
break;
}
}
@Override
public void mouseEntered(MouseEvent e) {
if (view.isDeleted()) {
return;
}
// SGU: I dont think that JTextComponent react to these event, but in case of, uncomment this
// GPO: Actually this is not the case because a mouse enter/exited event in one component does
// not make sense for another one. If we want to emulate those event, it should be done
// in the mouse moved event.
/*if (getController().hasEditedLabel()) {
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
handleEventForEditedLabel(e, focusedObject);
}*/
}
@Override
public void mouseExited(MouseEvent e) {
if (view.isDeleted()) {
return;
}
// SGU: I dont think that JTextComponent react to these event, but in case of, uncomment this
// GPO: Actually this is not the case because a mouse enter/exited event in one component does
// not make sense for another one. If we want to emulate those event, it should be done
// in the mouse moved event.
/*if (getController().hasEditedLabel()) {
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
handleEventForEditedLabel(e, focusedObject);
}*/
}
private ControlAreaDrag currentControlAreaDrag = null;
private class ControlAreaDrag {
private Point startMovingLocationInDrawingView;
private FGEPoint startMovingPoint;
private ControlArea<?> controlArea;
private double initialWidth;
private double initialHeight;
private ControlAreaDrag(ControlArea<?> aControlArea, MouseEvent e) {
controlArea = aControlArea;
startMovingLocationInDrawingView = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), view.getDrawingView());
logger.fine("ControlPointDrag: start pt = " + startMovingLocationInDrawingView);
FGEPoint relativeStartMovingPoint = controlArea.getGraphicalRepresentation()
.convertRemoteViewCoordinatesToLocalNormalizedPoint(startMovingLocationInDrawingView,
view.getDrawingView().getGraphicalRepresentation(), view.getDrawingView().getScale());
startMovingPoint = aControlArea.getArea().getNearestPoint(relativeStartMovingPoint);
Point clickedLocationInDrawingView = SwingUtilities
.convertPoint((Component) e.getSource(), e.getPoint(), view.getDrawingView());
aControlArea.startDragging(
getController(),
aControlArea.getGraphicalRepresentation().convertRemoteViewCoordinatesToLocalNormalizedPoint(
clickedLocationInDrawingView, view.getDrawingView().getGraphicalRepresentation(),
view.getDrawingView().getScale()));
if (controlArea.getGraphicalRepresentation().isConnectedToDrawing()) {
initialWidth = controlArea.getGraphicalRepresentation().getViewWidth(view.getScale());
initialHeight = controlArea.getGraphicalRepresentation().getViewHeight(view.getScale());
} else {
// System.out.println("Not connected to drawing");
initialWidth = 0;
initialHeight = 0;
}
}
private boolean moveTo(Point newLocationInDrawingView, MouseEvent e) {
FGEPoint newAbsoluteLocation = new FGEPoint(startMovingPoint.x
+ (newLocationInDrawingView.x - startMovingLocationInDrawingView.x) / view.getScale(), startMovingPoint.y
+ (newLocationInDrawingView.y - startMovingLocationInDrawingView.y) / view.getScale());
FGEPoint newRelativeLocation = getGraphicalRepresentation().getDrawingGraphicalRepresentation()
.convertLocalViewCoordinatesToRemoteNormalizedPoint(newLocationInDrawingView, controlArea.getGraphicalRepresentation(),
view.getScale());
FGEPoint pointRelativeToInitialConfiguration = new FGEPoint(startMovingPoint.x
+ (newLocationInDrawingView.x - startMovingLocationInDrawingView.x) / initialWidth/* *view.getScale()*/,
startMovingPoint.y + (newLocationInDrawingView.y - startMovingLocationInDrawingView.y) / initialHeight/* *view.getScale()*/);
return controlArea.dragToPoint(newRelativeLocation, pointRelativeToInitialConfiguration, newAbsoluteLocation, startMovingPoint,
e);
}
private void stopDragging(GraphicalRepresentation<?> focusedGR) {
controlArea.stopDragging(getController(), focusedGR);
}
}
private FloatingLabelDrag currentFloatingLabelDrag = null;
private class FloatingLabelDrag {
private GraphicalRepresentation<?> graphicalRepresentation;
private Point startMovingLocationInDrawingView;
private Point startLabelPoint;
private boolean started = false;
private FloatingLabelDrag(GraphicalRepresentation<?> aGraphicalRepresentation, Point startMovingLocationInDrawingView) {
graphicalRepresentation = aGraphicalRepresentation;
this.startMovingLocationInDrawingView = startMovingLocationInDrawingView;
logger.fine("FloatingLabelDrag: start pt = " + startMovingLocationInDrawingView);
startLabelPoint = graphicalRepresentation.getLabelLocation(view.getScale());
}
private void startDragging() {
graphicalRepresentation.notifyLabelWillMove();
}
private void moveTo(Point newLocationInDrawingView) {
if (!started) {
startDragging();
started = true;
}
Point newLabelCenterPoint = new Point(startLabelPoint.x + newLocationInDrawingView.x - startMovingLocationInDrawingView.x,
startLabelPoint.y + newLocationInDrawingView.y - startMovingLocationInDrawingView.y);
graphicalRepresentation.setLabelLocation(newLabelCenterPoint, view.getScale());
/*if (graphicalRepresentation instanceof ShapeGraphicalRepresentation
&& ((ShapeGraphicalRepresentation)graphicalRepresentation).isParentLayoutedAsContainer()) {
Point resultingLocation = graphicalRepresentation.getLabelViewCenter(view.getScale());
if (!resultingLocation.equals(newLabelCenterPoint)) {
int dx = resultingLocation.x-newLabelCenterPoint.x;
int dy = resultingLocation.y-newLabelCenterPoint.y;
startLabelCenterPoint.x = startLabelCenterPoint.x+dx;
startLabelCenterPoint.y = startLabelCenterPoint.y+dy;
}
}*/
}
private void stopDragging() {
if (getPaintManager().isPaintingCacheEnabled()) {
getPaintManager().removeFromTemporaryObjects(graphicalRepresentation);
getPaintManager().invalidate(graphicalRepresentation);
getPaintManager().repaint(view.getDrawingView());
}
}
}
@Override
public void mousePressed(MouseEvent e) {
if (view.isDeleted()) {
return;
}
boolean editable = getController().getDrawing().isEditable();
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
if (focusedObject == null) {
focusedObject = graphicalRepresentation.getDrawing().getDrawingGraphicalRepresentation();
}
editable &= !focusedObject.getIsReadOnly();
if (editable) {
if (getController().hasEditedLabel()) {
if (handleEventForEditedLabel(e, focusedObject)) {
// Special case, do nothing, since we let the label live its life !!!
return;
}
}
getController().stopEditionOfEditedLabelIfAny();
if (focusedObject.hasFloatingLabel() && getFocusRetriever().focusOnFloatingLabel(focusedObject, e)) {
currentFloatingLabelDrag = new FloatingLabelDrag(focusedObject, SwingUtilities.convertPoint((Component) e.getSource(),
e.getPoint(), view.getDrawingView()));
e.consume();
return;
} else {
ControlArea<?> ca = getFocusRetriever().getFocusedControlAreaForDrawable(focusedObject, e);
if (ca != null && ca.isDraggable()) {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Starting drag of control point " + ca);
}
currentControlAreaDrag = new ControlAreaDrag(ca, e);
e.consume();
return;
}
}
}
// We have now performed all low-level possible actions, let's go for the registered mouse controls
List<MouseDragControl> applicableMouseDragControls = new ArrayList<MouseDragControl>();
for (MouseDragControl mouseDragControl : focusedObject.getMouseDragControls()) {
if ((editable || !mouseDragControl.isModelEditionAction()) && mouseDragControl.isApplicable(focusedObject, getController(), e)) {
applicableMouseDragControls.add(mouseDragControl);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Found applicable " + mouseDragControl);
}
} else {
if (logger.isLoggable(Level.FINE)) {
logger.fine("Ignoring " + mouseDragControl);
}
}
}
if (applicableMouseDragControls.size() == 0) {
// No applicable mouse drag
return;
}
if (applicableMouseDragControls.size() > 1) {
logger.warning("More than one applicable MouseDragControl for graphical representation: " + focusedObject
+ " Applying first and forgetting others...");
}
// Apply applicable mouse drag control
MouseDragControl currentMouseDrag = applicableMouseDragControls.get(0);
if (logger.isLoggable(Level.FINE)) {
logger.fine("Applying " + currentMouseDrag);
}
if (currentMouseDrag.handleMousePressed(focusedObject, getController(), e)) {
// Everything OK
if (getController() != null) {
getController().setCurrentMouseDrag(currentMouseDrag);
}
} else {
// Something failed, abort this drag
if (getController() != null) {
getController().setCurrentMouseDrag(null);
}
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (view.isDeleted()) {
return;
}
if (getController().hasEditedLabel()) {
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
if (handleEventForEditedLabel(e, focusedObject)) {
return;
}
// Special case, do nothing, since we let the label live its life !!!
e.consume();
return;
}
/*if (getController().hasEditedLabel()) {
// Special case, do nothing, since we let the label live its life !!!
e.consume();
return;
}*/
if (currentFloatingLabelDrag != null) {
currentFloatingLabelDrag.stopDragging();
currentFloatingLabelDrag = null;
e.consume();
}
if (currentControlAreaDrag != null) {
GraphicalRepresentation<?> focusedGR = getFocusRetriever().getFocusedObject(e);
// logger.info("Stop dragging, focused on " + focusedGR.getDrawable());
currentControlAreaDrag.stopDragging(focusedGR);
currentControlAreaDrag = null;
e.consume();
}
if (view.isDeleted()) {
return;
}
// We have now performed all low-level possible actions, let's go for the registered mouse controls
if (getController().getCurrentMouseDrag() != null) {
DrawingController<?> controller = getController();
controller.getCurrentMouseDrag().handleMouseReleased(getController(), e);
controller.setCurrentMouseDrag(null);
}
}
@Override
public void mouseDragged(MouseEvent e) {
if (view.isDeleted()) {
return;
}
boolean editable = getController().getDrawing().isEditable();
if (editable) {
if (getController().hasEditedLabel()) {
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
if (handleEventForEditedLabel(e, focusedObject)) {
return;
}
// Special case, do nothing, since we let the label live its life !!!
e.consume();
return;
}
/*if (getController().hasEditedLabel()) {
// Special case, do nothing, since we let the label live its life !!!
e.consume();
return;
}*/
if (currentFloatingLabelDrag != null) {
Point newPointLocation = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), view.getDrawingView());
currentFloatingLabelDrag.moveTo(newPointLocation);
e.consume();
}
if (currentControlAreaDrag != null) {
Point newPointLocation = SwingUtilities.convertPoint((Component) e.getSource(), e.getPoint(), view.getDrawingView());
boolean continueDragging = currentControlAreaDrag.moveTo(newPointLocation, e);
e.consume();
if (!continueDragging) {
GraphicalRepresentation focusedGR = getFocusRetriever().getFocusedObject(e);
// logger.info("Stop dragging, focused on " + focusedGR.getDrawable());
currentControlAreaDrag.stopDragging(focusedGR);
logger.fine("OK, stopping dragging point");
currentControlAreaDrag = null;
}
}
}
// We have now performed all low-level possible actions, let's go for the registered mouse controls
getFocusRetriever().handleMouseMove(e);
if (getController().getCurrentMouseDrag() != null) {
getController().getCurrentMouseDrag().handleMouseDragged(getController(), e);
}
}
@Override
public void mouseMoved(MouseEvent e) {
if (view.isDeleted()) {
return;
}
if (getController().hasEditedLabel()) {
GraphicalRepresentation<?> focusedObject = getFocusRetriever().getFocusedObject(e);
if (handleEventForEditedLabel(e, focusedObject)) {
return;
// Special case, do nothing, since we let the label live its life !!!
/*e.consume();
return;*/
}
}
/*if (getController().hasEditedLabel()) {
// Special case, do nothing, since we let the label live its life !!!
e.consume();
return;
}*/
// We have now performed all low-level possible actions, let's go for the registered mouse controls
if (getController().getCurrentMouseDrag() != null) {
getController().getCurrentMouseDrag().handleMouseDragged(getController(), e);
}
getFocusRetriever().handleMouseMove(e);
if (getController().getCurrentTool() == EditorTool.DrawShapeTool) {
getController().getDrawShapeToolController().mouseMoved(e);
}
}
private Stack<MouseEvent> eventStack;
/**
* What happen here ? (SGU)
*
* Well, it's a long and difficult story. We have here a totally different view paradigm from Swing (where all view are rectangle).
* Here, we manage transparency, layers and complex shapes. That means that the mouse listener is sometimes not belonging to the view
* displayed accessed object.
*
* But we use swing in the context of text edition. Sometimes, we receive mouse events regarding JTextComponent management on some views
* that have nothing to do with the label this JTextComponent is representing. (focusedObject is not necessary object represented by the
* view)
*
* So, we have here to implement a re-targeting scheme for those events, for swing to correctly handle those events.
*
* @param e
* @param focusedObject
* @return
*/
private boolean handleEventForEditedLabel(MouseEvent e, GraphicalRepresentation<?> focusedObject) {
if (focusedObject == null || !focusedObject.getDrawing().isEditable()) {
return false;
}
LabelView<?> labelView = getController().getEditedLabel();
Point pointRelativeToTextComponent = SwingUtilities.convertPoint((Component) view, e.getPoint(), labelView);
if (labelView.getGraphicalRepresentation() == focusedObject) {
// Label being edited matches focused object:
// We potentially need to redispatch this event
if (labelView.contains(pointRelativeToTextComponent)) {
if (!labelView.isMouseInsideLabel()) {
MouseEvent newEvent = new MouseEvent(labelView.getTextComponent(), MouseEvent.MOUSE_ENTERED, e.getWhen(),
e.getModifiers(), pointRelativeToTextComponent.x, pointRelativeToTextComponent.y, e.getClickCount(),
e.isPopupTrigger());
labelView.getTextComponent().dispatchEvent(newEvent);
}
if (labelView.isEditing()) {
if (eventStack == null || eventStack.isEmpty() || eventStack.peek() != e) {
// This event effectively concerns related text component
// I will retarget it !
MouseEvent newEvent = new MouseEvent(labelView.getTextComponent(), e.getID(), e.getWhen(), e.getModifiers(),
pointRelativeToTextComponent.x, pointRelativeToTextComponent.y, e.getClickCount(), e.isPopupTrigger());
if (eventStack == null) {
eventStack = new Stack<MouseEvent>();
}
eventStack.add(newEvent);
labelView.getTextComponent().dispatchEvent(newEvent);
eventStack.pop();
if (eventStack.isEmpty()) {
eventStack = null;
}
e.consume();
return true;
}
}
} else {
triggerMouseExitedIfNeeded(e, labelView, pointRelativeToTextComponent);
}
return false;
} else {
triggerMouseExitedIfNeeded(e, labelView, pointRelativeToTextComponent);
}
return false;
}
private void triggerMouseExitedIfNeeded(MouseEvent e, LabelView<?> labelView, Point pointRelativeToTextComponent) {
if (labelView.isMouseInsideLabel()) {
MouseEvent newEvent = new MouseEvent(labelView.getTextComponent(), MouseEvent.MOUSE_EXITED, e.getWhen(), e.getModifiers(),
pointRelativeToTextComponent.x, pointRelativeToTextComponent.y, e.getClickCount(), e.isPopupTrigger());
labelView.getTextComponent().dispatchEvent(newEvent);
}
}
public DrawingController<?> getController() {
return view.getController();
}
public FocusRetriever getFocusRetriever() {
return view.getDrawingView().getFocusRetriever();
}
public Object getDrawable() {
return getGraphicalRepresentation().getDrawable();
}
public FGEView<?> getView() {
return view;
}
public GraphicalRepresentation<?> getGraphicalRepresentation() {
return graphicalRepresentation;
}
public FGEPaintManager getPaintManager() {
return view.getPaintManager();
}
}