/// <summary> /// Moves the objects represented by the current ghosts to be at the position of /// the ghosts</summary> protected virtual void MoveSelection() { // To avoid setting the dirty bit in custom applications. // http://sf.ship.scea.com/sf/go/artf22506 PointF dragOffset = m_owner.GetDragOffset(); if ((dragOffset.X == 0.0f) && (dragOffset.Y == 0.0f)) { return; } // If we're dragging up, then don't create new tracks if (dragOffset.Y < 0) { foreach (GhostInfo ghost in m_ghosts) { if (!ghost.Valid) { return; } } } // If Control key is being held down, then we're in copy mode. bool tryToCopy = (Control.ModifierKeys == Keys.Control); m_owner.TransactionContext.DoTransaction(delegate { ITimeline timeline = m_owner.TimelineDocument.Timeline; Dictionary <ITrack, ITrack> newTrackMap = new Dictionary <ITrack, ITrack>(); List <Sce.Atf.Pair <ITrack, IEvent> > toAdd = new List <Sce.Atf.Pair <ITrack, IEvent> >(); for (int i = 0; i < m_ghosts.Length; i++) { GhostInfo ghost = m_ghosts[i]; ITimelineObject ghostCopy = null; if (tryToCopy) { ICloneable cloneable = ghost.Object as ICloneable; if (cloneable != null) { ghostCopy = cloneable.Clone() as ITimelineObject; } } ITimelineReference reference = ghost.Object as ITimelineReference; if (reference != null) { if (ghostCopy != null) { reference = (ITimelineReference)ghostCopy; } reference.Start = ghost.Start; if (ghostCopy != null && timeline is IHierarchicalTimelineList) { ((IHierarchicalTimelineList)timeline).References.Add(reference); } continue; } IInterval interval = ghost.Object as IInterval; if (interval != null) { if (ghostCopy != null) { interval = (IInterval)ghostCopy; } interval.Start = ghost.Start; interval.Length = ghost.Length; ITrack target = (ITrack)ghost.Target; if (target != interval.Track) { if (target == null) { target = CreateTargetTrack(interval.Track, newTrackMap); } if (ghostCopy == null) { interval.Track.Intervals.Remove(interval); } toAdd.Add(new Sce.Atf.Pair <ITrack, IEvent>(target, interval)); } continue; } IKey key = ghost.Object as IKey; if (key != null) { if (ghostCopy != null) { key = (IKey)ghostCopy; } key.Start = ghost.Start; ITrack target = (ITrack)ghost.Target; if (target != key.Track) { if (target == null) { target = CreateTargetTrack(key.Track, newTrackMap); } if (ghostCopy == null) { key.Track.Keys.Remove(key); } toAdd.Add(new Sce.Atf.Pair <ITrack, IEvent>(target, key)); } continue; } IMarker marker = ghost.Object as IMarker; if (marker != null) { if (ghost.Valid && marker.Start != ghost.Start) { if (ghostCopy != null) { IMarker markerCopy = (IMarker)ghostCopy; markerCopy.Start = ghost.Start; marker.Timeline.Markers.Add(markerCopy); } else { marker.Start = ghost.Start; } } continue; } ITrack track = ghost.Object as ITrack; if (track != null) { ITrack target = (ITrack)ghost.Target; if (target != null && target != track) { if (ghostCopy != null) { track = (ITrack)ghostCopy; } int index = target.Group.Tracks.IndexOf(target); if (ghostCopy == null) { track.Group.Tracks.Remove(track); } target.Group.Tracks.Insert(index, track); } continue; } IGroup group = ghost.Object as IGroup; if (group != null) { IGroup target = (IGroup)ghost.Target; if (target != null && target != group) { if (ghostCopy != null) { group = (IGroup)ghostCopy; } int index = m_owner.TimelineDocument.Timeline.Groups.IndexOf(target); if (ghostCopy == null) { m_owner.TimelineDocument.Timeline.Groups.Remove(group); } m_owner.TimelineDocument.Timeline.Groups.Insert(index, group); } continue; } } // So that when multiple intervals from multiple tracks are relocated to a different // set of tracks, we need to remove them all and then add them all. If the remove and // adds are done in pairs, one by one, then the events can step on each other. artf32260 foreach (Sce.Atf.Pair <ITrack, IEvent> trackEventPair in toAdd) { if (trackEventPair.Second is IInterval) { trackEventPair.First.Intervals.Add((IInterval)trackEventPair.Second); } else { trackEventPair.First.Keys.Add((IKey)trackEventPair.Second); } } }, "Move Events".Localize("Move Manipulator's undo / redo description for moving timeline events")); }
// gets move information, for drawing ghosts and for performing actual move operation private GhostInfo[] GetMoveGhostInfo(Matrix worldToView, TimelineLayout layout) { // get start and y offsets in timeline space PointF dragOffset = m_owner.GetDragOffset(); // Get snapping points along the timeline (in world coordinates). List<float> movingPoints = new List<float>(2); TimelinePath snapperPath; if (m_mouseMoveHitRecord != null) snapperPath = m_mouseMoveHitRecord.HitPath;//use the last clicked event (interval, key or marker) else snapperPath = m_owner.Selection.LastSelected as TimelinePath;//moving a group or track, for example IEvent snapperEvent = snapperPath != null ? snapperPath.Last as IEvent : null; if (snapperEvent != null) { Matrix localToWorld = TimelineControl.CalculateLocalToWorld(snapperPath); float worldStart = GdiUtil.Transform(localToWorld, snapperEvent.Start + dragOffset.X); movingPoints.Add(worldStart); if (snapperEvent.Length > 0.0f) movingPoints.Add(GdiUtil.Transform(localToWorld, snapperEvent.Start + dragOffset.X + snapperEvent.Length)); } // Get the offset from one of the world snap points to the closest non-selected object. float snapOffset; try { s_snapOptions.FilterContext = snapperEvent; s_snapOptions.Filter = new TimelineControl.SnapFilter(MoveSnapFilter); snapOffset = m_owner.GetSnapOffset(movingPoints, s_snapOptions); } finally { s_snapOptions.FilterContext = null; s_snapOptions.Filter = null; } // adjust dragOffset to "snap-to" nearest event dragOffset.X += snapOffset; // get offsets in client space float xOffset = dragOffset.X * worldToView.Elements[0]; float yOffset = dragOffset.Y * worldToView.Elements[3]; TimelinePath[] targets = GetMoveTargets(layout); GhostInfo[] ghosts = new GhostInfo[targets.Length]; int i = -1; foreach(TimelinePath path in m_owner.Selection.Selection) { i++; ITimelineObject timelineObject = path.Last; RectangleF bounds = layout[path]; TimelinePath targetPath = targets[i]; ITimelineObject target = targetPath != null ? targetPath.Last : null; float start = 0; float length = 0; bool valid = true; IInterval interval = timelineObject as IInterval; IKey key = timelineObject as IKey; IMarker marker = timelineObject as IMarker; ITrack track = timelineObject as ITrack; IGroup group = timelineObject as IGroup; ITimelineReference reference = timelineObject as ITimelineReference; if (interval != null) { ITrack targetTrack = target as ITrack; start = interval.Start + dragOffset.X; length = interval.Length; valid = targetTrack != null && m_owner.Constraints.IsStartValid(interval, ref start) && m_owner.Constraints.IsLengthValid(interval, ref length); if (valid) { yOffset = layout[target].Y - layout[interval.Track].Y; TimelinePath testPath = new TimelinePath(targetPath); foreach (IInterval other in targetTrack.Intervals) { // skip selected intervals, since they are moving too testPath.Last = other; if (m_owner.Selection.SelectionContains(testPath)) continue; if (!m_owner.Constraints.IsIntervalValid(interval, ref start, ref length, other)) { valid = false; break; } } } } else if (reference != null) { // don't allow for vertical repositioning yet start = reference.Start + dragOffset.X; valid = true; } else if (key != null) { start = key.Start + dragOffset.X; ITrack targetTrack = target as ITrack; valid = targetTrack != null && m_owner.Constraints.IsStartValid(key, ref start); if (valid) { yOffset = layout[targetTrack].Y - layout[key.Track].Y; } } else if (marker != null) { start = marker.Start + dragOffset.X; yOffset = 0; valid = m_owner.Constraints.IsStartValid(marker, ref start); } else if (track != null) { xOffset = 0; if (target == null) { target = (m_owner.DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack(); } } else if (group != null) { xOffset = 0; if (target == null) { IList<IGroup> groups = m_owner.TimelineDocument.Timeline.Groups; target = (m_owner.DragDelta.Y < 0) ? groups[0] : groups[groups.Count - 1]; } } bounds.Offset(xOffset, yOffset); ghosts[i] = new GhostInfo(timelineObject, target, start, length, bounds, valid); } return ghosts; }
// gets move information, for drawing ghosts and for performing actual move operation private GhostInfo[] GetMoveGhostInfo(Matrix worldToView, TimelineLayout layout) { // get start and y offsets in timeline space PointF dragOffset = m_owner.GetDragOffset(); // Get snapping points along the timeline (in world coordinates). List <float> movingPoints = new List <float>(2); TimelinePath snapperPath; if (m_mouseMoveHitRecord != null) { snapperPath = m_mouseMoveHitRecord.HitPath;//use the last clicked event (interval, key or marker) } else { snapperPath = m_owner.Selection.LastSelected as TimelinePath;//moving a group or track, for example } IEvent snapperEvent = snapperPath != null ? snapperPath.Last as IEvent : null; if (snapperEvent != null) { Matrix localToWorld = TimelineControl.CalculateLocalToWorld(snapperPath); float worldStart = GdiUtil.Transform(localToWorld, snapperEvent.Start + dragOffset.X); movingPoints.Add(worldStart); if (snapperEvent.Length > 0.0f) { movingPoints.Add(GdiUtil.Transform(localToWorld, snapperEvent.Start + dragOffset.X + snapperEvent.Length)); } } // Get the offset from one of the world snap points to the closest non-selected object. float snapOffset; try { s_snapOptions.FilterContext = snapperEvent; s_snapOptions.Filter = new TimelineControl.SnapFilter(MoveSnapFilter); snapOffset = m_owner.GetSnapOffset(movingPoints, s_snapOptions); } finally { s_snapOptions.FilterContext = null; s_snapOptions.Filter = null; } // adjust dragOffset to "snap-to" nearest event dragOffset.X += snapOffset; // get offsets in client space float xOffset = dragOffset.X * worldToView.Elements[0]; float yOffset = dragOffset.Y * worldToView.Elements[3]; TimelinePath[] targets = GetMoveTargets(layout); GhostInfo[] ghosts = new GhostInfo[targets.Length]; int i = -1; foreach (TimelinePath path in m_owner.Selection.Selection) { i++; ITimelineObject timelineObject = path.Last; RectangleF bounds = layout[path]; TimelinePath targetPath = targets[i]; ITimelineObject target = targetPath != null ? targetPath.Last : null; float start = 0; float length = 0; bool valid = true; IInterval interval = timelineObject as IInterval; IKey key = timelineObject as IKey; IMarker marker = timelineObject as IMarker; ITrack track = timelineObject as ITrack; IGroup group = timelineObject as IGroup; ITimelineReference reference = timelineObject as ITimelineReference; if (interval != null) { ITrack targetTrack = target as ITrack; start = interval.Start + dragOffset.X; length = interval.Length; valid = targetTrack != null && m_owner.Constraints.IsStartValid(interval, ref start) && m_owner.Constraints.IsLengthValid(interval, ref length); if (valid) { yOffset = layout[target].Y - layout[interval.Track].Y; TimelinePath testPath = new TimelinePath(targetPath); foreach (IInterval other in targetTrack.Intervals) { // skip selected intervals, since they are moving too testPath.Last = other; if (m_owner.Selection.SelectionContains(testPath)) { continue; } if (!m_owner.Constraints.IsIntervalValid(interval, ref start, ref length, other)) { valid = false; break; } } } } else if (reference != null) { // don't allow for vertical repositioning yet start = reference.Start + dragOffset.X; valid = true; } else if (key != null) { start = key.Start + dragOffset.X; ITrack targetTrack = target as ITrack; valid = targetTrack != null && m_owner.Constraints.IsStartValid(key, ref start); if (valid) { yOffset = layout[targetTrack].Y - layout[key.Track].Y; } } else if (marker != null) { start = marker.Start + dragOffset.X; yOffset = 0; valid = m_owner.Constraints.IsStartValid(marker, ref start); } else if (track != null) { xOffset = 0; if (target == null) { target = (m_owner.DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack(); } } else if (group != null) { xOffset = 0; if (target == null) { IList <IGroup> groups = m_owner.TimelineDocument.Timeline.Groups; target = (m_owner.DragDelta.Y < 0) ? groups[0] : groups[groups.Count - 1]; } } bounds.Offset(xOffset, yOffset); ghosts[i] = new GhostInfo(timelineObject, target, start, length, bounds, valid); } return(ghosts); }
/// <summary> /// Creates and caches resize information, for drawing ghosts and for performing /// actual resize operations</summary> /// <param name="layout">TimelineLayout</param> /// <param name="worldDrag">Drag offset</param> /// <param name="worldToView">World to view transformation matrix</param> /// <returns>Array of GhostInfo</returns> internal GhostInfo[] CreateGhostInfo(TimelineLayout layout, float worldDrag, Matrix worldToView) { // Get snap-from point in world coordinates. float[] movingPoints = new[] { worldDrag + m_originalBoundary }; float snapOffset = m_owner.GetSnapOffset(movingPoints, null); // adjust dragOffset to snap-to nearest event worldDrag += snapOffset; DragOffsetWithSnap = worldDrag; GhostInfo[] ghosts = new GhostInfo[m_selection.Count]; IEnumerator <TimelinePath> events = m_selection.GetEnumerator(); m_worldGhostMin = float.MaxValue; m_worldGhostMax = float.MinValue; for (int i = 0; i < ghosts.Length; i++) { events.MoveNext(); IEvent curr = (IEvent)events.Current.Last; RectangleF bounds = layout[events.Current]; float viewStart = bounds.Left; float viewEnd = viewStart + bounds.Width; float worldStart = curr.Start; float worldEnd = worldStart + curr.Length; Resize( worldDrag, worldToView, ref viewStart, ref viewEnd, ref worldStart, ref worldEnd); float worldLength = worldEnd - worldStart; bounds = new RectangleF(viewStart, bounds.Y, viewEnd - viewStart, bounds.Height); if (m_worldGhostMin > worldStart) { m_worldGhostMin = worldStart; } if (m_worldGhostMax < worldEnd) { m_worldGhostMax = worldEnd; } bool valid = true; IInterval interval = curr as IInterval; if (interval != null) { TimelinePath testPath = new TimelinePath(events.Current); foreach (IInterval other in interval.Track.Intervals) { // Skip this interval if it's part of the selection because we have to assume // that the track began in a valid state and that all of the selected objects // will still be valid relative to each other. We only need to check that the // scaled objects are valid relative to the stationary objects. testPath.Last = other; if (Mode == ScaleMode.TimePeriod && m_selection.Contains(testPath)) { continue; } else if (other == interval) { continue; } if (!m_owner.Constraints.IsIntervalValid(interval, ref worldStart, ref worldLength, other)) { valid = false; break; } } } ghosts[i] = new GhostInfo(curr, null, worldStart, worldLength, bounds, valid); } m_ghosts = ghosts; return(ghosts); }