/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.g2d.element.handler.impl; import java.awt.geom.Point2D; import java.lang.reflect.Method; import java.util.Map; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.diagram.participant.DiagramParticipant; import org.simantics.g2d.element.ElementUtils; import org.simantics.g2d.element.IElement; import org.simantics.g2d.element.handler.Clickable; import org.simantics.scenegraph.g2d.events.MouseEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonReleasedEvent; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin; import org.simantics.scenegraph.g2d.events.MouseEvent.MouseExitEvent; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; import org.simantics.utils.threads.Executable; import org.simantics.utils.threads.IThreadWorkQueue; import org.simantics.utils.threads.SyncListenerList; import org.simantics.utils.threads.ThreadUtils; /** * Base implementation for button handlers * * * @author Toni Kalajainen */ public abstract class AbstractClickable extends AbstractGrabbable implements Clickable { private static final long serialVersionUID = -8329973386869163106L; public static Key HOVER_KEY = new KeyOf(Boolean.class, "HOVER"); public static Key PRESS_STATUS_KEY = new KeyOf(PressStatus.class, "PRESS_STATUS"); public AbstractClickable(Double strayDistance) { super(1000.0); } public AbstractClickable() { super(); } /** * Get element press status * @param e * @param ctx * @return */ public PressStatus getPressStatus(IElement e, ICanvasContext ctx) { Map gis = getGrabs(e, ctx); if (gis==null || gis.size()==0) { Boolean hover = e.getHint(HOVER_KEY); if (hover != null && hover) return PressStatus.HOVER; return PressStatus.NORMAL; } // is held down boolean held = false; boolean pressing = false; for (GrabInfo gi : gis.values()) { held = true; pressing |= onPickCheck(e, ctx, gi.pointerId, gi.dragPosElement); if (pressing) break; } if (pressing) return PressStatus.PRESSED; if (held) return PressStatus.HELD; Boolean hover = e.getHint(HOVER_KEY); if (hover != null && hover) return PressStatus.HOVER; return PressStatus.NORMAL; } @Override public boolean handleMouseEvent(IElement e, ICanvasContext ctx, MouseEvent me) { //System.out.println("AbstractClickable.hME element:" + e + " me:" + me); boolean b = super.handleMouseEvent(e, ctx, me); Boolean hovering; // DND drag starts causes DragBegin + ButtonReleasedEvents, and hovering must be set false if (!(me instanceof MouseExitEvent || me instanceof MouseDragBegin || me instanceof MouseButtonReleasedEvent)) hovering = Boolean.valueOf(onPickCheck(e, ctx, me.mouseId, ElementUtils.controlToElementCoordinate(e, ctx, me.controlPosition, null))); else hovering = false; if (!hovering.equals(e.getHint(HOVER_KEY))) { e.setHint(HOVER_KEY, hovering); } // hackety hack PressStatus newStatus = getPressStatus(e, ctx); if (!newStatus.equals(e.getHint(PRESS_STATUS_KEY))) e.setHint(PRESS_STATUS_KEY, newStatus) ; return b; } @Override protected void onDrag(GrabInfo gi, ICanvasContext ctx) { } @Override protected void onGrab(GrabInfo gi, ICanvasContext ctx) { } @Override protected void onGrabCancel(GrabInfo gi, ICanvasContext ctx) { } @Override protected void onRelease(GrabInfo gi, ICanvasContext ctx) { // pick is pick until last mouse releases if (getGrabCount(gi.e, ctx)>0) return; boolean pick = onPickCheck(gi.e, ctx, gi.pointerId, gi.dragPosElement); if (pick) { onClicked(gi, ctx); fireClicked(gi.e, ctx); } } protected boolean onGrabCheck(IElement e, ICanvasContext ctx, int pointerId, Point2D pickPos) { Point2D elementPos = ElementUtils.controlToElementCoordinate(e, ctx, pickPos, null); return onPickCheck(e, ctx, pointerId, elementPos); } protected abstract void onClicked(GrabInfo gi, ICanvasContext ctx); /** * Pick check in element coordinates * @param e * @param ctx * @param pointerId * @param pickPos * @return */ protected abstract boolean onPickCheck(IElement e, ICanvasContext ctx, int pointerId, Point2D pickPos); private static final Key KEY_CLICK_LISTENERS = new KeyOf(SyncListenerList.class); @Override public void addListener(final IElement e, final ICanvasContext ctx, final IThreadWorkQueue thread, final ClickListener listener) { ThreadUtils.syncExec(ctx.getThreadAccess(), new Runnable() { @Override public void run() { DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); SyncListenerList list = dp.getElementHint(e, KEY_CLICK_LISTENERS); if (list==null) { list = new SyncListenerList(ClickListener.class); dp.setElementHint(e, KEY_CLICK_LISTENERS, list); } list.add(thread, listener); }}); } @Override public void removeListener(final IElement e, final ICanvasContext ctx, final IThreadWorkQueue thread, final ClickListener listener) { ThreadUtils.syncExec(ctx.getThreadAccess(), new Runnable() { @Override public void run() { DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); SyncListenerList list = dp.getElementHint(e, KEY_CLICK_LISTENERS); if (list==null) return; list.remove(thread, listener); if (list.isEmpty()) dp.removeElementHint(e, KEY_CLICK_LISTENERS); }}); } private final static Method onClick = SyncListenerList.getMethod(ClickListener.class, "onClick"); public void fireClicked(IElement e, ICanvasContext ctx) { DiagramParticipant dp = ctx.getSingleItem(DiagramParticipant.class); SyncListenerList list = dp.getElementHint(e, KEY_CLICK_LISTENERS); if (list==null) return; Executable exes[] = list.getExecutables(onClick, e, ctx); ThreadUtils.multiSyncExec(exes); } }