// Describe a control point.
        private static TextPart[] DescribeControlPoint(SymbolDB symbolDB, EventDB eventDB, Id <ControlPoint> controlId, DescKind descKind)
        {
            Debug.Assert(descKind == DescKind.DescPane || descKind == DescKind.Tooltip);

            List <TextPart> list    = new List <TextPart>();
            ControlPoint    control = eventDB.GetControl(controlId);

            // Control name/code.
            list.Add(new TextPart(TextFormat.Title, Util.ControlPointName(eventDB, controlId, NameStyle.Long)));

            // Control location.
            if (descKind == DescKind.DescPane)
            {
                list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Location + "  "));
                list.Add(new TextPart(TextFormat.SameLine, string.Format("({0:##0.0}, {1:##0.0})", control.location.X, control.location.Y)));
            }

            // Which courses is it used in?
            list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.UsedIn : SelectionDescriptionText.UsedInCourses)));
            Id <Course>[] coursesUsingControl = QueryEvent.CoursesUsingControl(eventDB, controlId);
            list.Add(new TextPart(descKind == DescKind.Tooltip ? TextFormat.SameLine : TextFormat.NewLine, CourseListText(eventDB, coursesUsingControl)));

            // What is the competitor load?
            int load   = QueryEvent.GetControlLoad(eventDB, controlId);
            int visits = QueryEvent.GetControlVisitLoad(eventDB, controlId);

            if (load >= 0)
            {
                list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.Load : SelectionDescriptionText.CompetitorLoad)));
                if (visits != load)
                {
                    list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}/{1}", load, visits)));
                }
                else
                {
                    list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", load)));
                }
            }

            // Text version of the descriptions
            if (descKind == DescKind.DescPane)
            {
                Textifier textifier = new Textifier(eventDB, symbolDB, QueryEvent.GetDescriptionLanguage(eventDB));
                string    descText  = textifier.CreateTextForControl(controlId, null);
                list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.TextDescription));
                list.Add(new TextPart(TextFormat.NewLine, descText));
            }

            return(list.ToArray());
        }
        // Get a directive line for a marked route (not to the finish). The legId must be valid, because a marked route only occurs
        // with a real leg id.
        private DescriptionLine GetMarkedRouteLine(CourseView.ControlView controlViewFrom, CourseView.ControlView controlViewTo, Id <Leg> legId)
        {
            Leg leg = eventDB.GetLeg(legId);

            Debug.Assert(leg.flagging != FlaggingKind.None && leg.flagging != FlaggingKind.End);

            DescriptionLine line = new DescriptionLine();

            line.kind  = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Figure out the distance in the directive, rounded to nearest 10m.
            string distanceText;
            float  distance = QueryEvent.ComputeFlaggedLegLength(eventDB, leg.controlId1, leg.controlId2, legId);

            distance     = (float)(Math.Round(distance / 10.0) * 10.0);   // round to nearest 10 m.
            distanceText = string.Format("{0} m", distance);

            // Box 1: directive graphics.
            string symbolId = (leg.flagging == FlaggingKind.Begin) ? "13.1" : "13.2";

            line.boxes[0] = symbolDB[symbolId];

            // Box 2: distance of the flagging
            line.boxes[1] = distanceText;

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);

            line.textual = textifier.CreateTextForDirective(symbolId, distanceText);

            // The course control IDs, for use in coordinating the selection
            line.isLeg            = true;
            line.controlId        = controlViewFrom.controlId;
            line.courseControlId  = controlViewFrom.courseControlIds[0];
            line.courseControlId2 = controlViewTo.courseControlIds[0];

            return(line);
        }
        // Get a directive line for a flagged route to map exchange (not to the finish). The distance between the controls is calculated and used for the distance
        // in the direction.
        private DescriptionLine GetMapExchangeLine(CourseView.ControlView controlViewFrom, CourseView.ControlView controlViewTo)
        {
            DescriptionLine line = new DescriptionLine();

            line.kind  = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Figure out the distance in the directive, rounded to nearest 10m.
            float  distance;      // default distance is zero.
            string distanceText;

            distance     = controlViewFrom.legLength[0];
            distance     = (float)(Math.Round(distance / 10.0) * 10.0);   // round to nearest 10 m.
            distanceText = string.Format("{0} m", distance);

            // Box 1: directive graphics.
            string symbolId = "13.5";

            line.boxes[0] = symbolDB[symbolId];

            // Box 2: distance of the flagging
            line.boxes[1] = distanceText;

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);

            line.textual = textifier.CreateTextForDirective(symbolId, distanceText);

            // The course control IDs, for use in coordinating the selection
            line.isLeg            = true;
            line.controlId        = controlViewFrom.controlId;
            line.courseControlId  = controlViewFrom.courseControlIds[0];
            line.courseControlId2 = controlViewTo.courseControlIds[0];

            return(line);
        }
        // Describe a control point.
        private static TextPart[] DescribeControlPoint(SymbolDB symbolDB, EventDB eventDB, Id<ControlPoint> controlId, DescKind descKind)
        {
            Debug.Assert(descKind == DescKind.DescPane || descKind == DescKind.Tooltip);

            List<TextPart> list = new List<TextPart>();
            ControlPoint control = eventDB.GetControl(controlId);

            // Control name/code.
            list.Add(new TextPart(TextFormat.Title, Util.ControlPointName(eventDB, controlId, NameStyle.Long)));

            // Control location.
            if (descKind == DescKind.DescPane) {
                list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Location + "  "));
                list.Add(new TextPart(TextFormat.SameLine, string.Format("({0:##0.0}, {1:##0.0})", control.location.X, control.location.Y)));
            }

            // Which courses is it used in?
            list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.UsedIn : SelectionDescriptionText.UsedInCourses)));
            Id<Course>[] coursesUsingControl = QueryEvent.CoursesUsingControl(eventDB, controlId);
            list.Add(new TextPart(descKind == DescKind.Tooltip ? TextFormat.SameLine : TextFormat.NewLine, CourseListText(eventDB, coursesUsingControl)));

            // What is the competitor load?
            int load = QueryEvent.GetControlLoad(eventDB, controlId);
            if (load >= 0) {
                list.Add(new TextPart(TextFormat.Header, (descKind == DescKind.Tooltip ? SelectionDescriptionText.Load : SelectionDescriptionText.CompetitorLoad)));
                list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", load)));
            }

            // Text version of the descriptions
            if (descKind == DescKind.DescPane) {
                Textifier textifier = new Textifier(eventDB, symbolDB, QueryEvent.GetDescriptionLanguage(eventDB));
                string descText = textifier.CreateTextForControl(controlId, null);
                list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.TextDescription));
                list.Add(new TextPart(TextFormat.NewLine, descText));
            }

            return list.ToArray();
        }
        // Get a directive line for a finish or crossingpoint.
        private DescriptionLine GetDirectiveLine(CourseView.CourseViewKind kind, CourseView.ControlView controlView, CourseView.ControlView controlViewPrev)
        {
            ControlPoint  control = eventDB.GetControl(controlView.controlId);
            CourseControl courseControl;

            if (controlView.courseControlIds[0].IsNone)
            {
                courseControl = null;
            }
            else
            {
                courseControl = eventDB.GetCourseControl(controlView.courseControlIds[0]);
            }

            Debug.Assert(control.kind == ControlPointKind.Finish || control.kind == ControlPointKind.CrossingPoint || control.kind == ControlPointKind.MapIssue);

            DescriptionLine line = new DescriptionLine();

            line.kind  = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Figure out the distance in the directive, rounded to nearest 10m.
            float  distance = float.NaN;
            string distanceText;

            if (control.kind == ControlPointKind.MapIssue)
            {
                if (controlView.legLength != null)
                {
                    distance = controlView.legLength[0];
                }
            }
            else
            {
                if (controlViewPrev != null && controlViewPrev.legLength != null)
                {
                    distance = controlViewPrev.legLength[0];
                }
            }

            if (!float.IsNaN(distance))
            {
                distance     = (float)(Math.Round(distance / 10.0) * 10.0);  // round to nearest 10 m.
                distanceText = string.Format("{0} m", distance);
            }
            else
            {
                distanceText = "";
            }


            // Box 1: directive graphics.
            string directiveId = control.symbolIds[0];

            // Based on the leg flagging, we may modify the finish directive symbol.
            if (control.kind == ControlPointKind.Finish && (kind == CourseView.CourseViewKind.Normal || kind == CourseView.CourseViewKind.AllVariations))
            {
                FlaggingKind flagging = FlaggingKind.None;
                if (controlView != null && controlViewPrev != null)
                {
                    flagging = QueryEvent.GetLegFlagging(eventDB, controlViewPrev.controlId, controlView.controlId);
                }
                if (flagging == FlaggingKind.All)
                {
                    directiveId = "14.1";  // If flagging is All, then finish id must be flagging to finish.
                }
                else if (flagging == FlaggingKind.End)
                {
                    directiveId = "14.2";  // If flagging is Partial, then finish id must be flagging to funnel.
                }
            }

            line.boxes[0] = symbolDB[directiveId];

            // Box 2: distance for the control, if any.
            if (control.kind == ControlPointKind.Finish || control.kind == ControlPointKind.MapIssue)
            {
                line.boxes[1] = distanceText;
            }

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);

            line.textual = textifier.CreateTextForDirective(directiveId, distanceText);

            // The course control ID, for use in coordinating the selection
            line.controlId       = controlView.controlId;
            line.courseControlId = controlView.courseControlIds[0];

            return(line);
        }
        // Get a regular 8-box line for a start or regular control.
        private DescriptionLine GetRegularLine(CourseView.CourseViewKind kind, int scoreColumn, CourseView.ControlView controlView, Dictionary <string, string> descriptionKey)
        {
            Event         ev      = eventDB.GetEvent();
            ControlPoint  control = eventDB.GetControl(controlView.controlId);
            CourseControl courseControl;

            if (controlView.courseControlIds[0].IsNone)
            {
                courseControl = null;
            }
            else
            {
                courseControl = eventDB.GetCourseControl(controlView.courseControlIds[0]);
            }

            Debug.Assert(control.kind == ControlPointKind.Normal || control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange);

            DescriptionLine line = new DescriptionLine();

            line.kind  = DescriptionLineKind.Normal;
            line.boxes = new object[8];

            // Box A: ordinal or start triangle or points.
            if (control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange)
            {
                line.boxes[0] = symbolDB["start"];
            }
            else if (kind != CourseView.CourseViewKind.AllControls && controlView.ordinal > 0)
            {
                line.boxes[0] = Convert.ToString(controlView.ordinal);
            }
            else
            {
                line.boxes[0] = null;
            }

            // Box B: code of the control
            if (control.kind == ControlPointKind.Normal)
            {
                line.boxes[1] = Convert.ToString(control.code);
            }

            // Boxes C-H, from the symbols
            for (int i = 2; i < 8; ++i)
            {
                String symbolID = control.symbolIds[i - 2];
                if (symbolID != null)
                {
                    line.boxes[i] = symbolDB[control.symbolIds[i - 2]];

                    // See if we need to add this to the key.
                    bool addToKey;
                    if (ev.customSymbolKey.TryGetValue(symbolID, out addToKey) && addToKey && Symbol.ContainsLanguage(ev.customSymbolText[symbolID], language))
                    {
                        descriptionKey[symbolID] = Symbol.GetBestSymbolText(symbolDB, ev.customSymbolText[symbolID], language, false, "", "");
                    }
                }
            }

            // Box F -- may be text instead of a symbol.
            if (control.columnFText != null)
            {
                Debug.Assert(line.boxes[5] == null);
                line.boxes[5] = control.columnFText;
            }

            // Put points in the score column, for a score course.
            if (control.kind == ControlPointKind.Normal && scoreColumn >= 0 && courseControl != null)
            {
                int points = courseControl.points;
                if (points > 0)
                {
                    line.boxes[scoreColumn] = Convert.ToString(courseControl.points);
                }
                else
                {
                    line.boxes[scoreColumn] = null;
                }
            }

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);

            line.textual = textifier.CreateTextForControl(controlView.controlId, "");

            // The course control ID, for use in coordinating the selection
            line.controlId       = controlView.controlId;
            line.courseControlId = controlView.courseControlIds[0];

            return(line);
        }
        // Get a regular 8-box line for a start or regular control.
        private DescriptionLine GetRegularLine(CourseView.CourseViewKind kind, int scoreColumn, CourseView.ControlView controlView, Dictionary<string, string> descriptionKey)
        {
            Event ev = eventDB.GetEvent();
            ControlPoint control = eventDB.GetControl(controlView.controlId);
            CourseControl courseControl;

            if (controlView.courseControlIds[0].IsNone)
                courseControl = null;
            else
                courseControl = eventDB.GetCourseControl(controlView.courseControlIds[0]);

            Debug.Assert(control.kind == ControlPointKind.Normal || control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange);

            DescriptionLine line = new DescriptionLine();
            line.kind = DescriptionLineKind.Normal;
            line.boxes = new object[8];

            // Box A: ordinal or start triangle or points.
            if (control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange)
                line.boxes[0] = symbolDB["start"];
            else if (kind != CourseView.CourseViewKind.AllControls && controlView.ordinal > 0)
                line.boxes[0] = Convert.ToString(controlView.ordinal);
            else
                line.boxes[0] = null;

            // Box B: code of the control
            if (control.kind == ControlPointKind.Normal)
                line.boxes[1] = Convert.ToString(control.code);

            // Boxes C-H, from the symbols
            for (int i = 2; i < 8; ++i) {
                String symbolID = control.symbolIds[i - 2];
                if (symbolID != null) {
                    line.boxes[i] = symbolDB[control.symbolIds[i - 2]];

                    // See if we need to add this to the key.
                    bool addToKey;
                    if (ev.customSymbolKey.TryGetValue(symbolID, out addToKey) && addToKey && Symbol.ContainsLanguage(ev.customSymbolText[symbolID], language)) {
                        descriptionKey[symbolID] = Symbol.GetBestSymbolText(symbolDB, ev.customSymbolText[symbolID], language, false, "", "");
                    }
                }
            }

            // Box F -- may be text instead of a symbol.
            if (control.columnFText != null) {
                Debug.Assert(line.boxes[5] == null);
                line.boxes[5] = control.columnFText;
            }

            // Put points in the score column, for a score course.
            if (control.kind == ControlPointKind.Normal && scoreColumn >= 0 && courseControl != null) {
                int points = courseControl.points;
                if (points > 0)
                    line.boxes[scoreColumn] = Convert.ToString(courseControl.points);
                else
                    line.boxes[scoreColumn] = null;
            }

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);
            line.textual = textifier.CreateTextForControl(controlView.controlId, "");

            // The course control ID, for use in coordinating the selection
            line.controlId = controlView.controlId;
            line.courseControlId = controlView.courseControlIds[0];

            return line;
        }
        // Get a directive line for a marked route (not to the finish). The legId must be valid, because a marked route only occurs
        // with a real leg id.
        private DescriptionLine GetMarkedRouteLine(CourseView.ControlView controlViewFrom, CourseView.ControlView controlViewTo, Id<Leg> legId)
        {
            Leg leg = eventDB.GetLeg(legId);

            Debug.Assert(leg.flagging != FlaggingKind.None && leg.flagging != FlaggingKind.End);

            DescriptionLine line = new DescriptionLine();
            line.kind = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Figure out the distance in the directive, rounded to nearest 10m.
            string distanceText;
            float distance = QueryEvent.ComputeFlaggedLegLength(eventDB, leg.controlId1, leg.controlId2, legId);
            distance = (float) (Math.Round(distance / 10.0) * 10.0);      // round to nearest 10 m.
            distanceText = string.Format("{0} m", distance);

            // Box 1: directive graphics.
            string symbolId = (leg.flagging == FlaggingKind.Begin) ? "13.1" : "13.2";
            line.boxes[0] = symbolDB[symbolId];

            // Box 2: distance of the flagging
            line.boxes[1] = distanceText;

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);
            line.textual = textifier.CreateTextForDirective(symbolId, distanceText);

            // The course control IDs, for use in coordinating the selection
            line.isLeg = true;
            line.controlId = controlViewFrom.controlId;
            line.courseControlId = controlViewFrom.courseControlIds[0];
            line.courseControlId2 = controlViewTo.courseControlIds[0];

            return line;
        }
        // Get a directive line for a flagged route to map exchange (not to the finish). The distance between the controls is calculated and used for the distance
        // in the direction.
        private DescriptionLine GetMapExchangeLine(CourseView.ControlView controlViewFrom, CourseView.ControlView controlViewTo)
        {
            DescriptionLine line = new DescriptionLine();
            line.kind = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Figure out the distance in the directive, rounded to nearest 10m.
            float distance;       // default distance is zero.
            string distanceText;
            distance = controlViewFrom.legLength[0];
            distance = (float) (Math.Round(distance / 10.0) * 10.0);      // round to nearest 10 m.
            distanceText = string.Format("{0} m", distance);

            // Box 1: directive graphics.
            string symbolId = "13.5";
            line.boxes[0] = symbolDB[symbolId];

            // Box 2: distance of the flagging
            line.boxes[1] = distanceText;

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);
            line.textual = textifier.CreateTextForDirective(symbolId, distanceText);

            // The course control IDs, for use in coordinating the selection
            line.isLeg = true;
            line.controlId = controlViewFrom.controlId;
            line.courseControlId = controlViewFrom.courseControlIds[0];
            line.courseControlId2 = controlViewTo.courseControlIds[0];

            return line;
        }
        // Get a directive line for a map exchange at a control (not to the finish).
        private DescriptionLine GetMapExchangeAtControlLine(CourseView.ControlView controlWithExchange)
        {
            DescriptionLine line = new DescriptionLine();
            line.kind = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Distance is 0m at the control!
            string distanceText = string.Format("{0} m", 0);

            // Box 1: directive graphics.
            string symbolId = "13.5control";
            line.boxes[0] = symbolDB[symbolId];

            // Box 2: distance of the flagging
            line.boxes[1] = distanceText;

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);
            line.textual = textifier.CreateTextForDirective(symbolId, distanceText);

            // The course control IDs, for use in coordinating the selection
            line.controlId = controlWithExchange.controlId;
            line.courseControlId = controlWithExchange.courseControlIds[0];

            return line;
        }
        // Get a directive line for a finish or crossingpoint.
        private DescriptionLine GetDirectiveLine(CourseView.CourseViewKind kind, CourseView.ControlView controlView, CourseView.ControlView controlViewPrev)
        {
            ControlPoint control = eventDB.GetControl(controlView.controlId);
            CourseControl courseControl;

            if (controlView.courseControlIds[0].IsNone)
                courseControl = null;
            else
                courseControl = eventDB.GetCourseControl(controlView.courseControlIds[0]);

            Debug.Assert(control.kind == ControlPointKind.Finish || control.kind == ControlPointKind.CrossingPoint);

            DescriptionLine line = new DescriptionLine();
            line.kind = DescriptionLineKind.Directive;
            line.boxes = new object[2];

            // Figure out the distance in the directive, rounded to nearest 10m.
            string distanceText;
            if (controlViewPrev != null && controlViewPrev.legLength != null) {
                float distance = controlViewPrev.legLength[0];
                distance = (float)(Math.Round(distance / 10.0) * 10.0);      // round to nearest 10 m.
                distanceText = string.Format("{0} m", distance);
            }
            else
                distanceText = "";

            // Box 1: directive graphics.
            line.boxes[0] = symbolDB[control.symbolIds[0]];

            // Box 2: distance for the control, if any.
            if (control.kind == ControlPointKind.Finish)
                line.boxes[1] = distanceText;

            // Get the text version of the control using the Textifier.
            Textifier textifier = new Textifier(eventDB, symbolDB, language);
            line.textual = textifier.CreateTextForControl(controlView.controlId, distanceText);

            // The course control ID, for use in coordinating the selection
            line.controlId = controlView.controlId;
            line.courseControlId = controlView.courseControlIds[0];

            return line;
        }