Example #1
0
 /// <summary>
 /// Constructor for a HitRecord representing a hit on an ITimelineObject. If this
 /// ITimelineObject is in the main document, then the path will have just one element
 /// in it. Otherwise, the elements of the path should be ITimelineReference objects
 /// plus some other ITimelineObject (like IInterval, for example) as the last element.</summary>
 /// <param name="type">Hit type</param>
 /// <param name="path">Full path of the hit timeline object</param>
 public HitRecord(HitType type, TimelinePath path)
 {
     Type              = type;
     HitPath           = path;
     HitTimelineObject = path != null ? path.Last : null;
     HitObject         = HitTimelineObject;
 }
        private void owner_MouseMovePicked(object sender, HitEventArgs e)
        {
            if (e.MouseEvent.Button == MouseButtons.None)
            {
                HitRecord    hitRecord = e.HitRecord;
                TimelinePath hitPath   = hitRecord.HitPath;

                switch (hitRecord.Type)
                {
                case HitType.IntervalResizeLeft:
                case HitType.IntervalResizeRight:
                    if (Owner.IsEditable(hitPath))
                    {
                        Owner.Cursor = Cursors.SizeWE;
                    }
                    break;

                //one of the scale manipulator's handles?
                case HitType.Custom:
                    if (hitRecord.HitObject == m_leftHitObject ||
                        hitRecord.HitObject == m_rightHitObject)
                    {
                        Owner.Cursor = Cursors.SizeWE;
                    }
                    break;

                default:
                    break;
                }
            }
        }
Example #3
0
        // private because this may be moved to TimelineControl
        private bool Overlaps(TimelinePath path, float beginTime, float endTime)
        {
            IEvent e = (IEvent)path.Last;

            float start, length;

            using (Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path))
            {
                start  = GdiUtil.Transform(localToWorld, e.Start);
                length = GdiUtil.TransformVector(localToWorld, e.Length);
            }

            // If the length is zero, then count an exact match with beginTime or endTime as
            //  being an overlap.
            if (length == 0)
            {
                return(!(
                           start > endTime ||
                           start + length < beginTime));
            }

            // Otherwise, don't count an exact match.
            return(!(
                       start >= endTime ||
                       start + length <= beginTime));
        }
        private void owner_MouseDownPicked(object sender, HitEventArgs e)
        {
            if (e.MouseEvent.Button != MouseButtons.Left)
            {
                return;
            }

            TimelinePath hitPath = e.HitRecord.HitPath;

            bool isResizing = false;

            switch (e.HitRecord.Type)
            {
            case HitType.IntervalResizeLeft:
                if (Owner.IsEditable(hitPath) && (
                        Owner.Selection.SelectionContains(hitPath) ||
                        Owner.Select <IEvent>(hitPath)))
                {
                    IInterval hitInterval = e.HitRecord.HitTimelineObject as IInterval;
                    m_resizer  = new Resizer(Side.Left, ScaleMode.InPlace, hitInterval.Start, Owner);
                    isResizing = true;
                }
                break;

            case HitType.IntervalResizeRight:
                if (Owner.IsEditable(hitPath) && (
                        Owner.Selection.SelectionContains(hitPath) ||
                        Owner.Select <IEvent>(hitPath)))
                {
                    IInterval hitInterval = e.HitRecord.HitTimelineObject as IInterval;
                    m_resizer  = new Resizer(Side.Right, ScaleMode.InPlace, hitInterval.Start + hitInterval.Length, Owner);
                    isResizing = true;
                }
                break;

            case HitType.Custom:
            {
                HitRecordObject hitManipulator = e.HitRecord.HitObject as HitRecordObject;
                if (hitManipulator != null)
                {
                    if (hitManipulator.Side == Side.Left)
                    {
                        m_resizer = new Resizer(Side.Left, ScaleMode.TimePeriod, m_worldMin, Owner);
                    }
                    else
                    {
                        m_resizer = new Resizer(Side.Right, ScaleMode.TimePeriod, m_worldMax, Owner);
                    }
                    isResizing = true;
                }
            }
            break;

            default:
                m_resizer = null;
                break;
            }

            Owner.IsResizingSelection = isResizing; //legacy. obsolete.
        }
Example #5
0
 /// <summary>
 /// Constructor for a HitRecord representing a hit on an ITimelineObject. If this
 /// ITimelineObject is in the main document, then the path will have just one element
 /// in it. Otherwise, the elements of the path should be ITimelineReference objects
 /// plus some other ITimelineObject (like IInterval, for example) as the last element.</summary>
 /// <param name="type">Hit type</param>
 /// <param name="path">Full path of the hit timeline object</param>
 public HitRecord(HitType type, TimelinePath path)
 {
     Type = type;
     HitPath = path;
     HitTimelineObject = path != null ? path.Last : null;
     HitObject = HitTimelineObject;
 }
Example #6
0
        /// <summary>
        /// Gets the last group as determined by the associated ITimeline</summary>
        /// <returns>Last group as TimelinePath</returns>
        protected TimelinePath GetLastGroup()
        {
            TimelinePath last = null;

            foreach (TimelinePath group in Owner.AllGroups)
            {
                last = group;
            }
            return(last);
        }
Example #7
0
        /// <summary>
        /// Gets the last track as determined by the associated ITimeline</summary>
        /// <returns>Last track as TimelinePath</returns>
        protected TimelinePath GetLastTrack()
        {
            TimelinePath last = null;

            foreach (TimelinePath track in Owner.AllTracks)
            {
                last = track;
            }
            return(last);
        }
Example #8
0
        private bool IsEditable(ITimelineObject item)
        {
            var path = new TimelinePath(item);
            TimelineDocument document = (TimelineDocument)TimelineEditor.TimelineDocumentRegistry.ActiveDocument;

            if (document != null)
            {
                return(document.TimelineControl.IsEditable(path));
            }
            return(false);
        }
Example #9
0
        /// <summary>
        /// Checks whether the given timeline object's attribute is editable for the current
        /// context and document</summary>
        /// <param name="item">Timeline object that changed</param>
        /// <param name="attribute">Attribute on the timeline object that changed</param>
        /// <returns>True iff this timeline object attribute is editable for the current
        /// ActiveControl, ActiveContext, and ActiveDocument properties</returns>
        public virtual bool IsEditable(ITimelineObject item, AttributeInfo attribute)
        {
            if (attribute == Schema.groupType.expandedAttribute)
            {
                return(true);
            }

            TimelinePath path = new TimelinePath(item);

            return(ActiveControl.IsEditable(path));
        }
        private void owner_MouseMovePicked(object sender, HitEventArgs e)
        {
            string toolTipText = null;

            if (m_active)
            {
                e.Handled = true;

                if (e.HitRecord.Type == HitType.Interval &&
                    e.MouseEvent.Button == MouseButtons.None &&
                    !m_owner.IsUsingMouse &&
                    m_owner.IsEditable(e.HitRecord.HitPath))
                {
                    SetCursor();
                    TimelinePath hitPath   = e.HitRecord.HitPath;
                    IInterval    hitObject = (IInterval)e.HitRecord.HitTimelineObject;
                    float        worldX    = GdiUtil.InverseTransform(m_owner.Transform, e.MouseEvent.Location.X);

                    //Make sure the snap-to indicator line is drawn.
                    float delta = m_owner.GetSnapOffset(new[] { worldX }, s_snapOptions);

                    worldX += delta;
                    worldX  = m_owner.ConstrainFrameOffset(worldX);

                    using (Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(hitPath))
                    {
                        if (worldX <= GdiUtil.Transform(localToWorld, hitObject.Start) ||
                            worldX >= GdiUtil.Transform(localToWorld, hitObject.Start + hitObject.Length))
                        {
                            // Clear the results since a split is impossible.
                            m_owner.GetSnapOffset(new float[] {}, s_snapOptions);
                        }
                        else
                        {
                            toolTipText = worldX.ToString();
                        }
                    }
                }
            }

            if (toolTipText != null)
            {
                m_toolTip.Show(toolTipText, m_owner, e.MouseEvent.Location);
            }
            else
            {
                m_toolTip.Hide(m_owner);
            }
        }
