// type may be null, then the target will be auto-inferred based on currently selected blocks
        public void ConvertSelectedBlocksTo(string type)
        {
            bool rampsToColors = (type == "color");
            bool colorsToRamps = (type == "ramp");

            if (!rampsToColors && !colorsToRamps)
            {
                colorsToRamps = SelectedBlocks.Any(b => b is ColorBlockViewModel);
                rampsToColors = SelectedBlocks.Any(b => b is RampBlockViewModel);
                if (colorsToRamps && rampsToColors)
                {
                    throw new ArgumentException("type needs to be given when both color and ramp blocks are selected");
                }
            }

            using (ActionManager.CreateTransaction())
            {
                for (int i = 0; i < SelectedBlocks.Count; i++)
                {
                    var   block = SelectedBlocks[i].GetModel();
                    Block convertedBlock;
                    if (rampsToColors && block is RampBlock rampBlock)
                    {
                        convertedBlock = new ColorBlock(model, rampBlock.Tracks.ToArray())
                        {
                            Color = (rampBlock.StartColor == GloColor.Black ? rampBlock.EndColor : rampBlock.StartColor)
                        };
                    }
                    else if (colorsToRamps && block is ColorBlock colorBlock)
                    {
                        convertedBlock = new RampBlock(model, colorBlock.Tracks.ToArray())
                        {
                            StartColor = colorBlock.Color,
                            EndColor   = (colorBlock.Color == GloColor.White ? GloColor.Black : GloColor.White)
                        };
                    }
                    else
                    {
                        continue;
                    }

                    // copy generic properties
                    convertedBlock.StartTime      = block.StartTime;
                    convertedBlock.Duration       = block.Duration;
                    convertedBlock.SegmentContext = block.SegmentContext;

                    ActionManager.RecordReplace(model.Blocks, block, convertedBlock);
                    SelectedBlocks[i] = BlockViewModel.FromModel(this, convertedBlock);
                }
            }
        }
        public void InsertBlock(string type)
        {
            // inherit color and tracks from previous block, if applicable
            GloColor prevColor = GloColor.White;

            Track[] prevTracks = { _selectedTrack.GetModel() };

            if (EnableSmartInsert)
            {
                Func <BlockViewModel, bool> fnIsBlockApplicable =
                    (bl => bl.StartTime < CursorPosition && (bl is ColorBlockViewModel || bl is RampBlockViewModel));

                var prevBlocks = ((IEnumerable <BlockViewModel>)_selectedTrack.Blocks).Where(fnIsBlockApplicable);
                if (prevBlocks.Any())
                {
                    BlockViewModel prevBlock = prevBlocks.MaxBy(bl => bl.EndTimeOccupied);

                    // inherit color
                    if (prevBlock is ColorBlockViewModel)
                    {
                        prevColor = ((ColorBlockViewModel)prevBlock).GetModel().Color;
                    }
                    else
                    {
                        prevColor = ((RampBlockViewModel)prevBlock).GetModel().EndColor;
                    }

                    // inherit tracks, but only if the last block on the selected track is also the last block on all other tracks of the block
                    bool lastOfAllTracks = prevBlock.GetModel().Tracks.All(t => t.Blocks
                                                                           .Select(bl => BlockViewModel.FromModel(this, bl))
                                                                           .Where(fnIsBlockApplicable)
                                                                           .MaxBy(bl => bl.EndTimeOccupied)
                                                                           == prevBlock);
                    if (lastOfAllTracks)
                    {
                        prevTracks = prevBlock.GetModel().Tracks.ToArray();
                    }
                }
            }

            Block b;

            switch (type)
            {
            case "color":
                b = new ColorBlock(model, prevTracks)
                {
                    Color = prevColor
                };
                break;

            case "ramp":
                b = new RampBlock(model, prevTracks)
                {
                    StartColor = prevColor,
                    EndColor   = (prevColor == GloColor.White ? GloColor.Black : GloColor.White)
                };
                break;

            default:
                throw new ArgumentException("unsupported block type " + type);
            }

            b.SegmentContext = ActiveMusicSegment.GetModel();
            b.StartTime      = CursorPosition;
            b.Duration       = GridInterval;

            ActionManager.RecordAdd(model.Blocks, b);
        }