package org.apollo; import java.util.ArrayList; 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.core.bounds.AxisAlignedBoxBounds; 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()); if (isSnapOn()) { snapFreeTracks(gesture.getData().getPosition()); } } }); // Set the PLACE action setGestureAction(StandardGestureActions.getInstance().gestureType(StandardGestureType.PLACE), new GestureAction() { final List sampledTracksOnFreeItems = new ArrayList(); @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); sampledTracksOnFreeItems.add(st); } }); } @Override public void exec(final Gesture gesture) { final PickUpGestureData data = (PickUpGestureData) gesture.getData(); final List widgetPieces = new LinkedList(); if (data.getCopy()) { sampledTracksOnFreeItems.forEach(st -> { st.getItems().forEach(i -> { StandardGestureActions.anchor(i); widgetPieces.add(i); }); }); //FreeItems.getInstance().clear(); FreeItems.getInstance().removeAll(widgetPieces); anchorTracks(widgetPieces); final List toPickup = new LinkedList(); sampledTracksOnFreeItems.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) { sampledTracksOnFreeItems.forEach(st -> st.setIgnoreInjection(false)); sampledTracksOnFreeItems.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; private void snapFreeTracks(Point position) { // Get Apollo widgets that are attached to cursor FreeItems freeItems = FreeItems.getInstance(); List freeWidgets = ItemUtils.extractWidgets(freeItems); List freeApolloWidgets = new ArrayList(); for (Widget w: freeWidgets) { if (w instanceof SampledTrack || w instanceof LinkedTrack) { freeApolloWidgets.add(w); } } if (freeApolloWidgets.isEmpty()) { return; } // Get Apollo widgets that are on frame Frame currentFrame = DisplayController.getCurrentFrame(); List interactiveWidgets = currentFrame.getInteractiveWidgets(); List anchoredApolloWidgets = new ArrayList(); for (Widget w: interactiveWidgets) { if (w instanceof SampledTrack || w instanceof LinkedTrack) { anchoredApolloWidgets.add(w); } } if (anchoredApolloWidgets.isEmpty()) { return; } // Snap the first freeApolloWidget. // Snap the freeApolloWidgets, using the first as a guidepost. Widget toSnap = freeApolloWidgets.remove(0); Point origin = new Point(0,0); Point canditateDistance = new Point(Integer.MAX_VALUE, Integer.MAX_VALUE); Widget anchoredWidgetToSnapTo = null; AxisAlignedBoxBounds freeWidgetBounds = toSnap.getBounds(); for (Widget anchoredWidget: anchoredApolloWidgets) { AxisAlignedBoxBounds anchoredWidgetBounds = anchoredWidget.getBounds(); Point distance = freeWidgetBounds.distanceFrom(anchoredWidgetBounds); if (distance.getDistanceTo(origin) < canditateDistance.getDistanceTo(origin)) { canditateDistance = distance; anchoredWidgetToSnapTo = anchoredWidget; } } AxisAlignedBoxBounds anchoredWidgetToSnapToBounds = anchoredWidgetToSnapTo.getBounds(); int xDist = anchoredWidgetToSnapToBounds.getCentreX() < freeWidgetBounds.getCentreX() ? canditateDistance.getX() : -canditateDistance.getX(); int yDist = anchoredWidgetToSnapToBounds.getCentreY() < freeWidgetBounds.getCentreY() ? canditateDistance.getY() : -canditateDistance.getY(); int direction = -1; int tlX = toSnap.getX(); int tlY = toSnap.getY(); if ((xDist > 0 && yDist == 0) || (xDist == 0 && yDist == 0)) { tlX = anchoredWidgetToSnapTo.getBounds().getMaxX(); tlY = anchoredWidgetToSnapTo.getBounds().getMinY(); direction = 0; } else if (xDist < 0 && yDist == 0) { tlX = anchoredWidgetToSnapTo.getBounds().getMinX() - toSnap.getWidth(); tlY = anchoredWidgetToSnapTo.getBounds().getMinY(); direction = 1; } else if (yDist > 0 && xDist == 0) { tlX = anchoredWidgetToSnapTo.getBounds().getMinX(); tlY = anchoredWidgetToSnapTo.getBounds().getMaxY(); direction = 2; } else if (yDist < 0 && xDist == 0) { tlX = anchoredWidgetToSnapTo.getBounds().getMinX(); tlY = anchoredWidgetToSnapTo.getBounds().getMinY() - toSnap.getHeight(); direction = 3; } toSnap.setPosition(tlX, tlY); for (Widget toSnapNext: freeApolloWidgets) { switch (direction) { case 0: tlX = toSnap.getBounds().getMaxX(); tlY = toSnap.getBounds().getMinY(); toSnapNext.setPosition(tlX, tlY); break; case 1: tlX = toSnap.getBounds().getMinX() - toSnapNext.getWidth(); tlY = toSnap.getBounds().getMinY(); toSnapNext.setPosition(tlX, tlY); break; case 2: tlX = toSnap.getBounds().getMinX(); tlY = toSnap.getBounds().getMaxX(); toSnapNext.setPosition(tlX, tlY); break; case 3: tlX = toSnap.getBounds().getMinX(); tlY = toSnap.getBounds().getMinY() - toSnapNext.getHeight(); toSnapNext.setPosition(tlX, tlY); break; default: break; } toSnap = toSnapNext; } } /** * @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); } } } }