Example #11
0
        /// <summary>
        /// Adds range of tracks to selection, from anchor to given target.
        /// Handles selecting a track, taking into account selecting ranges of tracks.</summary>
        /// <param name="target">Track as TimelinePath</param>
        protected virtual void SelectTracks(TimelinePath target)
        {
            // add range of tracks, from anchor to target?
            if ((Control.ModifierKeys & Keys.Shift) != Keys.None)
            {
                if (Anchor != null && Anchor.Last is ITrack && SelectionContext != null)
                {
                    SelectionContext.SetRange(GetRangeOfTracks(Anchor, target));
                    return;
                }
            }

            // simply add this target track, using the current modifier keys to determine how
            Owner.Select <ITrack>(target);
        }
Example #12
0
        /// <summary>
        /// Adds range of groups to selection, from anchor to given target.
        /// Handles selecting a group, taking into account selecting ranges of groups.
        /// If no anchor, just selects target.</summary>
        /// <param name="target">Timeline path</param>
        protected virtual void SelectGroups(TimelinePath target)
        {
            // Add range of groups, from anchor to target? Holding down Ctrl or Alt simultaneously
            //  still does a range selection in Visual Studio's Solution Explorer.
            if ((Control.ModifierKeys & Keys.Shift) != Keys.None)
            {
                if (Anchor != null && Anchor.Last is IGroup && SelectionContext != null)
                {
                    SelectionContext.SetRange(GetRangeOfGroups(Anchor, target));
                    return;
                }
            }

            // simply add this target group, using the current modifier keys to determine how
            Owner.Select <IGroup>(target);
        }
Example #13
0
        /// <summary>
        /// Event handler for the timeline control.MouseDownPicked event</summary>
        /// <param name="sender">Timeline control that we are attached to</param>
        /// <param name="e">Event args</param>
        protected virtual void Owner_MouseDownPicked(object sender, HitEventArgs e)
        {
            HitRecord    hitRecord = e.HitRecord;
            TimelinePath hitObject = hitRecord.HitPath;

            if (hitObject != null)
            {
                Keys modifiers = Control.ModifierKeys;

                if (e.MouseEvent.Button == MouseButtons.Left)
                {
                    if (modifiers == Keys.None && SelectionContext != null &&
                        SelectionContext.SelectionContains(hitObject))
                    {
                        // The hit object is already selected. Wait until the mouse up to see if there was
                        //  a drag or not. If no drag, then set the selection set to be this one object.
                        m_mouseDownHitRecord = hitRecord;
                        m_mouseDownPos       = e.MouseEvent.Location;
                    }
                    else if (modifiers == Keys.Control)
                    {
                        // Either this object is not already selected or Shift key is being held down.
                        //  to be consistent with the Windows interface.
                        m_mouseDownHitRecord = hitRecord;
                        m_mouseDownPos       = e.MouseEvent.Location;
                    }
                    else if ((modifiers & Keys.Alt) == 0)
                    {
                        // The 'Alt' key might mean something different. If no Alt key, we can update the
                        //  selection immediately.
                        UpdateSelection(hitRecord, modifiers);
                    }
                }
                else if (e.MouseEvent.Button == MouseButtons.Right)
                {
                    if (modifiers == Keys.None && SelectionContext != null &&
                        !SelectionContext.SelectionContains(hitObject))
                    {
                        // The hit object is not already selected and a right-click landed on it. Select it.
                        UpdateSelection(hitRecord, modifiers);
                    }
                }
            }
        }
Example #14
0
        /// <summary>
        /// Updates the selection set, given the hit object and the modifier keys</summary>
        /// <param name="hitRecord">HitRecord</param>
        /// <param name="modifiers">Modifier keys</param>
        private void UpdateSelection(HitRecord hitRecord, Keys modifiers)
        {
            TimelinePath hitObject        = hitRecord.HitPath;
            bool         hitIsValidAnchor = true;

            switch (hitRecord.Type)
            {
            case HitType.GroupMove:
                SelectGroups(hitObject);
                break;

            case HitType.TrackMove:
                SelectTracks(hitObject);
                break;

            case HitType.Interval:
            case HitType.Key:
            case HitType.Marker:
                SelectEvents(hitObject);
                Owner.Constrain = (modifiers & Owner.ConstrainModifierKeys) != 0;
                break;

            default:
                Anchor           = null;
                hitIsValidAnchor = false;
                break;
            }

            if (hitIsValidAnchor)
            {
                // If the Shift key is not held down or the current Anchor is null, or the user
                //  has switched between track and group, or the user has picked an event, then
                //  update the Anchor. IEvents are always additive with the shift key.
                if ((modifiers & Keys.Shift) == 0 ||
                    Anchor == null ||
                    (Anchor.Last is IGroup && hitObject.Last is ITrack) ||
                    (Anchor.Last is ITrack && hitObject.Last is IGroup) ||
                    (Anchor.Last is IEvent && hitObject.Last is IEvent))
                {
                    Anchor = hitObject;
                }
            }
        }
Example #15
0
        // get track that contains y value (in client window coordinates, aka, screen space)
        private TimelinePath GetTargetTrack(
            float y,
            TimelineLayout layout,
            IList <ITrack> tracks)
        {
            TimelinePath testPath = new TimelinePath((ITimelineObject)null);

            foreach (ITrack track in tracks)
            {
                testPath.Last = track;
                RectangleF targetBounds = layout[testPath];
                if (targetBounds.Top <= y && targetBounds.Bottom >= y)
                {
                    return(testPath);
                }
            }

            return(null);
        }
Example #16
0
 // private because this may be moved to TimelineControl
 private TimelinePath GetOwningTrack(TimelinePath begin)
 {
     if (begin.Last is IKey)
     {
         TimelinePath trackPath = new TimelinePath(begin);
         trackPath.Last = ((IKey)begin.Last).Track;
         return(trackPath);
     }
     if (begin.Last is IInterval)
     {
         TimelinePath trackPath = new TimelinePath(begin);
         trackPath.Last = ((IInterval)begin.Last).Track;
         return(trackPath);
     }
     if (begin.Last is ITrack)
     {
         return(begin);
     }
     return(null);
 }
        private void owner_MouseDownPicked(object sender, HitEventArgs e)
        {
            TimelinePath hitPath     = e.HitRecord.HitPath;
            IInterval    hitInterval = e.HitRecord.HitTimelineObject as IInterval;

            if (m_active &&
                e.MouseEvent.Button == MouseButtons.Left &&
                !m_owner.IsUsingMouse &&
                m_owner.IsEditable(hitPath))
            {
                if (hitInterval != null &&
                    e.HitRecord.Type == HitType.Interval)
                {
                    PointF mouseLocation = e.MouseEvent.Location;
                    float  worldX        =
                        Sce.Atf.GdiUtil.InverseTransform(m_owner.Transform, mouseLocation.X);

                    worldX += m_owner.GetSnapOffset(new[] { worldX }, s_snapOptions);

                    float fraction;
                    using (Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(hitPath))
                    {
                        fraction =
                            (worldX - GdiUtil.Transform(localToWorld, hitInterval.Start)) /
                            GdiUtil.TransformVector(localToWorld, hitInterval.Length);
                    }

                    if (m_owner.Selection.SelectionContains(hitInterval))
                    {
                        SplitSelectedIntervals(fraction);
                    }
                    else
                    {
                        SplitUnselectedInterval(hitInterval, fraction);
                    }
                }
                Active    = false;
                e.Handled = true; //don't let subsequent listeners get this event
            }
        }
Example #18
0
        /// <summary>
        /// Gets the range of groups from 'begin' to 'end', inclusive, as determined by the order
        /// that they are presented by the ITimelineDocument. The result always has 'begin'
        /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same,
        /// then the result has only the one ITimelineObject.</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths in the given range.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable <TimelinePath> GetRangeOfGroups(TimelinePath begin, TimelinePath end)
        {
            TimelinePath        lastGroupToFind = null;
            List <TimelinePath> range           = new List <TimelinePath>();

            foreach (TimelinePath group in Owner.AllGroups)
            {
                if (lastGroupToFind == null)
                {
                    if (group == begin)
                    {
                        lastGroupToFind = end;
                    }
                    else if (group == end)
                    {
                        lastGroupToFind = begin;
                    }
                }

                if (lastGroupToFind != null)
                {
                    range.Add(group);
                }

                if (group == lastGroupToFind)
                {
                    break;
                }
            }

            if (lastGroupToFind == begin)
            {
                range.Reverse();
            }

            return(range);
        }
