private void timeline_MouseUp(object sender, MouseButtonEventArgs e)
        {
            selectionDragStart = null;

            if (selectionIsDragging)
            {
                selectionIsDragging          = false;
                dragSelectionRect.Visibility = Visibility.Hidden;

                if (selectionCompositionMode != CompositionMode.None)
                {
                    sequencer.ConfirmSelectionDelta();
                    selectionCompositionMode = CompositionMode.None;
                }

                timeline.ReleaseMouseCapture();
            }
            else
            {
                // If it was a non-dragging click on empty space, move the cursor.
                var originalDataContext = ((FrameworkElement)e.OriginalSource).DataContext;
                if ((e.ChangedButton == MouseButton.Left || e.ChangedButton == MouseButton.Right) &&
                    !(originalDataContext is BlockViewModel))
                {
                    sequencer.CursorPosition = SnapValue((float)e.GetPosition(timeline).X / sequencer.TimePixelScale);
                }
            }
        }
        // ===== Commands =====


        public void SelectBlock(BlockViewModel block, CompositionMode compositionMode)
        {
            Debug.WriteLine("single select (mode: {0}, block: {1})", compositionMode, block);

            switch (compositionMode)
            {
            case CompositionMode.None:
                SelectedBlocks.Clear();
                if (block != null && !SelectedBlocks.Contains(block))
                {
                    SelectedBlocks.Add(block);
                }
                break;

            case CompositionMode.Additive:
                if (block != null && !SelectedBlocks.Contains(block))
                {
                    SelectedBlocks.Add(block);
                }
                break;

            case CompositionMode.Subtractive:
                if (block != null)
                {
                    SelectedBlocks.Remove(block);
                }
                break;
            }
        }
        private void timeline_MouseMove(object sender, MouseEventArgs e)
        {
            if (selectionDragStart == null)
            {
                return; // TODO why?
            }
            if (selectionIsDragging)
            {
                Point p1 = e.GetPosition(timeline);
                Point p2 = selectionDragStart.Value;
                Rect  r  = new Rect(p1, p2);

                dragSelectionRect.Visibility = Visibility.Visible;
                Canvas.SetLeft(dragSelectionRect, r.Left);
                Canvas.SetTop(dragSelectionRect, r.Top);
                dragSelectionRect.Width  = r.Width;
                dragSelectionRect.Height = r.Height;

                MultiSelectBlocks(r);
                e.Handled = true;
            }
            else if (selectionDragStart != null)
            {
                Vector delta = e.GetPosition(timeline) - selectionDragStart.Value;
                if (delta.LengthSquared >= SELECTION_DRAG_INITIAL_THRESHOLD * SELECTION_DRAG_INITIAL_THRESHOLD)
                {
                    selectionIsDragging      = true;
                    selectionCompositionMode = CompositionModeFromKeyboard();
                    timeline.CaptureMouse();
                    e.Handled = true;
                }
            }
        }
        public void SelectBlocksDelta(IEnumerable <BlockViewModel> collection, CompositionMode compositionMode)
        {
            Debug.WriteLine("multiselect (mode: {0}, blocks: {1})", compositionMode, collection.Count());

            var sel = SelectedBlocks;

            switch (compositionMode)
            {
            case CompositionMode.None:
                ISet <BlockViewModel> newBlocks = new HashSet <BlockViewModel>(collection);
                if (newBlocks.Count == 0)
                {
                    sel.Clear();
                }
                else
                {
                    // make sel equal to newBlocks
                    for (int i = sel.Count - 1; i >= 0; i--)
                    {
                        if (!newBlocks.Contains(sel[i]))
                        {
                            sel.RemoveAt(i);
                        }
                    }
                    foreach (BlockViewModel b in newBlocks)
                    {
                        if (!sel.Contains(b))
                        {
                            sel.Add(b);
                        }
                    }
                }
                break;

            case CompositionMode.Additive:
            {
                // delta set contains all blocks that are part of the additive selection
                // calculate necessary changes
                var toDeselect = temporaryDeltaSelectedBlocks.Except(collection).ToList();
                var toSelect   = collection.Except(temporaryDeltaSelectedBlocks).Except(sel).ToList();

                foreach (BlockViewModel b in toDeselect)
                {
                    sel.Remove(b);
                    temporaryDeltaSelectedBlocks.Remove(b);
                }
                foreach (BlockViewModel b in toSelect)
                {
                    sel.Add(b);
                    temporaryDeltaSelectedBlocks.Add(b);
                }
            }
            break;

            case CompositionMode.Subtractive:
            {
                // delta set contains all blocks that are part of the subtractive selection
                // calculate necessary changes
                var toDeselect = collection.Except(temporaryDeltaSelectedBlocks).Intersect(sel).ToList();
                var toSelect   = temporaryDeltaSelectedBlocks.Except(collection).ToList();

                foreach (BlockViewModel b in toDeselect)
                {
                    sel.Remove(b);
                    temporaryDeltaSelectedBlocks.Add(b);
                }
                foreach (BlockViewModel b in toSelect)
                {
                    if (!sel.Contains(b))
                    {
                        sel.Add(b);
                    }
                    temporaryDeltaSelectedBlocks.Remove(b);
                }
            }
            break;
            }
        }
 // select multiple blocks, but as an atomic operation (not over time)
 public void SelectBlocks(IEnumerable <BlockViewModel> collection, CompositionMode compositionMode)
 {
     SelectBlocksDelta(collection, compositionMode);
     ConfirmSelectionDelta();
 }