public override bool CanPerform()
        {
            var prev = Row.Cells.LookupIndex(Cells.First().Index).Prev();

            // can't move if directly above a reference.
            if (prev != null && !string.IsNullOrEmpty(prev.Cell.Reference) && !prev.Cell.IsSelected)
            {
                return(false);
            }

            double increment = BeatCell.Parse(Increment) * Times;

            if (ShiftingRight)
            {
                if (Cells.Last().Index == Row.Cells.Count - 1)
                {
                    return(true);
                }

                if (Row.Cells.Count == 1)
                {
                    return(true);                      // can always move single cell right
                }
                var next = Row.Cells.LookupIndex(Cells.Last().Index).Next();

                return(next.Cell.Position > Cells.Last().Position + increment);
            }

            if (Cells.First().Index == 0)
            {
                return(Row.Offset >= increment);
            }


            return(prev.Cell.Position < Cells[0].Position - increment);
        }
 protected override void Transformation()
 {
     Group.FactorValue = Factor;
     Group.Factor      = BeatCell.Parse(Factor);
     ChangesViewWidth  = true;
 }
        public virtual void Undo()
        {
            // if no change, don't do anything
            if (AfterBeatCode == BeforeBeatCode)
            {
                return;
            }

            CellTree selectedCells = EditorViewController.Instance.DView.SelectedCells;

            // get current selection range if it's in this row
            int selectionStart = -1;
            int selectionEnd   = -1;

            // get selection indexes if there is a selection in the action's row
            if (selectedCells.Root != null && selectedCells.Root.Cell.Row == Row)
            {
                // find index of first selected cell
                selectionStart = selectedCells.Min.Cell.Index;

                selectionEnd = selectedCells.Max.Cell.Index;
            }

            bool selectFromBack = selectionEnd > RightIndexBoundOfTransform;

            if (selectFromBack)
            {
                // get index from back of list
                selectionStart = Row.Cells.Count - selectionStart;
                selectionEnd   = Row.Cells.Count - selectionEnd;
            }

            Row.FillFromBeatCode(BeforeBeatCode);
            if (BeforeOffset != AfterOffset)
            {
                Row.OffsetValue = BeforeOffset;
                Row.Offset      = BeatCell.Parse(BeforeOffset);
            }

            if (ChangesViewWidth)
            {
                double maxDur = DrawingView.Instance.Rows.Max(x => x.Duration);

                DrawingView.Instance.ResizeFrame(maxDur, false);
            }
            else
            {
                EditorViewController.Instance.DView.QueueRowToDraw(Row);

                RedrawReferencers();
            }

            EditorViewController.Instance.DView.ChangesApplied = false;

            // would be nice to only draw individual rows, but seems to be a problem
            //DrawingView.Instance.NeedsDisplay = true;


            if (selectionStart > -1)
            {
                if (selectFromBack)
                {
                    // convert back to forward indexed
                    selectionStart = Row.Cells.Count - selectionStart;
                    selectionEnd   = Row.Cells.Count - selectionEnd;
                }

                if (selectionStart < 0)
                {
                    selectionStart = 0;
                }
                if (selectionEnd >= Row.Cells.Count)
                {
                    selectionEnd = Row.Cells.Count - 1;
                }

                // make new selection
                CellTreeNode startNode = Row.Cells.LookupIndex(selectionStart);
                CellTreeNode endNode   = Row.Cells.LookupIndex(selectionEnd);

                EditorViewController.Instance.DView.SelectCell(startNode.Cell);

                if (startNode != endNode)
                {
                    EditorViewController.Instance.DView.SelectCell(endNode.Cell, true);
                }
            }
        }
        public virtual void Redo()
        {
            CellTree selectedCells = EditorViewController.Instance.DView.SelectedCells;

            // get current selection range if it's in this row
            int selectionStart  = -1;
            int selectionEnd    = -1;
            int rowLengthBefore = Row.Cells.Count;

            // get selection indexes if there is a selection in the action's row
            if (selectedCells.Root != null && selectedCells.Root.Cell.Row == Row)
            {
                // find index of first selected cell

                selectionStart = selectedCells.Min.Cell.Index;

                selectionEnd = selectedCells.Max.Cell.Index;
            }

            if (string.IsNullOrEmpty(AfterBeatCode))
            {
                // perform the transform and get the new beat code
                Transformation();

                AfterBeatCode = Row.Stringify();
                AfterOffset   = Row.OffsetValue;
            }

            bool selectFromBack = selectionEnd > RightIndexBoundOfTransform;

            if (selectFromBack)
            {
                // get index from back of list
                selectionStart = rowLengthBefore - selectionStart;
                selectionEnd   = rowLengthBefore - selectionEnd;
            }

            Row.FillFromBeatCode(AfterBeatCode);
            if (BeforeOffset != AfterOffset)
            {
                Row.OffsetValue = AfterOffset;
                Row.Offset      = BeatCell.Parse(AfterOffset);
            }

            if (ChangesViewWidth)
            {
                double maxDur = DrawingView.Instance.Rows.Max(x => x.Duration);

                // change the view's width
                //var curFrame = DrawingView.Instance.Frame;
                //curFrame.Width = (System.nfloat)(maxDur * DrawingView.ScalingFactor + 550);
                //DrawingView.Instance.Frame = curFrame;

                // need to draw the end portion of other rows
                if (maxDur == Row.Duration)
                {
                    DrawingView.Instance.ResizeFrame(maxDur);
                }
                else
                {
                    ChangesViewWidth = false;
                }
            }

            DrawingView.Instance.QueueRowToDraw(Row);

            DrawingView.Instance.ChangesApplied = false;
            RedrawReferencers();

            if (selectionStart > -1)
            {
                if (selectFromBack)
                {
                    // convert back to forward indexed
                    selectionStart = Row.Cells.Count - selectionStart;
                    selectionEnd   = Row.Cells.Count - selectionEnd;
                }

                if (selectionStart < 0)
                {
                    selectionStart = 0;
                }
                if (selectionEnd >= Row.Cells.Count)
                {
                    selectionEnd = Row.Cells.Count - 1;
                }

                // make new selection

                CellTreeNode startNode = Row.Cells.LookupIndex(selectionStart);
                CellTreeNode endNode   = Row.Cells.LookupIndex(selectionEnd);

                if (startNode != null && endNode != null)
                {
                    EditorViewController.Instance.DView.SelectCell(startNode.Cell);
                    if (startNode != endNode)
                    {
                        EditorViewController.Instance.DView.SelectCell(endNode.Cell, true);
                    }
                }
            }
        }
        public RemoveCells(CellTree cells) : base(cells.Root.Cell.Row, cells.Count > 1 ? "Remove Cells" : "Remove Cell")
        {
            Cells = cells;
            //Row = cells.Root.Cell.Row;
            //PreviousCellValue = previousCellValue;
            //Index = cells[0].Row.Cells.IndexOf(cells[0]);
            StartNode = Row.Cells.LookupIndex(cells.Min.Cell.Index);
            EndNode   = Row.Cells.LookupIndex(cells.Max.Cell.Index);

            StringBuilder duration = new StringBuilder();
            // find all groups that are encompassed by the selection
            HashSet <AbstractGroup> touchedGroups = new HashSet <AbstractGroup>();
            Repeat         groupBeingAppendedTo   = null;                 // a group who's LTM is actively being augmented
            Queue <Repeat> rgToAppendTo           = new Queue <Repeat>(); // RGs that may need to have their LTM added to

            //LinkedList<AbstractGroup> openedGroups = new LinkedList<AbstractGroup>();
            //LinkedList<AbstractGroup> closedGroups = new LinkedList<AbstractGroup>();

            foreach (Cell c in Cells)
            {
                //if (!string.IsNullOrEmpty(c.Reference)) continue;
                foreach ((bool begun, AbstractGroup group) in c.GroupActions)
                {
                    if (begun)
                    {
                        OpenedGroups.AddFirst(group);
                    }
                    else
                    {
                        ClosedGroups.AddFirst(group);
                    }
                }

                // add to the LTM of groups with a previous cell in the selection but not this cell
                if (rgToAppendTo.Any() && !c.RepeatGroups.Contains(rgToAppendTo.Peek()))
                {
                    groupBeingAppendedTo = rgToAppendTo.Dequeue();
                }
                if (groupBeingAppendedTo != null)
                {
                    groupBeingAppendedTo.LastTermModifier = BeatCell.Add(groupBeingAppendedTo.LastTermModifier, c.Value);
                }

                int times = 1;                 // times this cell gets repeated
                // track the times that each RG's LTM gets repeated
                Dictionary <Repeat, int> lcmTimes = new Dictionary <Repeat, int>();

                foreach (Repeat rg in c.RepeatGroups.Reverse())
                {
                    // remove cell from group
                    rg.ExclusiveCells.Remove(c);
                    rg.Cells.Remove(c);

                    // remove break cells
                    if (rg.BreakCell == c)
                    {
                        rg.BreakCell = null;
                    }

                    if (touchedGroups.Contains(rg))
                    {
                        continue;
                    }

                    rgToAppendTo.Enqueue(rg);
                    touchedGroups.Add(rg);

                    if (
                        (StartNode.Cell == rg.ExclusiveCells.First?.Value || rg.Position > StartNode.Cell.Position) &&
                        (EndNode.Cell == rg.ExclusiveCells.Last?.Value || rg.Position + rg.Length < EndNode.Cell.Position))
                    {
                        RepGroups.Add(rg);

                        bool cellAboveBreak = rg.BreakCell != null && c.Position > rg.BreakCell.Position;

                        times *= rg.Times - (cellAboveBreak ? 1 : 0);
                        // multiply all nested rgs' LTMs by this groups repeat times.
                        foreach (KeyValuePair <Repeat, int> kv in lcmTimes)
                        {
                            bool aboveBreak = rg.BreakCell != null && kv.Key.Position > rg.BreakCell.Position;

                            lcmTimes[kv.Key] *= rg.Times - (aboveBreak ? 1 : 0);
                        }
                        lcmTimes.Add(rg, 1);
                    }
                }

                foreach (Multiply mg in c.MultGroups)
                {
                    // remove cell from group
                    mg.ExclusiveCells.Remove(c);
                    if (touchedGroups.Contains(mg))
                    {
                        continue;
                    }
                    touchedGroups.Add(mg);
                    if (
                        (StartNode.Cell == mg.ExclusiveCells.First.Value || mg.Position > StartNode.Cell.Position) &&
                        (EndNode.Cell == mg.ExclusiveCells.Last.Value || mg.Position + mg.Length < EndNode.Cell.Position + EndNode.Cell.Duration))
                    {
                        MultGroups.Add(mg);
                    }
                }

                // get the double version of duration
                Duration += c.Duration * times;

                // get the string version of duration
                // add cell's repeat durations if this cell is in the same scope as the first cell.
                if ((!c.RepeatGroups.Any() && !StartNode.Cell.RepeatGroups.Any()) ||
                    c.RepeatGroups.Last?.Value == StartNode.Cell.RepeatGroups.Last?.Value)
                {
                    duration.Append("+0").Append(BeatCell.MultiplyTerms(c.Value, times));
                }
                // add any LTM's from repeat groups
                foreach (KeyValuePair <Repeat, int> kv in lcmTimes)
                {
                    duration.Append("+0").Append(BeatCell.MultiplyTerms(kv.Key.LastTermModifier, kv.Value));
                    Duration += BeatCell.Parse(kv.Key.LastTermModifier) * kv.Value;
                }
            }

            // Transfer group actions from deleted cells to the 2 cells outside the deleted group
            //CellTreeNode after = EndNode.Next();
            //CellTreeNode before = StartNode.Prev();
            //
            //if (after != null)
            //{
            //	foreach (AbstractGroup group in openedGroups)
            //	{
            //		if (group.ExclusiveCells.Count > 0)
            //		{
            //            after.Cell.GroupActions.AddFirst((true, group));
            //		}
            //	}
            //}
            //
            //if (before != null)
            //{
            //	foreach (AbstractGroup group in closedGroups)
            //	{
            //		if (group.ExclusiveCells.Count > 0)
            //		{
            //            before.Cell.GroupActions.AddFirst((false, group));
            //		}
            //	}
            //}

            BeatCodeDuration = BeatCell.SimplifyValue(duration.ToString());
        }