Example #19
0
        /// <summary>
        /// Gets the range of tracks from 'begin' to 'end', inclusive, as determined by the order
        /// that they are presented by the ITimelineDocument. The result always has 'begin'
        /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same,
        /// then the result has only the one ITimelineObject.</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths in the given range.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable <TimelinePath> GetRangeOfTracks(TimelinePath begin, TimelinePath end)
        {
            TimelinePath        lastTrackToFind = null;
            List <TimelinePath> range           = new List <TimelinePath>();

            foreach (TimelinePath track in Owner.AllTracks)
            {
                if (lastTrackToFind == null)
                {
                    if (track == begin)
                    {
                        lastTrackToFind = end;
                    }
                    else if (track == end)
                    {
                        lastTrackToFind = begin;
                    }
                }

                if (lastTrackToFind != null)
                {
                    range.Add(track);
                }

                if (track == lastTrackToFind)
                {
                    break;
                }
            }

            if (lastTrackToFind == begin)
            {
                range.Reverse();
            }

            return(range);
        }
        // get track that contains y value (in client window coordinates, aka, screen space)
        private TimelinePath GetTargetTrack(
            float y,
            TimelineLayout layout,
            IList<ITrack> tracks)
        {
            TimelinePath testPath = new TimelinePath((ITimelineObject)null);
            foreach (ITrack track in tracks)
            {
                testPath.Last = track;
                RectangleF targetBounds = layout[testPath];
                if (targetBounds.Top <= y && targetBounds.Bottom >= y)
                    return testPath;
            }

            return null;
        }
Example #21
0
        /// <summary>
        /// Checks whether the given timeline object's attribute is editable for the current
        /// context and document</summary>
        /// <param name="item">Timeline object that changed</param>
        /// <param name="attribute">Attribute on the timeline object that changed</param>
        /// <returns>True iff this timeline object attribute is editable for the current
        /// ActiveControl, ActiveContext, and ActiveDocument properties</returns>
        public virtual bool IsEditable(ITimelineObject item, AttributeInfo attribute)
        {
            if (attribute == Schema.groupType.expandedAttribute)
                return true;

            TimelinePath path = new TimelinePath(item);
            return ActiveControl.IsEditable(path);
        }
            /// <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);
            }
Example #23
0
        // gets objects of appropriate type that are underneath the moving selected objects
        private TimelinePath[] GetMoveTargets(TimelineLayout layout)
        {
            // get groups and visible tracks
            List <IGroup> groups        = new List <IGroup>();
            List <ITrack> visibleTracks = new List <ITrack>();

            foreach (IGroup group in m_owner.TimelineDocument.Timeline.Groups)
            {
                groups.Add(group);

                IList <ITrack> tracks      = group.Tracks;
                bool           expanded    = group.Expanded;
                bool           collapsible = tracks.Count > 1;
                bool           collapsed   = collapsible && !expanded;
                if (!collapsed)
                {
                    foreach (ITrack track in tracks)
                    {
                        visibleTracks.Add(track);
                    }
                }
            }

            TimelinePath[] targets = new TimelinePath[m_owner.Selection.SelectionCount];
            RectangleF     bounds;
            TimelinePath   testPath = new TimelinePath((ITimelineObject)null);

            int i = -1;

            foreach (TimelinePath path in m_owner.Selection.Selection)
            {
                i++;

                IGroup group = path.Last as IGroup;
                if (group != null)
                {
                    foreach (IGroup targetGroup in groups)
                    {
                        Point p = DragPoint;
                        testPath.Last = targetGroup;
                        RectangleF targetBounds = layout[testPath];
                        if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y)
                        {
                            targets[i] = new TimelinePath(testPath);
                            break;
                        }
                    }
                    continue;
                }

                ITrack track = path.Last as ITrack;
                if (track != null)
                {
                    foreach (ITrack targetTrack in visibleTracks)
                    {
                        Point p = m_owner.DragPoint;
                        testPath.Last = targetTrack;
                        RectangleF targetBounds = layout[testPath];
                        if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y)
                        {
                            targets[i] = new TimelinePath(testPath);
                            break;
                        }
                    }
                    if (targets[i] == null)
                    {
                        foreach (IGroup targetGroup in groups)
                        {
                            Point p = DragPoint;
                            testPath.Last = targetGroup;
                            RectangleF targetBounds = layout[testPath];
                            if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y)
                            {
                                targets[i] = new TimelinePath(testPath);
                                break;
                            }
                        }
                    }
                    continue;
                }

                IInterval interval = path.Last as IInterval;
                if (interval != null)
                {
                    track = interval.Track;
                    if (track != null)
                    {
                        testPath.Last = track;
                        bounds        = layout[testPath];
                        float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                        continue;
                    }
                    else
                    {
                        float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                    }
                }

                IKey key = path.Last as IKey;
                if (key != null)
                {
                    track = key.Track;
                    if (track != null)
                    {
                        testPath.Last = track;
                        bounds        = layout[testPath];
                        float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                        continue;
                    }
                    else
                    {
                        //float y = m_owner.ClientToCanvas(m_owner.DragPoint).Y;
                        float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                    }
                }

                // ignore Markers, as they don't hit targets
            }

            return(targets);
        }
        /// <summary>
        /// Gets the range of groups from 'begin' to 'end', inclusive, as determined by the order
        /// that they are presented by the ITimelineDocument. The result always has 'begin'
        /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same,
        /// then the result has only the one ITimelineObject.</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths in the given range.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable<TimelinePath> GetRangeOfGroups(TimelinePath begin, TimelinePath end)
        {
            TimelinePath lastGroupToFind = null;
            List<TimelinePath> range = new List<TimelinePath>();
            foreach (TimelinePath group in Owner.AllGroups)
            {
                if (lastGroupToFind == null)
                {
                    if (group == begin)
                        lastGroupToFind = end;
                    else if (group == end)
                        lastGroupToFind = begin;
                }

                if (lastGroupToFind != null)
                    range.Add(group);

                if (group == lastGroupToFind)
                    break;
            }

            if (lastGroupToFind == begin)
                range.Reverse();

            return range;
        }
        /// <summary>
        /// Adds range of tracks to selection, from anchor to given target.
        /// Handles selecting a track, taking into account selecting ranges of tracks.</summary>
        /// <param name="target">Track as TimelinePath</param>
        protected virtual void SelectTracks(TimelinePath target)
        {
            // add range of tracks, from anchor to target?
            if ((Control.ModifierKeys & Keys.Shift) != Keys.None)
            {
                if (Anchor != null && Anchor.Last is ITrack && SelectionContext != null)
                {
                    SelectionContext.SetRange(GetRangeOfTracks(Anchor, target));
                    return;
                }
            }

            // simply add this target track, using the current modifier keys to determine how
            Owner.Select<ITrack>(target);
        }
Example #26
0
 /// <summary>
 /// Draws the timeline to the display</summary>
 /// <param name="timeline">Timeline</param>
 /// <param name="selection">Selected timeline objects</param>
 /// <param name="activeGroup">Currently active group, or null</param>
 /// <param name="activeTrack">Currently active track, or null</param>
 /// <param name="transform">Transform taking timeline objects to display coordinates</param>
 /// <param name="clientRectangle">Bounds of displayed area of timeline, in screen space</param>
 /// <param name="marginBounds">Page coordinate bounding rectangle for printing</param>
 public void Print(
     ITimeline timeline,
     ISelectionContext selection,
     TimelinePath activeGroup,
     TimelinePath activeTrack,
     Matrix transform,
     RectangleF clientRectangle,
     Rectangle marginBounds)
 {
     m_marginBounds = marginBounds;
     try
     {
         m_printing = true;
         Draw(timeline, selection, activeGroup, activeTrack, transform, clientRectangle);
     }
     finally
     {
         m_printing = false;
     }
 }
        // private because this may be moved to TimelineControl
        private bool Overlaps(TimelinePath path, float beginTime, float endTime)
        {
            IEvent e = (IEvent)path.Last;

            Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path);
            float start = GdiUtil.Transform(localToWorld, e.Start);
            float length = GdiUtil.TransformVector(localToWorld, e.Length);

            // If the length is zero, then count an exact match with beginTime or endTime as
            //  being an overlap.
            if (length == 0)
                return !(
                    start > endTime ||
                    start + length < beginTime);

            // Otherwise, don't count an exact match.
            return !(
                start >= endTime ||
                start + length <= beginTime);
        }
