package org.apollo; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apollo.gio.gesture.data.TrackAdjustmentGestureData; import org.apollo.util.Mutable; import org.apollo.widgets.LinkedTrack; import org.apollo.widgets.SampledTrack; import org.apollo.widgets.TrackWidgetCommons; import org.expeditee.core.Dimension; import org.expeditee.core.Point; import org.expeditee.gio.EcosystemManager; import org.expeditee.gio.gesture.Gesture; import org.expeditee.gio.gesture.Gesture.GestureType; import org.expeditee.gio.gesture.GestureAction; import org.expeditee.gio.gesture.GestureListener; import org.expeditee.gio.gesture.StandardGestureActions; import org.expeditee.gio.gesture.StandardGestureActions.StandardGestureType; import org.expeditee.gio.gesture.data.ItemSpecificGestureData; import org.expeditee.gio.gesture.data.PickUpGestureData; import org.expeditee.gio.input.KBMInputEvent.Key; import org.expeditee.gio.input.StandardInputEventListeners; import org.expeditee.gui.DisplayController; import org.expeditee.gui.Frame; import org.expeditee.gui.FrameIO; import org.expeditee.gui.FreeItems; import org.expeditee.items.Dot; import org.expeditee.items.Item; import org.expeditee.items.Item.HighlightMode; import org.expeditee.items.ItemUtils; import org.expeditee.items.Line; import org.expeditee.items.Text; import org.expeditee.items.UserAppliedPermission; import org.expeditee.items.widgets.InteractiveWidgetInitialisationFailedException; import org.expeditee.items.widgets.InteractiveWidgetNotAvailableException; import org.expeditee.items.widgets.Widget; import org.expeditee.items.widgets.WidgetCorner; import org.expeditee.items.widgets.WidgetEdge; public class ApolloGestureActions implements GestureListener { //@formatter:off public enum ApolloGestureType { ADJUST_INITIATION_TIME, ADJUST_VERTICAL_POSITION, CREATE_LINKED_TRACKS, MODULUS_POSITION, // TODO: Think of a better name. cts16 TOGGLE_QUALITY_GRAPHICS } //@formatter:on private static ApolloGestureActions _instance = null; public static ApolloGestureActions getInstance() { if (_instance == null) { _instance = new ApolloGestureActions(); } return _instance; } private HashMap _gestureTypes; private HashMap _actions; private ApolloGestureActions() { _gestureTypes = new HashMap(); initialiseGestureTypes(); _actions = new HashMap(); initialiseActions(); } @Override public void preGesture(final Gesture gesture) { final GestureAction action = getGestureAction(gesture.getType()); if (action == null) { return; } action.prepare(gesture); } @Override public void onGesture(final Gesture gesture) { final GestureAction action = getGestureAction(gesture.getType()); if (action == null) { return; } action.exec(gesture); } @Override public void postGesture(final Gesture gesture) { final GestureAction action = getGestureAction(gesture.getType()); if (action == null) { return; } action.finalise(gesture); } private void setGestureAction(GestureType type, GestureAction action) { if (type == null) { return; } _actions.put(type, action); } private GestureAction getGestureAction(GestureType type) { if (type == null) { return null; } return _actions.get(type); } private void initialiseActions() { // Set the ADJUST_INITIATION_TIME action setGestureAction(gestureType(ApolloGestureType.ADJUST_INITIATION_TIME), new GestureAction() { @Override public void exec(Gesture gesture) { TrackAdjustmentGestureData data = (TrackAdjustmentGestureData) gesture.getData(); Widget selectedIW = null; if (data.getCurrentItem() != null) { if (data.getCurrentItem() instanceof WidgetEdge) { selectedIW = ((WidgetEdge) data.getCurrentItem()).getWidgetSource(); } else if (data.getCurrentItem() instanceof WidgetCorner) { selectedIW = ((WidgetCorner) data.getCurrentItem()).getWidgetSource(); } } if (selectedIW != null) { adjustInitiationTime(selectedIW, data.getAdjustment()); } } }); // Set the ADJUST_VERTICAL_POSITION action setGestureAction(gestureType(ApolloGestureType.ADJUST_VERTICAL_POSITION), new GestureAction() { @Override public void exec(Gesture gesture) { TrackAdjustmentGestureData data = (TrackAdjustmentGestureData) gesture.getData(); Widget selectedIW = null; if (data.getCurrentItem() != null) { if (data.getCurrentItem() instanceof WidgetEdge) { selectedIW = ((WidgetEdge) data.getCurrentItem()).getWidgetSource(); } else if (data.getCurrentItem() instanceof WidgetCorner) { selectedIW = ((WidgetCorner) data.getCurrentItem()).getWidgetSource(); } } if (selectedIW != null) { adjustVerticalPosition(selectedIW, data.getAdjustment()); } } }); // Set the CREATE_LINKED_TRACKS action setGestureAction(gestureType(ApolloGestureType.CREATE_LINKED_TRACKS), new GestureAction() { @Override public void exec(Gesture gesture) { ItemSpecificGestureData data = (ItemSpecificGestureData) gesture.getData(); createLinkedTracks(data.getCurrentItem(), data.getCurrentItems()); } }); // Set the MOD_POSITION action setGestureAction(gestureType(ApolloGestureType.MODULUS_POSITION), new GestureAction() { @Override public void exec(Gesture gesture) { Frame current = DisplayController.getCurrentFrame(); Dimension windowSize = EcosystemManager.getGraphicsManager().getWindowSize(); for (Item i : current.getSortedItems()) { Point pos = i.getPosition(); pos.setX(pos.getX() % windowSize.width); pos.setY(pos.getY() % windowSize.height); i.setPosition(pos); } } }); // Set the TOGGLE_QUALITY_GRAPHICS action setGestureAction(gestureType(ApolloGestureType.TOGGLE_QUALITY_GRAPHICS), new GestureAction() { @Override public void exec(Gesture gesture) { ApolloSystem.useQualityGraphics = !ApolloSystem.useQualityGraphics; } }); // Set the MOVE_CURSOR action setGestureAction(StandardGestureActions.getInstance().gestureType(StandardGestureType.MOVE_CURSOR), new GestureAction() { @Override public void exec(Gesture gesture) { mouseMoved(gesture.getData().getPosition()); } }); // Set the PLACE action setGestureAction(StandardGestureActions.getInstance().gestureType(StandardGestureType.PLACE), new GestureAction() { final List sampledTracks = new LinkedList(); @Override public void prepare(final Gesture gesture) { final FreeItems freeItems = FreeItems.getInstance(); List widgets = null; if (freeItems != null && !freeItems.isEmpty()) { widgets = ItemUtils.extractWidgets(freeItems); } widgets.forEach(w -> { if (w instanceof SampledTrack) { final SampledTrack st = (SampledTrack) w; st.setIgnoreInjection(true); sampledTracks.add(st); } }); } @Override public void exec(final Gesture gesture) { final PickUpGestureData data = (PickUpGestureData) gesture.getData(); final List widgetPieces = new LinkedList(); if (data.getCopy()) { sampledTracks.forEach(st -> { st.getItems().forEach(i -> { StandardGestureActions.anchor(i); widgetPieces.add(i); }); }); FreeItems.getInstance().clear(); anchorTracks(widgetPieces); final List toPickup = new LinkedList(); sampledTracks.forEach(st -> { try { final Widget copy = st.copy(); toPickup.addAll(copy.getItems()); } catch (InteractiveWidgetNotAvailableException e) { e.printStackTrace(); } catch (InteractiveWidgetInitialisationFailedException e) { e.printStackTrace(); } }); StandardGestureActions.pickup(toPickup); } else { anchorTracks(FreeItems.getInstance()); } } @Override public void finalise(final Gesture gesture) { sampledTracks.forEach(st -> st.setIgnoreInjection(false)); sampledTracks.clear(); } }); } /** Initialises the set of gesture types. */ private void initialiseGestureTypes() { for (ApolloGestureType type : ApolloGestureType.values()) { GestureType gestureType; if ((gestureType = GestureType.register(type.toString())) == null) { gestureType = GestureType.get(type.toString()); } _gestureTypes.put(type, gestureType); } } /** Gets the gesture type associated with the given standard type. */ public GestureType gestureType(ApolloGestureType type) { if (type == null) { return null; } return _gestureTypes.get(type); } /* * TODO: EVERYTHING BELOW HERE IS ONLY TEMPORARILY SITUATED IN THIS CLASS * * The following methods were mostly copy-pasted here from the old * ApolloFrameKeyboardActions and ApolloFrameMouseActions classes (to enable * quick change-over to the new input system). Really they should be relocated * to more suitable homes based on their function. */ public static void adjustInitiationTime(Widget track, long adjustment) { if (track instanceof SampledTrack) { adjustInitiationTime((SampledTrack) track, adjustment); } else if (track instanceof LinkedTrack) { adjustInitiationTime((LinkedTrack) track, adjustment); } } public static void adjustInitiationTime(SampledTrack track, long adjustment) { Mutable.Long oldIt = track.getInitiationTimeFromMeta(); if (oldIt == null) { oldIt = Mutable.createMutableLong(0); } track.setInitiationTime(oldIt.value + adjustment); } public static void adjustInitiationTime(LinkedTrack track, long adjustment) { Mutable.Long oldIt = track.getInitiationTimeFromMeta(); if (oldIt == null) { oldIt = Mutable.createMutableLong(0); } track.setInitiationTime(oldIt.value + adjustment); } public static void adjustVerticalPosition(Widget track, long adjustment) { if (track instanceof SampledTrack) { adjustVerticalPosition((SampledTrack) track, adjustment); } else if (track instanceof LinkedTrack) { adjustVerticalPosition((LinkedTrack) track, adjustment); } } public static void adjustVerticalPosition(SampledTrack track, long adjustment) { track.setYPosition(track.getY() + (int) adjustment); } public static void adjustVerticalPosition(LinkedTrack track, long adjustment) { track.setYPosition(track.getY() + (int) adjustment); } public static void createLinkedTracks(Item currentItem, Collection enclosedItems) { if (currentItem == null) { if (enclosedItems != null) { Frame current = DisplayController.getCurrentFrame(); Collection toRemove = new LinkedHashSet(enclosedItems.size()); LinkedList enclosedLinkedTracks = new LinkedList(); LinkedList enclosedTracks = new LinkedList(); // Get enclosing shape and enclosed tracks for (Item ip : enclosedItems) { if (ip.getParent() != current) { continue; } if (ip.hasPermission(UserAppliedPermission.full)) { // Only include lines if one of their enpoints are also being removed if (ip instanceof Line) { Line l = (Line) ip; Item end = l.getEndItem(); Item start = l.getStartItem(); // If one end of a line is being delted, remove the // other end if all its connecting lines are being // delted if (enclosedItems.contains(end)) { if (!enclosedItems.contains(start) && enclosedItems.containsAll(start.getLines())) { toRemove.add(start); } } else if (enclosedItems.contains(start)) { if (enclosedItems.containsAll(end.getLines())) { toRemove.add(end); } } else { continue; } } else if (ip instanceof WidgetCorner) { Widget iw = ((WidgetCorner) ip).getWidgetSource(); if (!enclosedTracks.contains(iw) && iw instanceof SampledTrack) { enclosedTracks.add((SampledTrack) iw); } else if (!enclosedLinkedTracks.contains(iw) && iw instanceof LinkedTrack) { enclosedLinkedTracks.add((LinkedTrack) iw); } } else { if ((ip instanceof Line || ip instanceof Dot) && ip.getHighlightMode() == HighlightMode.Enclosed) { toRemove.add(ip); toRemove.addAll(ip.getConnected()); } } } } // End searching enclosed items // If there was some enclosed tracks if (current != null && !toRemove.isEmpty() && (!enclosedLinkedTracks.isEmpty() || !enclosedTracks.isEmpty())) { // Remove enclosure current.removeAllItems(toRemove); // Determine initiation time of group Mutable.Long initTime = null; Point initiatePosition = null; for (SampledTrack st : enclosedTracks) { if (initTime == null) { initTime = st.getInitiationTimeFromMeta(); initiatePosition = st.getPosition(); } else if (st.getInitiationTimeFromMeta() != null && st.getInitiationTimeFromMeta().value < initTime.value) { initTime = st.getInitiationTimeFromMeta(); initiatePosition = st.getPosition(); } } for (LinkedTrack lt : enclosedLinkedTracks) { if (initTime == null) { initTime = lt.getInitiationTimeFromMeta(); initiatePosition = lt.getPosition(); } else if (lt.getInitiationTimeFromMeta() != null && lt.getInitiationTimeFromMeta().value < initTime.value) { initTime = lt.getInitiationTimeFromMeta(); initiatePosition = lt.getPosition(); } } assert (initTime != null); assert (initiatePosition != null); // Creat the link to contain all tracks String name = current.getTitle(); if (name == null) { name = "Unnamed"; } name += " group"; Text linkSource = new Text(current.getNextItemID()); linkSource.addToData(TrackWidgetCommons.META_NAME_TAG + name); linkSource.addToData(TrackWidgetCommons.META_INITIATIONTIME_TAG + initTime); linkSource.setPosition(initiatePosition); linkSource.setParent(current); LinkedTrack linkedVersion = new LinkedTrack(linkSource, null); // Create a new frame to hold the track group Frame newFrame = FrameIO.CreateNewFrame(linkedVersion.getItems().get(0)); for (SampledTrack st : enclosedTracks) { st.saveAudio(); // Blocking current.removeAllItems(st.getItems()); newFrame.addAllItems(st.getItems()); } for (LinkedTrack lt : enclosedLinkedTracks) { current.removeAllItems(lt.getItems()); newFrame.addAllItems(lt.getItems()); } FrameIO.SaveFrame(newFrame); // Link the linked tracks linkedVersion.setLink(newFrame.getName(), null); // Add the new link current.addAllItems(linkedVersion.getItems()); // Ensure initiation time is retained to the exact frame... avoiding loss due to // resolution linkedVersion.setInitiationTime(initTime.value); } } } } /** * The minimum distance from a track widget to auto-align free space items to. */ private static final int COARSE_X_PLACEMENT_TOLERANCE = 80; private static final int COARSE_Y_PLACEMENT_TOLERANCE = 20; public void mouseMoved(Point to) { boolean forwareToExpeditee = true; if (isYAxisRestictionOn() && !FreeItems.getInstance().isEmpty()) { // Restrict movement of free items to Y-Axis only forwareToExpeditee = false; int smallestY = FreeItems.getInstance().get(0).getY(); for (Item i : FreeItems.getInstance()) { if (i.getY() < smallestY) { smallestY = i.getY(); } } for (Item i : FreeItems.getInstance()) { i.setY(to.getY() + (i.getY() - smallestY)); } } else if (isSnapOn() && !FreeItems.getInstance().isEmpty()) { // Couse movement of free items: Restraining left-most pixel in items // to the closest anchored track widgets x position. Frame currentFrame = DisplayController.getCurrentFrame(); if (currentFrame != null) { // Search all anchored track x positions for the current frame List widgets = currentFrame.getInteractiveWidgets(); int closestXPosition = -1; int closestYPosition = -1; int closestXDistance = -1; int closestYDistance = -1; for (Widget iw : widgets) { if (iw instanceof SampledTrack || iw instanceof LinkedTrack) { // Determine TOP-LEFT Snapping int xDistance = Math.abs(to.getX() - iw.getX()); if (closestXDistance < 0 || xDistance < closestXDistance) { closestXDistance = xDistance; closestXPosition = iw.getX(); } int yDistance = Math.abs(to.getY() - iw.getY()); if (closestYDistance < 0 || yDistance < closestYDistance) { closestYDistance = yDistance; closestYPosition = iw.getY(); } // Determine BOTTOM-RIGHT Snapping xDistance = Math.abs(to.getX() - (iw.getX() + iw.getWidth())); if (closestXDistance < 0 || xDistance < closestXDistance) { closestXDistance = xDistance; closestXPosition = iw.getX() + iw.getWidth(); } yDistance = Math.abs(to.getY() - (iw.getY() + iw.getHeight())); if (closestYDistance < 0 || yDistance < closestYDistance) { closestYDistance = yDistance; closestYPosition = iw.getY() + iw.getHeight(); } } } // Determine top-left position of free items int smallestX = FreeItems.getInstance().get(0).getX(); int smallestY = FreeItems.getInstance().get(0).getY(); for (Item i : FreeItems.getInstance()) { if (i.getX() < smallestX) { smallestX = i.getX(); } if (i.getY() < smallestY) { smallestY = i.getY(); } } for (Item i : FreeItems.getInstance()) { int x; int y; if (closestXDistance > 0 && closestXDistance < COARSE_X_PLACEMENT_TOLERANCE) { x = closestXPosition + (i.getX() - smallestX); } else { x = to.getX() + (i.getX() - smallestX); } if (closestYDistance > 0 && closestYDistance < COARSE_Y_PLACEMENT_TOLERANCE) { y = closestYPosition + (i.getY() - smallestY); } else { y = to.getY() + (i.getY() - smallestY); } i.setPosition(x, y); } forwareToExpeditee = false; } } // Expeditees frame mouse actions uses an offset and fights over free-item // movement if it listens to the mouse event router... therefore add an extra // layer to avoid this... otherwise auto-aligned items jitter like crazy while // moving the cursus // if (forwareToExpeditee) { // FrameMouseActions.getInstance().mouseMoved(e); // } else { // FrameGraphics.refresh(true); // } } /** * @return True if the user is restricting movement on the y-axis only */ public static boolean isYAxisRestictionOn() { return StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL) && !StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT); } public static boolean isSnapOn() { return StandardInputEventListeners.kbmStateListener.isKeyDown(Key.SHIFT) && !StandardInputEventListeners.kbmStateListener.isKeyDown(Key.CTRL); } private void anchorTracks(final List widgetPieces) { // Widget -> delta IT time Map anchoredLinkedTracks = null; Map anchoredTracks = null; // if (FreeItems.getInstance().size() > 1) { if (widgetPieces.size() > 1) { // List anchoringWidgets = ItemUtils.extractWidgets(FreeItems.getInstance()); List anchoringWidgets = ItemUtils.extractWidgets(widgetPieces); Mutable.Long firstInitTime = null; anchoredLinkedTracks = new HashMap(); anchoredTracks = new HashMap(); for (Widget iw : anchoringWidgets) { Mutable.Long it = null; if (iw instanceof LinkedTrack) { LinkedTrack lt = (LinkedTrack) iw; it = lt.getInitiationTimeFromMeta(); if (it == null) { it = Mutable.createMutableLong(0); } anchoredLinkedTracks.put(lt, it); } else if (iw instanceof SampledTrack) { SampledTrack st = (SampledTrack) iw; it = st.getInitiationTimeFromMeta(); if (it == null) { it = Mutable.createMutableLong(0); } anchoredTracks.put(st, it); } else { continue; } if (firstInitTime == null) { firstInitTime = Mutable.createMutableLong(it.value); // Important to create new instance } else if (it.value < firstInitTime.value) { firstInitTime = Mutable.createMutableLong(it.value); // Important to create new instance } } // Should do a accurate anchor with init times properly aligned? if ((anchoredLinkedTracks.size() + anchoredTracks.size()) > 1) { assert (firstInitTime != null); // Then calc all the deltas for (LinkedTrack lt : anchoredLinkedTracks.keySet()) { Mutable.Long it = anchoredLinkedTracks.get(lt); it.value -= firstInitTime.value; } for (SampledTrack st : anchoredTracks.keySet()) { Mutable.Long it = anchoredTracks.get(st); it.value -= firstInitTime.value; } } else { anchoredLinkedTracks = null; anchoredTracks = null; } } // If anchored a group of tracks .. adjust initiation times to if (anchoredLinkedTracks != null) { Mutable.Long firstInitTime = null; for (LinkedTrack lt : anchoredLinkedTracks.keySet()) { Mutable.Long it = anchoredLinkedTracks.get(lt); if (it.value == 0) { firstInitTime = lt.getInitiationTimeFromMeta(); break; } } if (firstInitTime == null) { for (SampledTrack st : anchoredTracks.keySet()) { Mutable.Long it = anchoredTracks.get(st); if (it.value == 0) { firstInitTime = st.getInitiationTimeFromMeta(); break; } } } assert (firstInitTime != null); for (LinkedTrack lt : anchoredLinkedTracks.keySet()) { Mutable.Long it = anchoredLinkedTracks.get(lt); if (it.value == 0) { continue; } lt.setInitiationTime(firstInitTime.value + it.value); } for (SampledTrack st : anchoredTracks.keySet()) { Mutable.Long it = anchoredTracks.get(st); if (it.value == 0) { continue; } st.setInitiationTime(firstInitTime.value + it.value); } } } }