private void timelineBlock_MouseUp(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton != MouseButton.Left && e.ChangedButton != MouseButton.Right)
            {
                return;
            }

            if (dragMode != BlockDragMode.None)
            {
                FrameworkElement control      = (FrameworkElement)sender;
                FrameworkElement controlBlock = (FrameworkElement)VisualTreeHelper.GetChild(control, 0);
                //BlockViewModel block = (BlockViewModel)control.DataContext;

                // Suppres context menu after drag.
                if (!dragNeedsToOvercomeThreshold)
                {
                    e.Handled = true;
                }

                // this would be the place to record the undo/redo action (1 for all blocks)

                // reset drag state
                dragMode          = BlockDragMode.None;
                dragStart         = new Point();
                dragTrackBaseline = -1;
                draggedBlocks     = null;

                controlBlock.ReleaseMouseCapture();
            }
        }
        private void timelineBlock_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Handle pipette as normal click event.
            if (sequencer.IsPipetteActive)
            {
                return;
            }

            FrameworkElement control      = (FrameworkElement)sender;
            FrameworkElement controlBlock = (FrameworkElement)VisualTreeHelper.GetChild(control, 0);
            BlockViewModel   block        = (control.DataContext as BlockViewModel);

            // Make sure the clicked block is always selected before dragging.
            if (!block.IsSelected)
            {
                var compositionMode = CompositionModeFromKeyboard();
                if (compositionMode == CompositionMode.Subtractive)
                {
                    compositionMode = CompositionMode.None;
                }
                sequencer.SelectBlock(block, compositionMode);
            }

            var localPos = e.Manipulators.First().GetPosition(controlBlock);

            BlockDragMode mode;

            if (localPos.X > controlBlock.ActualWidth - DRAG_START_END_PIXEL_WINDOW_TOUCH && localPos.X < controlBlock.ActualWidth + DRAG_START_END_PIXEL_WINDOW_TOUCH)
            {
                mode = BlockDragMode.End;
            }
            else if (localPos.X < DRAG_START_END_PIXEL_WINDOW_TOUCH)
            {
                mode = BlockDragMode.Start;
            }
            else
            {
                mode = BlockDragMode.Block;
            }

            // record initial information
            dragMode  = mode;
            dragStart = e.Manipulators.First().GetPosition(timeline); // always relative to timeline
            dragNeedsToOvercomeThreshold = true;
            dragTrackBaseline            = GetTrackIndexFromOffset(dragStart.Y);
            draggedBlocks = sequencer.SelectedBlocks.Select(b => new DraggedBlockData {
                block = b, initialDuration = b.Duration, initialStartTime = b.StartTime
            }).ToList();


            e.Mode    = ManipulationModes.Translate;
            e.Handled = true;
            // TODO [low] handling ManipulationStarting prevents double tap (to select similar) from working with touch, but not handling it stops firing ManipulationCompleted (because of PanningMode of the scroller)
        }
        private void timelineBlock_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (dragMode == BlockDragMode.None)
            {
                return;
            }

            BlockViewModel block = ((sender as FrameworkElement).DataContext as BlockViewModel);

            // this would be the place to record the undo/redo action (1 for all blocks)

            // reset drag state
            dragMode          = BlockDragMode.None;
            dragStart         = new Point();
            dragTrackBaseline = -1;
            draggedBlocks     = null;

            e.Handled = true;
        }
        private void timelineBlock_MouseDown(object sender, MouseButtonEventArgs e)
        {
            if (e.ChangedButton != MouseButton.Left && e.ChangedButton != MouseButton.Right)
            {
                return;
            }

            FrameworkElement control = (FrameworkElement)sender;
            // Get to the actual list item, because for some reason capturing the mouse on the ContentPresenter
            // wrapper (ItemContainer) messes up the reported coordinates during MouseMove.
            FrameworkElement controlBlock = (FrameworkElement)VisualTreeHelper.GetChild(control, 0);
            BlockViewModel   block        = (control.DataContext as BlockViewModel);

            if (sequencer.IsPipetteActive)
            {
                HandlePipetteClick(block, controlBlock, e);
                return;
            }

            var localMouse = e.GetPosition(controlBlock);

            BlockDragMode mode;

            if (e.RightButton == MouseButtonState.Pressed)
            {
                mode = BlockDragMode.Block;
            }
            else if (localMouse.X > controlBlock.ActualWidth - DRAG_START_END_PIXEL_WINDOW && localMouse.X < controlBlock.ActualWidth + DRAG_START_END_PIXEL_WINDOW)
            {
                mode = BlockDragMode.End;
            }
            else if (localMouse.X < DRAG_START_END_PIXEL_WINDOW)
            {
                mode = BlockDragMode.Start;
            }
            else
            {
                mode = BlockDragMode.None;
            }


            if (mode == BlockDragMode.None)
            {
                IEnumerable <BlockViewModel> toSelect;
                if (e.ClickCount == 2 && block is ColorBlockViewModel)
                {
                    toSelect = sequencer.AllBlocks.OfType <ColorBlockViewModel>().Where(other => other.Color == ((ColorBlockViewModel)block).Color);
                }
                else if (e.ClickCount == 2 && block is RampBlockViewModel)
                {
                    toSelect = sequencer.AllBlocks.OfType <RampBlockViewModel>().Where(other => other.StartColor == ((RampBlockViewModel)block).StartColor &&
                                                                                       other.EndColor == ((RampBlockViewModel)block).EndColor);
                }
                else
                {
                    toSelect = Enumerable.Repeat(block, 1);
                }

                sequencer.SelectBlocks(toSelect, CompositionModeFromKeyboard());
            }
            else
            {
                // Make sure the clicked block is always selected before dragging.
                if (!block.IsSelected)
                {
                    var compositionMode = CompositionModeFromKeyboard();
                    if (compositionMode == CompositionMode.Subtractive)
                    {
                        compositionMode = CompositionMode.None;
                    }
                    sequencer.SelectBlock(block, compositionMode);
                }

                // record initial information
                dragMode  = mode;
                dragStart = e.GetPosition(timeline); // always relative to timeline
                dragNeedsToOvercomeThreshold = true;
                dragTrackBaseline            = GetTrackIndexFromOffset(dragStart.Y);
                draggedBlocks = sequencer.SelectedBlocks.Select(b => new DraggedBlockData {
                    block = b, initialDuration = b.Duration, initialStartTime = b.StartTime
                }).ToList();

                controlBlock.CaptureMouse();
                e.Handled = true;
            }

            //control.Focus();
        }