Example #28
0
        // documentTop is in timeline coordinates
        private void LayoutSubTimeline(
            TimelinePath path, ref float documentTop, Context c,
            TimelineLayout result)
        {
            SizeF pixelSize = c.PixelSize;
            float margin = pixelSize.Height * m_margin;

            // Limit Markers, etc., to being drawn within the owning timeline.
            RectangleF originalBounds = c.Bounds;
            c.Bounds.Y = documentTop;

            // Add a large bounding box for the whole TimelineReference row.
            float docRowHeight = Math.Max(margin * 2, MinimumTrackSize);
            RectangleF refBounds = new RectangleF(c.Bounds.X, documentTop, c.Bounds.Width, docRowHeight);
            documentTop += docRowHeight;

            // The whole timeline, with all groups and tracks laid out, and offset by the reference.Start.
            ITimelineReference reference = (ITimelineReference)path.Last;
            TimelineReferenceOptions options = reference.Options;
            IHierarchicalTimeline resolved = reference.Target;
            if (resolved != null)
            {
                Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path);
                c.PushTransform(localToWorld, MatrixOrder.Prepend);

                LayoutSubTimeline(path, resolved, ref documentTop, options.Expanded, c, result);

                c.PopTransform();
            }

            // Now that we know the height of the tallest group, we can update the collapsed rectangle.
            if (!options.Expanded)
            {
                docRowHeight = Math.Max(docRowHeight, documentTop - refBounds.Y);
                refBounds.Height = docRowHeight;
            }
            refBounds = GdiUtil.Transform(c.Transform, refBounds);
            result.Add(path, refBounds);

            // Restore the bounds so that the horizontal scale (tick marks & numbers) is correct.
            c.Bounds = originalBounds;
        }
 // private because this may be moved to TimelineControl
 private TimelinePath GetOwningTrack(TimelinePath begin)
 {
     if (begin.Last is IKey)
     {
         TimelinePath trackPath = new TimelinePath(begin);
         trackPath.Last = ((IKey)begin.Last).Track;
         return trackPath;
     }
     if (begin.Last is IInterval)
     {
         TimelinePath trackPath = new TimelinePath(begin);
         trackPath.Last = ((IInterval)begin.Last).Track;
         return trackPath;
     }
     if (begin.Last is ITrack)
         return begin;
     return null;
 }
Example #30
0
        /// <summary>
        /// Draws the timeline to the display</summary>
        /// <param name="timeline">Timeline</param>
        /// <param name="selection">Selected timeline objects</param>
        /// <param name="activeGroup">Currently active group, or null</param>
        /// <param name="activeTrack">Currently active track, or null</param>
        /// <param name="transform">Transform taking timeline objects to display coordinates</param>
        /// <param name="clientRectangle">Display coordinate bounding rectangle</param>
        /// <param name="g">Graphics object</param>
        /// <returns>Bounding rectangles for all timeline objects, organized in a dictionary of TimelinePath/RectangleF pairs</returns>
        public virtual TimelineLayout Draw(
            ITimeline timeline,
            ISelectionContext selection,
            TimelinePath activeGroup,
            TimelinePath activeTrack,
            Matrix transform,
            RectangleF clientRectangle,
            Graphics g)
        {
            if (m_printing)
            {
                transform.Translate(m_marginBounds.Left, m_marginBounds.Top, MatrixOrder.Append);
                g.SetClip(m_marginBounds);
            }

            Context c = new Context(this, transform, clientRectangle, g);
            TimelineLayout layout = Layout(timeline, c);
            c.ClearRecursionData();

            // Clear the header column.
            g.FillRectangle(SystemBrushes.Control, 0, 0, m_headerWidth, c.ClientRectangle.Height);

            // Draw the main timeline and then any sub-timelines.
            DrawSubTimeline(null, timeline, false, true, selection, activeGroup, activeTrack, layout, c);
            foreach (TimelinePath path in TimelineControl.GetHierarchy(timeline))
                DrawSubTimeline(path, selection, activeGroup, activeTrack, layout, c);

            // Draw the dark vertical line on the header that separates the groups and tracks.
            g.DrawLine(SystemPens.ControlDark, TrackIndent, m_timeScaleHeight, TrackIndent, c.ClientRectangle.Height);

            // Draw the dark vertical line on the right-side of the header, separating it from the canvas.
            g.DrawLine(SystemPens.ControlDark, m_headerWidth, m_timeScaleHeight, m_headerWidth, c.ClientRectangle.Height);

            // draw scales, etc.
            if (m_printing)
                c.Graphics.TranslateTransform(0, m_marginBounds.Top);
            DrawEventOverlay(c);

            // Draw the dark horizontal line underneath the scale.
            g.DrawLine(SystemPens.ControlDark, 0, m_timeScaleHeight, m_headerWidth, m_timeScaleHeight);

            // Give the Markers in the main timeline precedence over the scale and canvas
            RectangleF clipBounds = g.ClipBounds;
            clipBounds.X = m_headerWidth;
            DrawMarkers(null, timeline, selection, c, layout, clipBounds);

            return layout;
        }
        // gets objects of appropriate type that are underneath the moving selected objects
        private TimelinePath[] GetMoveTargets(TimelineLayout layout)
        {
            // get groups and visible tracks
            List<IGroup> groups = new List<IGroup>();
            List<ITrack> visibleTracks = new List<ITrack>();
            foreach (IGroup group in m_owner.TimelineDocument.Timeline.Groups)
            {
                groups.Add(group);

                IList<ITrack> tracks = group.Tracks;
                bool expanded = group.Expanded;
                bool collapsible = tracks.Count > 1;
                bool collapsed = collapsible && !expanded;
                if (!collapsed)
                    foreach (ITrack track in tracks)
                        visibleTracks.Add(track);
            }

            TimelinePath[] targets = new TimelinePath[m_owner.Selection.SelectionCount];
            RectangleF bounds;
            TimelinePath testPath = new TimelinePath((ITimelineObject)null);

            int i = -1;
            foreach(TimelinePath path in m_owner.Selection.Selection)
            {
                i++;

                IGroup group = path.Last as IGroup;
                if (group != null)
                {
                    foreach (IGroup targetGroup in groups)
                    {
                        Point p = DragPoint;
                        testPath.Last = targetGroup;
                        RectangleF targetBounds = layout[testPath];
                        if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y)
                        {
                            targets[i] = new TimelinePath(testPath);
                            break;
                        }
                    }
                    continue;
                }

                ITrack track = path.Last as ITrack;
                if (track != null)
                {
                    foreach (ITrack targetTrack in visibleTracks)
                    {
                        Point p = m_owner.DragPoint;
                        testPath.Last = targetTrack;
                        RectangleF targetBounds = layout[testPath];
                        if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y)
                        {
                            targets[i] = new TimelinePath(testPath);
                            break;
                        }
                    }
                    if (targets[i] == null)
                    {
                        foreach (IGroup targetGroup in groups)
                        {
                            Point p = DragPoint;
                            testPath.Last = targetGroup;
                            RectangleF targetBounds = layout[testPath];
                            if (targetBounds.Top <= p.Y && targetBounds.Bottom >= p.Y)
                            {
                                targets[i] = new TimelinePath(testPath);
                                break;
                            }
                        }
                    }
                    continue;
                }

                IInterval interval = path.Last as IInterval;
                if (interval != null)
                {
                    track = interval.Track;
                    if (track != null)
                    {
                        testPath.Last = track;
                        bounds = layout[testPath];
                        float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                        continue;
                    }
                    else
                    {
                        float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                    }
                }

                IKey key = path.Last as IKey;
                if (key != null)
                {
                    track = key.Track;
                    if (track != null)
                    {
                        testPath.Last = track;
                        bounds = layout[testPath];
                        float y = bounds.Top + bounds.Height * 0.5f + DragDelta.Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                        continue;
                    }
                    else
                    {
                        //float y = m_owner.ClientToCanvas(m_owner.DragPoint).Y;
                        float y = m_owner.RectangleToClient(new Rectangle(Cursor.Position, new Size())).Y;
                        targets[i] = GetTargetTrack(y, layout, visibleTracks);
                    }
                }

                // ignore Markers, as they don't hit targets
            }

            return targets;
        }
        /// <summary>
        /// Gets the range of tracks from 'begin' to 'end', inclusive, as determined by the order
        /// that they are presented by the ITimelineDocument. The result always has 'begin'
        /// as the first ITimelineObject and 'end' as the last. If 'begin' and 'end' are the same,
        /// then the result has only the one ITimelineObject.</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths in the given range.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable<TimelinePath> GetRangeOfTracks(TimelinePath begin, TimelinePath end)
        {
            TimelinePath lastTrackToFind = null;
            List<TimelinePath> range = new List<TimelinePath>();
            foreach (TimelinePath track in Owner.AllTracks)
            {
                if (lastTrackToFind == null)
                {
                    if (track == begin)
                        lastTrackToFind = end;
                    else if (track == end)
                        lastTrackToFind = begin;
                }

                if (lastTrackToFind != null)
                    range.Add(track);

                if (track == lastTrackToFind)
                    break;
            }

            if (lastTrackToFind == begin)
                range.Reverse();

            return range;
        }
Example #33
0
        /// <summary>
        /// Gets the range of events that intersect the rectangle that encloses 'begin' and 'end'</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths that intersect the rectangle.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable <TimelinePath> GetRangeOfEvents(TimelinePath begin, TimelinePath end)
        {
            // If the two paths are the same, then the rectangle is just a point.
            // This check fixes a bug where the 'rectangle' becomes the whole interval
            //  and then falsely selects an adjacent zero-length object due to Overlaps().
            if (begin.Equals(end))
            {
                return new TimelinePath[] { begin }
            }
            ;

            // Get the range of tracks between these two events.
            bool searchMarkers = false;

            System.Collections.IEnumerable rangeOfTracks;
            if (begin.Last is IMarker || end.Last is IMarker)
            {
                rangeOfTracks = Owner.AllTracks;

                searchMarkers = true;
            }
            else
            {
                TimelinePath beginTrack = GetOwningTrack(begin);
                TimelinePath endTrack   = GetOwningTrack(end);
                rangeOfTracks = GetRangeOfTracks(beginTrack, endTrack);
            }

            // Get the range of times to look for.
            float beginStart, beginEnd;

            D2dTimelineControl.CalculateRange(begin, out beginStart, out beginEnd);
            float endStart, endEnd;

            D2dTimelineControl.CalculateRange(end, out endStart, out endEnd);
            float beginTime = Math.Min(beginStart, endStart);
            float endTime   = Math.Max(beginEnd, endEnd);

            // Look through all the IEvents of these tracks.
            List <TimelinePath> range = new List <TimelinePath>();

            foreach (TimelinePath testPath in rangeOfTracks)
            {
                ITrack track = (ITrack)testPath.Last;
                foreach (IKey key in track.Keys)
                {
                    testPath.Last = key;
                    if (Overlaps(testPath, beginTime, endTime))
                    {
                        range.Add(new TimelinePath(testPath));
                    }
                }

                foreach (IInterval interval in track.Intervals)
                {
                    testPath.Last = interval;
                    if (Overlaps(testPath, beginTime, endTime))
                    {
                        range.Add(new TimelinePath(testPath));
                    }
                }
            }

            // Look for markers?
            if (searchMarkers)
            {
                foreach (TimelinePath testPath in Owner.AllMarkers)
                {
                    if (Overlaps(testPath, beginTime, endTime))
                    {
                        range.Add(testPath);
                    }
                }
            }

            return(range);
        }
Example #34
0
        /// <summary>
        /// Draws the timeline to the display</summary>
        /// <param name="timeline">Timeline</param>
        /// <param name="selection">Selected timeline objects</param>
        /// <param name="activeGroup">Currently active group, or null</param>
        /// <param name="activeTrack">Currently active track, or null</param>
        /// <param name="transform">Transform taking timeline objects to display coordinates</param>
        /// <param name="clientRectangle">Bounds of displayed area of timeline, in screen space</param>
        /// <returns>Bounding rectangles for all timeline objects, organized in a dictionary of TimelinePath/RectangleF pairs</returns>
        public virtual TimelineLayout Draw(
            ITimeline timeline,
            ISelectionContext selection,
            TimelinePath activeGroup,
            TimelinePath activeTrack,
            Matrix transform,
            RectangleF clientRectangle)
        {
            Context c = new Context(this, transform, clientRectangle, m_graphics);
            TimelineLayout layout = Layout(timeline, c);
            c.ClearRecursionData();

            try
            {
                if (m_printing)
                {
                    transform.Translate(m_marginBounds.Left, m_marginBounds.Top, MatrixOrder.Append);
                    m_graphics.PushAxisAlignedClip(m_marginBounds);
                }

                // Clear the header column.
                m_graphics.FillRectangle(new RectangleF(0, 0, HeaderWidth, c.ClientRectangle.Height), m_headerBrush);

                // Draw the main timeline and then any sub-timelines.
                DrawSubTimeline(null, timeline, false, true, selection, activeGroup, activeTrack, layout, c);
                foreach (TimelinePath path in D2dTimelineControl.GetHierarchy(timeline))
                    DrawSubTimeline(path, selection, activeGroup, activeTrack, layout, c);

                // Draw the dark vertical line on the header that separates the groups and tracks.
                m_graphics.DrawLine(new PointF(TrackIndent, m_timeScaleHeight), new PointF(TrackIndent, c.ClientRectangle.Height), m_headerLineBrush);

                // Draw the dark vertical line on the right-side of the header, separating it from the canvas.
                m_graphics.DrawLine(new PointF(HeaderWidth, m_timeScaleHeight), new PointF(HeaderWidth, c.ClientRectangle.Height), m_headerLineBrush);

                // draw scales, etc.
                if (m_printing)
                    c.Graphics.TranslateTransform(0, m_marginBounds.Top);
                DrawEventOverlay(c);

                // Draw the dark horizontal line underneath the scale.
                m_graphics.DrawLine(new PointF(0, m_timeScaleHeight), new PointF(HeaderWidth, m_timeScaleHeight), m_scaleLineBrush);
            }
            finally
            {
                if (m_printing)
                    m_graphics.PopAxisAlignedClip();
            }

            // Give the Markers in the main timeline precedence over the scale and canvas
            RectangleF clipBounds = m_graphics.ClipBounds;
            clipBounds.X = HeaderWidth;
            //var clipBounds = new RectangleF(HeaderWidth, 0, m_graphics.Size.Width, m_graphics.Size.Height);
            DrawMarkers(null, timeline, selection, c, layout, clipBounds);

            return layout;
        }
Example #35
0
        private void DrawSubTimeline(
            TimelinePath path,
            ISelectionContext selection,
            TimelinePath activeGroup,
            TimelinePath activeTrack,
            TimelineLayout layout,
            Context c)
        {
            // Include the reference's offset into the Transform and InverseTransform properties.
            Matrix localToWorld = D2dTimelineControl.CalculateLocalToWorld(path);
            c.PushTransform(localToWorld, MatrixOrder.Prepend);

            // draw the row that has this timeline reference's name
            ITimelineReference reference = (ITimelineReference)path.Last;
            RectangleF clipBounds = m_graphics.ClipBounds;
            RectangleF bounds = layout.GetBounds(path);
            IHierarchicalTimeline timeline = reference.Target;
            if (bounds.IntersectsWith(clipBounds))
            {
                DrawMode drawMode = DrawMode.Normal;
                if (selection.SelectionContains(path))
                    drawMode |= DrawMode.Selected;
                DrawTimelineReference(reference, bounds, drawMode, c);
            }

            // draw the timeline document as if it were the main document
            if (timeline != null)
                DrawSubTimeline(path, timeline, true, reference.Options.Expanded, selection, activeGroup, activeTrack, layout, c);

            c.PopTransform();
        }
Example #36
0
        private void DrawSubTimeline(
            TimelinePath path,
            ITimeline timeline,
            bool subTimeline,
            bool expandedTimeline,
            ISelectionContext selection,
            TimelinePath activeGroup,
            TimelinePath activeTrack,
            TimelineLayout layout,
            Context c)
        {
            //if (c.TestRecursion(timeline))
            //    return;

            if (!subTimeline)
                m_offsetX = c.Transform.OffsetX;

            RectangleF clipBounds = m_graphics.ClipBounds;

            DrawGroupsAndTracks(path, timeline, expandedTimeline, selection, c, layout, clipBounds);

            // draw markers over keys, intervals, tracks, and group
            if (subTimeline)
            {
                // Give the Markers in the main timeline precedence; draw on top of everything.
                clipBounds.X = HeaderWidth;
                clipBounds.Width -= HeaderWidth;
                DrawMarkers(path, timeline, selection, c, layout, clipBounds);
            }

            // Draw the group and track handles only if the owning timeline is expanded.
            if (expandedTimeline)
            {
                if (m_printing)
                    c.Graphics.TranslateTransform(m_marginBounds.Left, 0);
                RectangleF bounds;
                foreach (IGroup group in timeline.Groups)
                {
                    IList<ITrack> tracks = group.Tracks;
                    TimelinePath groupPath = path + group;
                    bounds = layout.GetBounds(groupPath);
                    bounds = GetGroupHandleRect(bounds, !group.Expanded);
                    RectangleF groupLabelBounds = new RectangleF(bounds.X, bounds.Y, HeaderWidth, bounds.Height);

                    // Draw group's move handle.
                    DrawMoveHandle(bounds, selection.SelectionContains(groupPath), groupPath == activeGroup);

                    // Draw expander?
                    if (tracks.Count > 1)
                    {
                        RectangleF expanderRect = GetExpanderRect(bounds);
                        m_graphics.DrawExpander(
                            expanderRect.X,
                            expanderRect.Y,
                            expanderRect.Width,
                            m_expanderBrush,
                            group.Expanded);

                        groupLabelBounds.X += TrackIndent;
                        groupLabelBounds.Width -= TrackIndent;
                    }

                    // Draw tracks' move handles?
                    if (group.Expanded || tracks.Count == 1)
                    {
                        foreach (ITrack track in tracks)
                        {
                            TimelinePath trackPath = path + track;
                            bounds = layout.GetBounds(trackPath);
                            bounds = GetTrackHandleRect(bounds);
                            DrawMoveHandle(bounds, selection.SelectionContains(trackPath), trackPath == activeTrack);
                            if (bounds.Width > 0f)
                                m_graphics.DrawText(track.Name, m_trackTextFormat, bounds, m_nameBrush);
                        }
                    }

                    // Draw group name.
                    if (groupLabelBounds.Width > 0)
                        m_graphics.DrawText(group.Name, c.TextFormat, groupLabelBounds, m_nameBrush);
                }
                if (m_printing)
                    c.Graphics.TranslateTransform(-m_marginBounds.Left, 0);
            }

            return;
        }
        /// <summary>
        /// Adds range of groups to selection, from anchor to given target.
        /// Handles selecting a group, taking into account selecting ranges of groups.
        /// If no anchor, just selects target.</summary>
        /// <param name="target">Timeline path</param>
        protected virtual void SelectGroups(TimelinePath target)
        {
            // Add range of groups, from anchor to target? Holding down Ctrl or Alt simultaneously
            //  still does a range selection in Visual Studio's Solution Explorer.
            if ((Control.ModifierKeys & Keys.Shift) != Keys.None)
            {
                if (Anchor != null && Anchor.Last is IGroup && SelectionContext != null)
                {
                    SelectionContext.SetRange(GetRangeOfGroups(Anchor, target));
                    return;
                }
            }

            // simply add this target group, using the current modifier keys to determine how
            Owner.Select<IGroup>(target);
        }
Example #38
0
 private void DrawMarkers(TimelinePath path, ITimeline timeline, ISelectionContext selection,
     Context c, TimelineLayout layout, RectangleF clipBounds)
 {
     RectangleF bounds;
     foreach (IMarker marker in timeline.Markers)
     {
         TimelinePath markerPath = path + marker;
         if (!layout.TryGetBounds(markerPath, out bounds))
             continue;
         if (bounds.IntersectsWith(clipBounds))
         {
             DrawMode drawMode = DrawMode.Normal;
             if (selection.SelectionContains(markerPath))
                 drawMode |= DrawMode.Selected;
             Draw(marker, bounds, drawMode, c);
         }
     }
 }
        /// <summary>
        /// Gets the range of events that intersect the rectangle that encloses 'begin' and 'end'</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths that intersect the rectangle.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable<TimelinePath> GetRangeOfEvents(TimelinePath begin, TimelinePath end)
        {
            // If the two paths are the same, then the rectangle is just a point.
            // This check fixes a bug where the 'rectangle' becomes the whole interval
            //  and then falsely selects an adjacent zero-length object due to Overlaps().
            if (begin.Equals(end))
                return new TimelinePath[] { begin };

            // Get the range of tracks between these two events.
            bool searchMarkers = false;
            System.Collections.IEnumerable rangeOfTracks;
            if (begin.Last is IMarker || end.Last is IMarker)
            {
                rangeOfTracks = Owner.AllTracks;
                searchMarkers = true;
            }
            else
            {
                TimelinePath beginTrack = GetOwningTrack(begin);
                TimelinePath endTrack = GetOwningTrack(end);
                rangeOfTracks = GetRangeOfTracks(beginTrack, endTrack);
            }

            // Get the range of times to look for.
            float beginStart, beginEnd;
            D2dTimelineControl.CalculateRange(begin, out beginStart, out beginEnd);
            float endStart, endEnd;
            D2dTimelineControl.CalculateRange(end, out endStart, out endEnd);
            float beginTime = Math.Min(beginStart, endStart);
            float endTime = Math.Max(beginEnd, endEnd);

            // Look through all the IEvents of these tracks.
            List<TimelinePath> range = new List<TimelinePath>();
            foreach (TimelinePath testPath in rangeOfTracks)
            {
                ITrack track = (ITrack)testPath.Last;
                foreach (IKey key in track.Keys)
                {
                    testPath.Last = key;
                    if (Overlaps(testPath, beginTime, endTime))
                        range.Add(new TimelinePath(testPath));
                }

                foreach (IInterval interval in track.Intervals)
                {
                    testPath.Last = interval;
                    if (Overlaps(testPath, beginTime, endTime))
                        range.Add(new TimelinePath(testPath));
                }
            }

            // Look for markers?
            if (searchMarkers)
            {
                foreach (TimelinePath testPath in Owner.AllMarkers)
                {
                    if (Overlaps(testPath, beginTime, endTime))
                        range.Add(testPath);
                }
            }

            return range;
        }
Example #40
0
        private void DrawGroupsAndTracks(TimelinePath path, ITimeline timeline, bool expandedTimeline,
            ISelectionContext selection, Context c, TimelineLayout layout, RectangleF clipBounds)
        {
            RectangleF canvasBounds = clipBounds; //clipBounds minus the left-side header
            canvasBounds.X = HeaderWidth;
            canvasBounds.Width -= HeaderWidth;

            RectangleF bounds;
            foreach (IGroup group in timeline.Groups)
            {
                TimelinePath groupPath = path + group;
                if (!layout.TryGetBounds(groupPath, out bounds))
                    continue;
                if (bounds.IntersectsWith(clipBounds))
                {
                    DrawMode drawMode = DrawMode.Normal;
                    if (selection.SelectionContains(groupPath))
                        drawMode |= DrawMode.Selected;
                    if (expandedTimeline)
                        Draw(group, bounds, drawMode, c);

                    IList<ITrack> tracks = group.Tracks;
                    bool collapsed = !expandedTimeline || (!group.Expanded && tracks.Count > 1);
                    foreach (ITrack track in tracks)
                    {
                        TimelinePath trackPath = path + track;
                        bounds = layout.GetBounds(trackPath);
                        if (bounds.IntersectsWith(clipBounds))
                        {
                            drawMode = DrawMode.Normal;
                            if (selection.SelectionContains(trackPath))
                                drawMode |= DrawMode.Selected;
                            if (collapsed)
                                drawMode = DrawMode.Collapsed;
                            Draw(track, bounds, drawMode, c);

                            foreach (IInterval interval in track.Intervals)
                            {
                                TimelinePath intervalPath = path + interval;
                                bounds = layout.GetBounds(intervalPath);
                                if (bounds.IntersectsWith(canvasBounds))
                                {
                                    drawMode = DrawMode.Normal;
                                    if (selection.SelectionContains(intervalPath))
                                        drawMode |= DrawMode.Selected;
                                    if (collapsed)
                                        drawMode = DrawMode.Collapsed;
                                    Draw(interval, bounds, drawMode, c);
                                }
                            }

                            foreach (IKey key in track.Keys)
                            {
                                TimelinePath keyPath = path + key;
                                bounds = layout.GetBounds(keyPath);
                                if (bounds.IntersectsWith(canvasBounds))
                                {
                                    drawMode = DrawMode.Normal;
                                    if (selection.SelectionContains(keyPath))
                                        drawMode |= DrawMode.Selected;
                                    if (collapsed)
                                        drawMode = DrawMode.Collapsed;
                                    Draw(key, bounds, drawMode, c);
                                }
                            }
                        }
                    }
                }
            }
        }
Example #41
0
        // 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 = 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 = D2dTimelineControl.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 D2dTimelineControl.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);

            // Pretend that drag-drop objects are in the TimelineLayout object
            if (m_owner.DragDropObjects != null)
            {
                foreach (ITimelineObject dragDrop in m_owner.DragDropObjects)
                {
                    layout[dragDrop] = GetDragDropBounds(dragDrop);
                }
            }

            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;
                    valid &= m_owner.Constraints.IsStartValid(interval, ref start);
                    valid &= m_owner.Constraints.IsLengthValid(interval, ref length);

                    if (valid)
                    {
                        ITrack intervalTrack = interval.Track;
                        if (intervalTrack != null)
                        {
                            yOffset = layout[target].Y - layout[interval.Track].Y;
                        }
                        else
                        {
                            yOffset = layout[target].Y - bounds.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;
                    valid &= m_owner.Constraints.IsStartValid(key, ref start);

                    if (valid)
                    {
                        ITrack keyTrack = key.Track;
                        if (keyTrack != null)
                        {
                            yOffset = layout[targetTrack].Y - layout[key.Track].Y;
                        }
                        else
                        {
                            yOffset = layout[targetTrack].Y - bounds.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 =
                            (DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack();
                    }
                }
                else if (group != null)
                {
                    xOffset = 0;
                    if (target == null)
                    {
                        IList <IGroup> groups = m_owner.TimelineDocument.Timeline.Groups;
                        target = (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);
        }
Example #42
0
        private void PickSubTimeline(
            TimelinePath path,
            RectangleF pickRect,
            Context c,
            TimelineLayout layout,
            List<HitRecord> result)
        {
            RectangleF clipBounds = c.Graphics.ClipBounds;
            ITimelineReference reference = (ITimelineReference)path.Last;
            RectangleF bounds;
            if (!layout.TryGetBounds(path, out bounds))
                return;

            IHierarchicalTimeline timeline = reference.Target;
            if (timeline != null && reference.Options.Expanded)
                PickSubTimeline(path, timeline, pickRect, c, layout, result);

            if (timeline != null && bounds.IntersectsWith(clipBounds))
            {
                IList<IGroup> groups = timeline.Groups;
                bool collapsible = groups.Count > 0;
                bool expanded = reference.Options.Expanded;
                bool collapsed = collapsible && !expanded;

                RectangleF handleRect = GetGroupHandleRect(bounds, collapsed);
                RectangleF expanderRect = GetExpanderRect(handleRect);

                if (collapsible && expanderRect.IntersectsWith(pickRect))
                {
                    result.Add(new HitRecord(HitType.GroupExpand, path));
                }
                else if (bounds.IntersectsWith(pickRect))
                {
                    result.Add(new HitRecord(HitType.Key, path));
                }
            }
            else if (bounds.IntersectsWith(pickRect))
            {
                result.Add(new HitRecord(HitType.Key, path));
            }
        }
        /// <summary>
        /// Gets the range of events that intersect the rectangle that encloses 'begin' and 'end'</summary>
        /// <param name="begin">Beginning timeline path</param>
        /// <param name="end">Ending timeline path</param>
        /// <returns>Enumeration of timeline paths that intersect the rectangle.
        /// A timeline path is a sequence of objects in timelines, e.g., groups, tracks, events.</returns>
        protected virtual IEnumerable<TimelinePath> GetRangeOfEvents(TimelinePath begin, TimelinePath end)
        {
            // Get the range of tracks between these two events.
            bool searchMarkers = false;
            System.Collections.IEnumerable rangeOfTracks;
            if (begin.Last is IMarker || end.Last is IMarker)
            {
                rangeOfTracks = Owner.AllTracks;
                searchMarkers = true;
            }
            else
            {
                TimelinePath beginTrack = GetOwningTrack(begin);
                TimelinePath endTrack = GetOwningTrack(end);
                rangeOfTracks = GetRangeOfTracks(beginTrack, endTrack);
            }

            // Get the range of times to look for.
            float beginStart, beginEnd;
            TimelineControl.CalculateRange(begin, out beginStart, out beginEnd);
            float endStart, endEnd;
            TimelineControl.CalculateRange(end, out endStart, out endEnd);
            float beginTime = Math.Min(beginStart, endStart);
            float endTime = Math.Max(beginEnd, endEnd);

            // Look through all the IEvents of these tracks.
            List<TimelinePath> range = new List<TimelinePath>();
            foreach (TimelinePath testPath in rangeOfTracks)
            {
                ITrack track = (ITrack)testPath.Last;
                foreach (IKey key in track.Keys)
                {
                    testPath.Last = key;
                    if (Overlaps(testPath, beginTime, endTime))
                        range.Add(new TimelinePath(testPath));
                }

                foreach (IInterval interval in track.Intervals)
                {
                    testPath.Last = interval;
                    if (Overlaps(testPath, beginTime, endTime))
                        range.Add(new TimelinePath(testPath));
                }
            }

            // Look for markers?
            if (searchMarkers)
            {
                foreach (TimelinePath testPath in Owner.AllMarkers)
                {
                    if (Overlaps(testPath, beginTime, endTime))
                        range.Add(testPath);
                }
            }

            return range;
        }
Example #44
0
        private void PickSubTimeline(
            TimelinePath root,
            ITimeline timeline,
            RectangleF pickRect,
            Context c,
            TimelineLayout layout,
            List<HitRecord> result)
        {
            if (timeline == null)
                return;

            RectangleF clipBounds = c.Graphics.ClipBounds;
            RectangleF clientRectangle = c.ClientRectangle;
            RectangleF bounds;
            HitType hitType;

            foreach (IMarker marker in timeline.Markers)
            {
                if (!layout.TryGetBounds(root + marker, out bounds))
                    continue;
                if (bounds.IntersectsWith(clipBounds) && pickRect.Right >= HeaderWidth)
                {
                    hitType = Pick(marker, bounds, pickRect, c);
                    if (hitType != HitType.None)
                        result.Add(new HitRecord(hitType, root + marker));
                }
            }

            // If the pick is on the timescale, then let's stop here.
            if (pickRect.Left > HeaderWidth &&
                pickRect.Right < clientRectangle.Width &&
                pickRect.Bottom > 0 &&
                pickRect.Bottom < TimeScaleHeight)
            {
                if ((result.Count == 0) && (pickRect.Height <= 2 * PickTolerance) && (pickRect.Width <= 2 * PickTolerance))
                    result.Add(new HitRecord(HitType.TimeScale, null));
                return;
            }

            IList<IGroup> groups = timeline.Groups;
            for (int i = groups.Count - 1; i >= 0; i--)
            {
                IGroup group = groups[i];
                if (!layout.ContainsPath(root + group))
                    continue;
                IList<ITrack> tracks = group.Tracks;
                bool expanded = group.Expanded;
                bool collapsible = tracks.Count > 1;
                bool collapsed = collapsible && !expanded;
                if (!collapsed)
                {
                    for (int j = tracks.Count - 1; j >= 0; j--)
                    {
                        ITrack track = tracks[j];
                        IList<IKey> keys = track.Keys;
                        for (int k = keys.Count - 1; k >= 0; k--)
                        {
                            IKey key = keys[k];
                            if (!layout.TryGetBounds(root + key, out bounds))
                                continue;
                            if (bounds.IntersectsWith(clipBounds) && pickRect.Right >= HeaderWidth)
                            {
                                hitType = Pick(key, bounds, pickRect, c);
                                if (hitType != HitType.None)
                                    result.Add(new HitRecord(hitType, root + key));
                            }
                        }

                        IList<IInterval> intervals = track.Intervals;
                        for (int k = intervals.Count - 1; k >= 0; k--)
                        {
                            IInterval interval = intervals[k];
                            if (!layout.TryGetBounds(root + interval, out bounds))
                                continue;
                            if (bounds.IntersectsWith(clipBounds) && pickRect.Right >= HeaderWidth)
                            {
                                hitType = Pick(interval, bounds, pickRect, c);
                                if (hitType != HitType.None)
                                    result.Add(new HitRecord(hitType, root + interval));
                            }
                        }

                        if (!layout.TryGetBounds(root + track, out bounds))
                            continue;
                        if (bounds.IntersectsWith(clipBounds))
                        {
                            RectangleF handleRect = GetTrackHandleRect(bounds);
                            if (handleRect.IntersectsWith(pickRect))
                            {
                                result.Add(new HitRecord(HitType.TrackMove, root + track));
                            }
                            else if (bounds.IntersectsWith(pickRect))
                            {
                                result.Add(new HitRecord(HitType.Track, root + track));
                            }
                        }
                    }
                }

                if (!layout.TryGetBounds(root + group, out bounds))
                    continue;
                if (bounds.IntersectsWith(clipBounds))
                {
                    RectangleF handleRect = GetGroupHandleRect(bounds, collapsed);
                    RectangleF expanderRect = GetExpanderRect(handleRect);

                    if (collapsible && expanderRect.IntersectsWith(pickRect))
                    {
                        result.Add(new HitRecord(HitType.GroupExpand, root + group));
                    }
                    else if (handleRect.IntersectsWith(pickRect))
                    {
                        result.Add(new HitRecord(HitType.GroupMove, root + group));
                    }
                    else if (bounds.IntersectsWith(pickRect))
                    {
                        result.Add(new HitRecord(HitType.Group, root + group));
                    }
                }
            }

            if (pickRect.Left < HeaderWidth && HeaderWidth < pickRect.Right)
            {
                result.Add(new HitRecord(HitType.HeaderResize, null));
            }
        }
Example #45
0
        // documentTop is in timeline coordinates
        private void LayoutSubTimeline(
            TimelinePath path,
            ITimeline timeline, ref float documentTop, bool expandedTimeline, Context c,
            TimelineLayout result)
        {
            //if (c.TestRecursion(timeline))
            //    return;

            RectangleF bounds;
            SizeF pixelSize = c.PixelSize;
            float margin = pixelSize.Height * m_margin;

            float groupTop = documentTop;
            float documentBottom = groupTop;

            foreach (IGroup group in timeline.Groups)
            {
                bool expanded = expandedTimeline && group.Expanded;

                float groupBottom = groupTop;
                float trackTop = groupTop;
                foreach (ITrack track in group.Tracks)
                {
                    float eventTop = trackTop + margin;
                    float trackBottom = eventTop;

                    foreach (IInterval interval in track.Intervals)
                    {
                        bounds = GetBounds(interval, eventTop, c);
                        trackBottom = Math.Max(trackBottom, bounds.Bottom);
                        bounds = GdiUtil.Transform(c.Transform, bounds);
                        // add it, even if 'expandedTimeline' is false, to get the shadow effect
                        result.Add(path + interval, bounds);
                    }

                    foreach (IKey key in track.Keys)
                    {
                        bounds = GetBounds(key, eventTop, c);
                        trackBottom = Math.Max(trackBottom, bounds.Bottom);
                        bounds = GdiUtil.Transform(c.Transform, bounds);
                        // add it, even if 'expandedTimeline' is false, to get the shadow effect
                        result.Add(path + key, bounds);
                    }

                    trackBottom += margin;
                    trackBottom = Math.Max(trackBottom, trackTop + MinimumTrackSize); // need height for track, even if it's empty

                    bounds = new RectangleF(c.Bounds.X, trackTop, c.Bounds.Width, trackBottom - trackTop);
                    bounds = GdiUtil.Transform(c.Transform, bounds);
                    bounds.X = c.ClientRectangle.X;
                    // add it, even if 'expandedTimeline' is false, to get the shadow effect
                    result.Add(path + track, bounds);

                    if (expanded)
                        trackTop = trackBottom;

                    groupBottom = Math.Max(groupBottom, trackBottom);
                }

                // need height for group, even if it's empty
                groupBottom = Math.Max(groupBottom, groupTop + Math.Max(margin*2, MinimumTrackSize));

                float groupHeight = groupBottom - groupTop;
                bounds = new RectangleF(0, groupTop, c.Bounds.Width, groupHeight);
                bounds = GdiUtil.Transform(c.Transform, bounds);
                bounds.X = c.ClientRectangle.X;
                // add it, even if 'expandedTimeline' is false, to get the shadow effect
                result.Add(path + group, bounds);
                if (expandedTimeline)
                    groupTop = groupBottom;
                documentBottom = Math.Max(documentBottom, groupBottom);
            }

            if (expandedTimeline)
            {
                // Draw Markers, but limit them to be within the owning timeline.
                RectangleF originalBounds = c.Bounds;
                c.Bounds.Height = documentBottom - c.Bounds.Y;
                foreach (IMarker marker in timeline.Markers)
                {
                    bounds = GetBounds(marker, c);
                    bounds = GdiUtil.Transform(c.Transform, bounds);
                    result.Add(path + marker, bounds);
                }
                c.Bounds = originalBounds;
            }

            documentTop = documentBottom;
        }
        // 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 = 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 = D2dTimelineControl.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 D2dTimelineControl.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);

            // Pretend that drag-drop objects are in the TimelineLayout object
            if (m_owner.DragDropObjects != null)
            {
                foreach (ITimelineObject dragDrop in m_owner.DragDropObjects)
                    layout[dragDrop] = GetDragDropBounds(dragDrop);
            }

            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;
                    valid &= m_owner.Constraints.IsStartValid(interval, ref start);
                    valid &= m_owner.Constraints.IsLengthValid(interval, ref length);

                    if (valid)
                    {
                        ITrack intervalTrack = interval.Track;
                        if (intervalTrack != null)
                            yOffset = layout[target].Y - layout[interval.Track].Y;
                        else
                            yOffset = layout[target].Y - bounds.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;
                    valid &= m_owner.Constraints.IsStartValid(key, ref start);

                    if (valid)
                    {
                        ITrack keyTrack = key.Track;
                        if (keyTrack != null)
                            yOffset = layout[targetTrack].Y - layout[key.Track].Y;
                        else
                            yOffset = layout[targetTrack].Y - bounds.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 =
                            (DragDelta.Y < 0) ? GetLastTrack() : GetFirstTrack();
                    }
                }
                else if (group != null)
                {
                    xOffset = 0;
                    if (target == null)
                    {
                        IList<IGroup> groups = m_owner.TimelineDocument.Timeline.Groups;
                        target = (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;
        }