// Format the given CourseView into a bunch of course objects, and add it to the given course Layout public RectangleF FormatCourseToLayout(SymbolDB symbolDB, CourseView courseViewAllVariations, CourseView specificVariation, CourseLayout courseLayout, CourseLayer layerAllVariations, CourseLayer layerSpecificVariation) { this.eventDB = courseViewAllVariations.EventDB; this.symbolDB = symbolDB; this.courseLayout = courseLayout; this.courseLayerAllVariationsAndParts = layerAllVariations; this.courseLayerSpecificVariation = layerSpecificVariation; this.controlViewsAllVariationsAndParts = courseViewAllVariations.ControlViews; this.controlViewsSpecificVariation = specificVariation.ControlViews; this.controlPositions = new ControlPosition[controlViewsAllVariationsAndParts.Count]; this.courseControlIdsSpecificVariation = QueryEvent.EnumCourseControlIds(eventDB, specificVariation.CourseDesignator).ToArray(); this.variationMap = QueryEvent.GetVariantCodeMapping(eventDB, courseViewAllVariations.CourseDesignator); SizeF totalAbstractSize = AssignControlPositions(0, controlViewsAllVariationsAndParts.Count, 0, 0); // Now create objects now that the positions have been created. scaleRatio = 1.0F; appearance = new CourseAppearance(); for (int index = 0; index < controlViewsAllVariationsAndParts.Count; ++index) { CreateObjectsForControlView(controlViewsAllVariationsAndParts[index], controlPositions[index]); } PointF bottomCenter = LocationFromAbstractPosition(0, 0); SizeF size = SizeFromAbstractSize(totalAbstractSize); RectangleF rect = new RectangleF(bottomCenter.X - size.Width / 2, bottomCenter.Y - size.Height, size.Width, size.Height); rect.Inflate(widthUnit, heightUnit); return rect; }
// Get the description kind to use. private DescriptionKind GetDescriptionKind(CourseView courseView) { if (descPrintSettings.UseCourseDefault) { return QueryEvent.GetDefaultDescKind(eventDB, courseView.BaseCourseId); } else { return descPrintSettings.DescKind; } }
// Get a punch pattern renderer for rendering the description from a course view. private PunchesRenderer GetRenderer(CourseView courseView) { PunchcardFormat punchcardFormat = eventDB.GetEvent().punchcardFormat; PunchesRenderer renderer = new PunchesRenderer(eventDB); renderer.CellSize = punchPrintSettings.BoxSize / 0.254F; renderer.CourseView = courseView; renderer.PunchcardFormat = punchcardFormat; renderer.Margin = 0; return renderer; }
// Get the angle from the given control index to the next control. public static double ComputeAngleOut(EventDB eventDB, CourseView courseView, int controlIndex) { PointF pt1 = eventDB.GetControl(courseView.ControlViews[controlIndex].controlId).location; // Get index of next control. int nextControlIndex = courseView.GetNextControl(controlIndex); if (nextControlIndex < 0) return double.NaN; // By default, the location of the next control is the direction. PointF pt2 = eventDB.GetControl(courseView.ControlViews[nextControlIndex].controlId).location; // If there is a custom leg, then use the location of the first bend instead. Id<Leg> legId = QueryEvent.FindLeg(eventDB, courseView.ControlViews[controlIndex].controlId, courseView.ControlViews[nextControlIndex].controlId); if (legId.IsNotNone) { Leg leg = eventDB.GetLeg(legId); if (leg.bends != null && leg.bends.Length > 0) pt2 = leg.bends[0]; } return Math.Atan2(pt2.Y - pt1.Y, pt2.X - pt1.X); }
private void CreateLegBetweenControls(CourseView.ControlView controlView1, ControlPosition controlPosition1, CourseView.ControlView controlView2, ControlPosition controlPosition2, int splitLegIndex, ForkPosition forkStart) { PointF dropTargetPosition; SymPath path = PathBetweenControls(controlPosition1, controlPosition2, forkStart, out dropTargetPosition); CourseObj courseObj = new TopologyLegCourseObj(controlView1.controlId, controlView1.courseControlIds[splitLegIndex], controlView2.courseControlIds[0], scaleRatio, appearance, path); CourseLayer layer; if (LegInSpecificVariation(controlView1.courseControlIds[splitLegIndex], controlView2.courseControlIds[0])) layer = courseLayerSpecificVariation; else layer = courseLayerAllVariationsAndParts; courseObj.layer = layer; courseLayout.AddCourseObject(courseObj); // Add the drop target. courseObj = new TopologyDropTargetCourseObj(controlView1.controlId, controlView1.courseControlIds[splitLegIndex], controlView2.courseControlIds[0], scaleRatio, appearance, dropTargetPosition); courseObj.layer = layer; courseLayout.AddCourseObject(courseObj); if (forkStart != null && forkStart.variationCode != '\0') { // There is a variation fork. courseObj = CreateVariationCode(controlView1, controlPosition1, splitLegIndex, forkStart); courseLayout.AddCourseObject(courseObj); } }
private void CreateControlNumber(CourseView.ControlView controlView, ControlPosition controlPosition) { Id<ControlPoint> controlId = controlView.controlId; Id<CourseControl> courseControlId = controlView.courseControlIds[0]; ControlPoint control = eventDB.GetControl(controlId); PointF location = LocationFromAbstractPosition(controlPosition.x, controlPosition.y); CourseLayer layer; if (ControlViewInSpecificVariation(controlView)) layer = courseLayerSpecificVariation; else layer = courseLayerAllVariationsAndParts; CourseObj courseObj; switch (control.kind) { case ControlPointKind.Start: case ControlPointKind.MapExchange: // Triangle looks best if we displace it down a bit (0.8 looks right). courseObj = new StartCourseObj(controlId, courseControlId, scaleRatio * 0.75F, appearance, 0, new PointF(location.X, location.Y - 0.8F), CrossHairOptions.NoCrossHair); break; case ControlPointKind.Finish: courseObj = new FinishCourseObj(controlId, courseControlId, scaleRatio * 0.75F, appearance, null, location, CrossHairOptions.NoCrossHair); break; case ControlPointKind.Normal: courseObj = new ControlNumberCourseObj(controlId, courseControlId, scaleRatio, appearance, control.code, location); break; case ControlPointKind.CrossingPoint: courseObj = new CrossingCourseObj(controlId, courseControlId, Id<Special>.None, scaleRatio * 1.5F, appearance, 0, location); break; default: Debug.Fail("bad control kind"); return; } courseObj.layer = layer; courseLayout.AddCourseObject(courseObj); }
// Create the normal view onto a score course private static CourseView CreateScoreCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); CourseView courseView = new CourseView(eventDB, courseDesignator); Id<CourseControl> courseControlId; courseView.courseName = course.name; courseView.scoreColumn = course.scoreColumn; courseControlId = course.firstCourseControl; while (courseControlId.IsNotNone) { ControlView controlView = new ControlView(); CourseControl courseControl = eventDB.GetCourseControl(courseControlId); controlView.courseControlIds = new[] { courseControlId }; controlView.controlId = courseControl.control; // Ordinals assigned after sorting. controlView.ordinal = -1; controlView.joinIndex = -1; // Move to the next control. courseView.controlViews.Add(controlView); courseControlId = courseControl.nextCourseControl; } // Sort the control views: first by kind, then by score, then by code. courseView.controlViews.Sort(delegate(ControlView view1, ControlView view2) { ControlPoint control1 = eventDB.GetControl(view1.controlId); ControlPoint control2 = eventDB.GetControl(view2.controlId); CourseControl courseControl1 = eventDB.GetCourseControl(view1.courseControlIds[0]); CourseControl courseControl2 = eventDB.GetCourseControl(view2.courseControlIds[0]); if (control1.kind < control2.kind) return -1; else if (control1.kind > control2.kind) return 1; if (courseControl1.points != courseControl2.points) return courseControl1.points.CompareTo(courseControl2.points); int result = Util.CompareCodes(control1.code, control2.code); if (result != 0) return result; return view1.controlId.id.CompareTo(view2.controlId.id); }); // Assign ordinals, if applicable. If scores in column A, then no ordinals will be assigned. if (courseView.scoreColumn != 0) { int ordinal = course.firstControlOrdinal; foreach (ControlView control in courseView.controlViews) { if (eventDB.GetControl(control.controlId).kind == ControlPointKind.Normal) control.ordinal = ordinal++; } } courseView.Finish(); return courseView; }
// Count controls in a course view that match a predicate. private static string CountControls(CourseView courseView, Predicate<Id<ControlPoint>> predicate) { string desc = ""; int[] count = new int[6]; foreach (CourseView.ControlView controlView in courseView.ControlViews) { if (predicate(controlView.controlId)) { ControlPoint control = courseView.EventDB.GetControl(controlView.controlId); if (control.kind == ControlPointKind.Normal || control.kind == ControlPointKind.Start || control.kind == ControlPointKind.Finish || control.kind == ControlPointKind.CrossingPoint || control.kind == ControlPointKind.MapExchange) { count[(int) control.kind] += 1; } } } // Add the counts of each control kind to a string. if (count[(int) ControlPointKind.Normal] > 0) desc = AddCount(desc, count[(int) ControlPointKind.Normal], SelectionDescriptionText.Control_Singular, SelectionDescriptionText.Control_Plural); if (count[(int) ControlPointKind.Start] > 0) desc = AddCount(desc, count[(int) ControlPointKind.Start], SelectionDescriptionText.Start_Singular, SelectionDescriptionText.Start_Plural); if (count[(int) ControlPointKind.Finish] > 0) desc = AddCount(desc, count[(int) ControlPointKind.Finish], SelectionDescriptionText.Finish_Singular, SelectionDescriptionText.Finish_Plural); if (count[(int) ControlPointKind.CrossingPoint] > 0) desc = AddCount(desc, count[(int) ControlPointKind.CrossingPoint], SelectionDescriptionText.MandCrossing_Singular, SelectionDescriptionText.MandCrossing_Plural); if (count[(int) ControlPointKind.MapExchange] > 0) desc = AddCount(desc, count[(int) ControlPointKind.MapExchange], SelectionDescriptionText.MapExchange_Singular, SelectionDescriptionText.MapExchange_Plural); // If we didn't find anthing the count string will still be empty. if (desc == "") desc = SelectionDescriptionText.None; return desc; }
// 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; }
protected override void WriteCourseStart(CourseView courseView, string courseName, int courseNumber, string[] classNames, bool isScore, int variationNumber, VariationInfo variationInfo) { xmlWriter.WriteStartElement("Course"); if (variationInfo != null) { xmlWriter.WriteElementString("Name", variationInfo.CodeString); xmlWriter.WriteElementString("CourseFamily", courseName); } else { xmlWriter.WriteElementString("Name", courseName); } if (!isScore) { xmlWriter.WriteElementString("Length", XmlConvert.ToString(Math.Round(courseView.MaxTotalLength / 100F) * 100F)); // round to nearest 100m if (courseView.TotalClimb > 0) xmlWriter.WriteElementString("Climb", XmlConvert.ToString(Math.Round(courseView.TotalClimb / 5, MidpointRounding.AwayFromZero) * 5.0)); // round to nearest 5m } }
// 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; }
protected override void WriteCourseControl(ControlPointKind kind, CourseView.ControlView controlView, bool isScore, ref int sequenceNumber, ref float distanceThisLeg) { switch (kind) { case ControlPointKind.Start: xmlWriter.WriteElementString("StartPointCode", controlCodeMap[controlView.controlId]); break; case ControlPointKind.Finish: xmlWriter.WriteElementString("FinishPointCode", controlCodeMap[controlView.controlId]); if (!isScore) { xmlWriter.WriteElementString("DistanceToFinish", XmlConvert.ToString(Math.Round(distanceThisLeg))); distanceThisLeg = 0; } break; case ControlPointKind.MapExchange: case ControlPointKind.Normal: xmlWriter.WriteStartElement("CourseControl"); // With map exchanges, the sequence can be different than the ordinals. We always use the sequence. xmlWriter.WriteElementString("Sequence", XmlConvert.ToString(sequenceNumber++)); xmlWriter.WriteElementString("ControlCode", controlCodeMap[controlView.controlId]); if (!isScore) { xmlWriter.WriteElementString("LegLength", XmlConvert.ToString(Math.Round(distanceThisLeg))); distanceThisLeg = 0; } if (isScore) { int points = eventDB.GetCourseControl(controlView.courseControlIds[0]).points; if (points > 0) xmlWriter.WriteElementString("ScoreOPoints", XmlConvert.ToString(points)); } xmlWriter.WriteEndElement(); // "CourseControl" break; case ControlPointKind.CrossingPoint: // Intentionally skip crossing points. break; } }
protected override void WriteCourseControl(ControlPointKind kind, CourseView.ControlView controlView, bool isScore, ref int sequenceNumber, ref float distanceThisLeg) { xmlWriter.WriteStartElement("CourseControl"); switch (kind) { case ControlPointKind.Start: xmlWriter.WriteAttributeString("type", "Start"); break; case ControlPointKind.Finish: // UNDONE: special instruction for taped route xmlWriter.WriteAttributeString("type", "Finish"); if (eventDB.GetControl(controlView.controlId).symbolIds?[0] == "14.1") xmlWriter.WriteAttributeString("specialInstruction", "TapedRoute"); else if(eventDB.GetControl(controlView.controlId).symbolIds?[0] == "14.2") xmlWriter.WriteAttributeString("specialInstruction", "FunnelTapedRoute"); break; case ControlPointKind.MapExchange: xmlWriter.WriteAttributeString("type", "Start"); break; case ControlPointKind.Normal: xmlWriter.WriteAttributeString("type", "Control"); break; case ControlPointKind.CrossingPoint: xmlWriter.WriteAttributeString("type", "CrossingPoint"); if (eventDB.GetControl(controlView.controlId).symbolIds?[0] == "13.4") xmlWriter.WriteAttributeString("specialInstruction", "MandatoryOutOfBoundsAreaPassage"); else xmlWriter.WriteAttributeString("specialInstruction", "MandatoryCrossingPoint"); break; } xmlWriter.WriteElementString("Control", controlCodeMap[controlView.controlId]); if (!isScore && controlView.ordinal > 0) { xmlWriter.WriteElementString("MapText", XmlConvert.ToString(controlView.ordinal)); } if (!isScore && kind != ControlPointKind.Start) { xmlWriter.WriteElementString("LegLength", XmlConvert.ToString(Math.Round(distanceThisLeg))); distanceThisLeg = 0; } if (isScore && kind == ControlPointKind.Normal) { int points = eventDB.GetCourseControl(controlView.courseControlIds[0]).points; if (points > 0) xmlWriter.WriteElementString("Score", XmlConvert.ToString(points)); } xmlWriter.WriteEndElement(); // "CourseControl" }
protected abstract void WriteCourseStart(CourseView courseView, string courseName, int courseNumber, string[] classNames, bool isScore, int variationNumber, VariationInfo variationInfo);
protected abstract void WriteCourseControl(ControlPointKind kind, CourseView.ControlView controlView, bool isScore, ref int sequenceNumber, ref float distanceThisLeg);
// 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; }
private void CreateObjectsForControlView(CourseView.ControlView controlView, ControlPosition controlPosition) { CreateControlNumber(controlView, controlPosition); if (controlView.legTo != null) { for (int i = 0; i < controlView.legTo.Length; ++i) { ForkPosition forkStart; if (controlPosition.forkStart != null) forkStart = controlPosition.forkStart[i]; else forkStart = null; CreateLegBetweenControls(controlView, controlPosition, controlViewsAllVariationsAndParts[controlView.legTo[i]], controlPositions[controlView.legTo[i]], i, forkStart); } } }
private void WriteLegLengthTable(EventDB eventDB, CourseView courseView) { BeginTable("", 3, "leftalign", "leftalign", "rightalign"); WriteTableHeaderRow(ReportText.ColumnHeader_Leg, ReportText.ColumnHeader_Controls, ReportText.ColumnHeader_Length); // Go through the control views. int controlViewIndex = 0; float distanceThisLeg = 0; float totalLegs = 0; int legNumber = 1; Id<ControlPoint> controlIdPrev = Id<ControlPoint>.None; while (controlViewIndex >= 0 && controlViewIndex < courseView.ControlViews.Count) { CourseView.ControlView controlView = courseView.ControlViews[controlViewIndex]; ControlPointKind kind = eventDB.GetControl(controlView.controlId).kind; // Don't report crossing points. if (kind != ControlPointKind.CrossingPoint) { if (controlIdPrev.IsNotNone) { string legText = string.Format("{0}\u2013{1}", Util.ControlPointName(eventDB, controlIdPrev, NameStyle.Medium), Util.ControlPointName(eventDB, controlView.controlId, NameStyle.Medium)); WriteTableRow(Convert.ToString(legNumber), legText, string.Format("{0} m", Math.Round(distanceThisLeg))); totalLegs += distanceThisLeg; legNumber += 1; } controlIdPrev = controlView.controlId; distanceThisLeg = 0; } if (controlView.legLength != null) distanceThisLeg += controlView.legLength[0]; controlViewIndex = courseView.GetNextControl(controlViewIndex); } // Write average row if (legNumber > 1) { BeginTableRow("summaryrow"); WriteSpannedTableCell(2, ReportText.LegLength_Average); WriteTableCell(string.Format("{0} m", Convert.ToString(Math.Round(totalLegs / (float) (legNumber - 1))))); EndTableRow(); } EndTable(); }
private CourseObj CreateVariationCode(CourseView.ControlView controlView1, ControlPosition controlPosition1, int splitLegIndex, ForkPosition forkStart) { // Delta between position of fork start and position of code. float deltaX = (forkStart.x < controlPosition1.x) ? -0.4F : 0.4F; float deltaY = -0.4F; float x = forkStart.x + deltaX; float y = forkStart.y + deltaY; string text = "(" + forkStart.variationCode + ")"; CourseObj courseObj = new VariationCodeCourseObj(controlView1.controlId, controlView1.courseControlIds[splitLegIndex], scaleRatio, appearance, text, LocationFromAbstractPosition(x, y)); courseObj.layer = CourseLayer.AllVariations; return courseObj; }
// Describe a course. private static TextPart[] DescribeCourse(EventDB eventDB, CourseView activeCourseView) { List<TextPart> list = new List<TextPart>(); CourseDesignator courseDesignator = activeCourseView.CourseDesignator; // Course name if (courseDesignator.AllParts) { list.Add(new TextPart(TextFormat.Title, string.Format(SelectionDescriptionText.CourseName, activeCourseView.CourseName))); } else { list.Add(new TextPart(TextFormat.Title, string.Format(SelectionDescriptionText.CourseNameAndPart, activeCourseView.CourseName, courseDesignator.Part + 1))); } if (activeCourseView.Kind == CourseView.CourseViewKind.Normal || activeCourseView.Kind == CourseView.CourseViewKind.AllVariations) { // Course length if (!courseDesignator.AllParts) { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Length)); list.Add(new TextPart(TextFormat.SameLine, Util.GetLengthInKm(activeCourseView.PartLength, activeCourseView.PartLength, 2))); } else { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Length)); list.Add(new TextPart(TextFormat.SameLine, Util.GetLengthInKm(activeCourseView.MinTotalLength, activeCourseView.MaxTotalLength, 2))); // If the user specified a length, show both that length and the calculated length. if (activeCourseView.MinTotalLength != activeCourseView.MinMeasuredLength || activeCourseView.MaxTotalLength != activeCourseView.MaxMeasuredLength) { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.CalculatedLength)); list.Add(new TextPart(TextFormat.SameLine, Util.GetLengthInKm(activeCourseView.MinMeasuredLength, activeCourseView.MaxMeasuredLength, 2))); } } // Don't have climb for a single part of multi-part course. if (activeCourseView.TotalClimb >= 0 && courseDesignator.AllParts) { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.Climb + " ")); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0:#,###} m", Math.Round(activeCourseView.TotalClimb, MidpointRounding.AwayFromZero)))); } } else if (activeCourseView.Kind == CourseView.CourseViewKind.Score) { // Total controls list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.TotalControls + " ")); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", activeCourseView.TotalNormalControls))); if (activeCourseView.TotalScore > 0) { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.TotalScore + " ")); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", activeCourseView.TotalScore))); } } // What is the competitor load? int load = QueryEvent.GetCourseLoad(eventDB, activeCourseView.BaseCourseId); if (load >= 0) { list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.CompetitorLoad)); list.Add(new TextPart(TextFormat.SameLine, string.Format("{0}", load))); } return list.ToArray(); }
// Describe the selection, and return an array of TextParts for display in the UI. public static TextPart[] DescribeSelection(SymbolDB symbolDB, EventDB eventDB, CourseView activeCourseView, SelectionMgr.SelectionInfo selection) { if (selection.SelectionKind == SelectionMgr.SelectionKind.Key) { return DescribeKey(eventDB); } else if (selection.SelectionKind == SelectionMgr.SelectionKind.TextLine) { return DescribeTextLine(eventDB, selection.SelectedControl, selection.SelectedTextLineKind); } else if (selection.SelectionKind == SelectionMgr.SelectionKind.Control) { return DescribeControlPoint(symbolDB, eventDB, selection.SelectedControl, DescKind.DescPane); } else if (selection.SelectionKind == SelectionMgr.SelectionKind.Leg) { return DescribeLeg(eventDB, selection.SelectedCourseControl, selection.SelectedCourseControl2, DescKind.DescPane); } else if (selection.SelectionKind == SelectionMgr.SelectionKind.Special) { return DescribeSpecial(eventDB, selection.SelectedSpecial, activeCourseView.ScaleRatio, DescKind.DescPane); } else if (selection.SelectionKind == SelectionMgr.SelectionKind.MapExchangeAtControl) { return DescribeMapExchangeAtControl(eventDB, selection.SelectedControl); } else if (selection.ActiveCourseDesignator.IsNotAllControls) { return DescribeCourse(eventDB, activeCourseView); } else { return DescribeAllControls(eventDB, activeCourseView); } }
// Create an filtered All Controls view -- show controls from the control collection, but only includes some. // excludedCourses contains an array of course ids to excluded from the contgrols. // kindFilter, if non-null, limits the controls to this kind of controls. public static CourseView CreateFilteredAllControlsView(EventDB eventDB, CourseDesignator[] excludedCourses, ControlPointKind kindFilter, bool addSpecials, bool addDescription) { CourseView courseView = new CourseView(eventDB, CourseDesignator.AllControls); courseView.courseName = MiscText.AllControls; courseView.scoreColumn = -1; // Add every control to the course view, subject to the filters. foreach (Id<ControlPoint> controlId in eventDB.AllControlPointIds) { ControlPoint control = eventDB.GetControl(controlId); // Check if the control is filtered out. if (excludedCourses != null) { // Filter excluded courses. foreach (CourseDesignator excludedCourseDesignator in excludedCourses) { if (QueryEvent.CourseUsesControl(eventDB, excludedCourseDesignator, controlId)) goto SKIP; } } if (kindFilter != ControlPointKind.None) { // Filter on control type. if (control.kind != kindFilter) goto SKIP; } // We are going to include this control in the collection. ControlView controlView = new ControlView(); controlView.courseControlIds = new[] { Id<CourseControl>.None }; controlView.controlId = controlId; // All controls doesn't have ordinals. controlView.ordinal = -1; controlView.joinIndex = -1; courseView.controlViews.Add(controlView); SKIP: ; } // Sort the control views: first by kind, then by code. courseView.controlViews.Sort((view1, view2) => QueryEvent.CompareControlIds(eventDB, view1.controlId, view2.controlId)); courseView.Finish(); if (addSpecials) { // Add every special, regardless of courses it is on, except for descriptions. Descriptions are added to all // controls only if they appear in all courses (or specifically for the all controls view), and if "addDescription" is true foreach (Id<Special> specialId in eventDB.AllSpecialIds) { Special special = eventDB.GetSpecial(specialId); if (special.kind == SpecialKind.Descriptions) { if (addDescription && QueryEvent.CourseContainsSpecial(eventDB, CourseDesignator.AllControls, specialId)) courseView.descriptionViews.Add(new DescriptionView(specialId, CourseDesignator.AllControls)); } else courseView.specialIds.Add(specialId); } } return courseView; }
// Describe the all controls. private static TextPart[] DescribeAllControls(EventDB eventDB, CourseView activeCourseView) { List<TextPart> list = new List<TextPart>(); // Course name list.Add(new TextPart(TextFormat.Title, activeCourseView.CourseName)); // Count controls in use. list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.ControlsInUse)); list.Add(new TextPart(TextFormat.NewLine, CountControls(activeCourseView, delegate(Id<ControlPoint> controlId) { return QueryEvent.CoursesUsingControl(eventDB, controlId).Length > 0; }))); // Count controls not in use. list.Add(new TextPart(TextFormat.Header, SelectionDescriptionText.ControlsNotInUse)); list.Add(new TextPart(TextFormat.NewLine, CountControls(activeCourseView, delegate(Id<ControlPoint> controlId) { return QueryEvent.CoursesUsingControl(eventDB, controlId).Length == 0; }))); return list.ToArray(); }
// 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; }
// Create the standard view onto a regular course, or a single variation of a variation course. private static CourseView CreateStandardCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); if (QueryEvent.HasVariations(eventDB, courseDesignator.CourseId) && courseDesignator.VariationInfo == null) throw new ApplicationException("Cannot create course view without specifying which variation"); // Get sub-part of the course. firstCourseControls is the first control to process, lastCourseControl is the last one to // process, or None if process to the end of the course. Id<CourseControl> firstCourseControl, lastCourseControl; if (courseDesignator.AllParts) { firstCourseControl = course.firstCourseControl; lastCourseControl = Id<CourseControl>.None; } else { QueryEvent.GetCoursePartBounds(eventDB, courseDesignator, out firstCourseControl, out lastCourseControl); } CourseView courseView = new CourseView(eventDB, courseDesignator); int ordinal; courseView.courseName = course.name; courseView.scoreColumn = -1; ordinal = 1; ordinal = course.firstControlOrdinal; // To get the ordinals correct, we get the course control ids for all parts. List<Id<CourseControl>> courseControls = QueryEvent.EnumCourseControlIds(eventDB, courseDesignator.WithAllParts()).ToList(); int index = 0; // Increase the ordinal value for each normal control before the first one we're considering. while (index < courseControls.Count && courseControls[index] != firstCourseControl) { CourseControl courseControl = eventDB.GetCourseControl(courseControls[index]); ControlPoint control = eventDB.GetControl(courseControl.control); if (control.kind == ControlPointKind.Normal) ++ordinal; ++index; } for (; index < courseControls.Count; ++index) { Id<CourseControl> courseControlId = courseControls[index]; ControlView controlView = new ControlView(); CourseControl courseControl = eventDB.GetCourseControl(courseControlId); ControlPoint control = eventDB.GetControl(courseControl.control); controlView.courseControlIds = new[] { courseControlId }; controlView.controlId = courseControl.control; // Set the ordinal number. if (control.kind == ControlPointKind.Normal) controlView.ordinal = ordinal++; else if (control.kind == ControlPointKind.Start || control.kind == ControlPointKind.MapExchange) controlView.ordinal = 0; else controlView.ordinal = -1; controlView.joinIndex = -1; // Don't show the map exchange for the next part at the end of this part. if (courseControlId == lastCourseControl && !courseDesignator.AllParts && control.kind == ControlPointKind.MapExchange) { controlView.hiddenControl = true; } // Set the legTo array with the next courseControlID. This is later updated // to the indices. if (index < courseControls.Count - 1 && courseControlId != lastCourseControl) { Id<CourseControl> nextCourseControl = courseControls[index + 1]; controlView.legTo = new int[1] { nextCourseControl.id }; // legTo initially holds course control ids, later changed. } // Add the controlview. courseView.controlViews.Add(controlView); if (courseControlId == lastCourseControl) break; } // If this is a part that should also have the finish on it, and it isn't the last part, then // add the finish. if (courseDesignator.IsNotAllControls && !courseDesignator.AllParts && courseDesignator.Part != QueryEvent.CountCourseParts(eventDB, courseDesignator.CourseId) - 1 && QueryEvent.GetPartOptions(eventDB, courseDesignator).ShowFinish) { if (QueryEvent.HasFinishControl(eventDB, courseDesignator.CourseId)) courseView.extraCourseControls.Add(QueryEvent.LastCourseControl(eventDB, courseDesignator.CourseId, false)); } courseView.Finish(); return courseView; }
CourseLayout CreateCourseLayout(CourseView courseView) { // Create the CourseLayout. CourseLayout courseLayout = new CourseLayout(); courseLayout.SetLayerColor(CourseLayer.Descriptions, NormalCourseAppearance.blackColorOcadId, NormalCourseAppearance.blackColorName, NormalCourseAppearance.blackColorC, NormalCourseAppearance.blackColorM, NormalCourseAppearance.blackColorY, NormalCourseAppearance.blackColorK, false); courseLayout.SetLayerColor(CourseLayer.MainCourse, NormalCourseAppearance.courseOcadId, NormalCourseAppearance.courseColorName, creationSettings.cyan, creationSettings.magenta, creationSettings.yellow, creationSettings.black, creationSettings.purpleOverprint); CourseFormatter.FormatCourseToLayout(symbolDB, courseView, courseAppearance, courseLayout, CourseLayer.MainCourse); return courseLayout; }
// Create the view of all variations of a course with variations. Cannot be a single part of a multi-part course. // Does not contain ordinals. private static CourseView CreateAllVariationsCourseView(EventDB eventDB, CourseDesignator courseDesignator) { Course course = eventDB.GetCourse(courseDesignator.CourseId); if (!courseDesignator.AllParts) throw new ApplicationException("Cannot create all variations of a single part"); CourseView courseView = new CourseView(eventDB, courseDesignator); courseView.courseName = course.name; courseView.scoreColumn = -1; // To get the ordinals correct, we get the course control ids for all parts. List<Id<CourseControl>> courseControls = QueryEvent.EnumCourseControlIds(eventDB, courseDesignator).ToList(); for (int index = 0; index < courseControls.Count; ++index) { Id<CourseControl> courseControlId = courseControls[index]; CourseControl courseControl = eventDB.GetCourseControl(courseControlId); // We add each split control only once, even though it has multiple variations. Check to see if we have already // handled it. bool alreadyHandled = false; if (courseControl.split) { foreach (ControlView cv in courseView.controlViews) { if (cv.courseControlIds.Contains(courseControlId)) alreadyHandled = true; } } if (!alreadyHandled) { ControlView controlView = new ControlView(); controlView.controlId = courseControl.control; // Set the ordinal number. All variations does not include an ordinal. controlView.ordinal = -1; // Set all course control ids associated with split control, or a single one for a non-split control. // Set the legTo array with the next courseControlID(s). This is later updated // to the indices. if (courseControl.split) { controlView.courseControlIds = QueryEvent.AllVariationsOfCourseControl(eventDB, courseControlId).ToArray(); if (courseControl.nextCourseControl.IsNotNone) { controlView.legTo = new int[controlView.courseControlIds.Length]; for (int i = 0; i < controlView.legTo.Length; ++i) { controlView.legTo[i] = eventDB.GetCourseControl(controlView.courseControlIds[i]).nextCourseControl.id; } } if (courseControl.loop) controlView.joinIndex = courseControlId.id; else controlView.joinIndex = courseControl.splitEnd.id; } else { controlView.courseControlIds = new[] { courseControlId }; if (courseControl.nextCourseControl.IsNotNone) controlView.legTo = new int[1] { courseControl.nextCourseControl.id }; // legTo initially holds course control ids, later changed. controlView.joinIndex = -1; } // Add the controlview. courseView.controlViews.Add(controlView); } } courseView.Finish(); return courseView; }
// Write a map to the given file name. void ExportMap(CourseView courseView, string outputFilename) { // Create the CourseLayout. CourseLayout courseLayout = CreateCourseLayout(courseView); // Create the map and write it out. Map map = courseLayout.RenderToMap(); using (map.Write()) { map.MapScale = courseView.MapScale; map.PrintScale = courseView.PrintScale; map.PrintArea = controller.GetCurrentPrintAreaRectangle(courseView.CourseDesignator); switch (controller.MapType) { case MapType.OCAD: // Set OCAD map as template. // OCAD 6 doesn't support another OCAD file as a template. if (! (creationSettings.fileFormat.kind == MapFileFormatKind.OCAD && creationSettings.fileFormat.version <= 6)) AddTemplateToMap(map, new TemplateInfo(controller.MapFileName, new PointF(0, 0), 0, 0, true)); // Use same real world coordinates as underlying map (nicer, but also works around bug in OCAD 11 // where background maps with real world coordinates aren't displayed if the map map doesn't have same real // world coordinates). map.RealWorldCoords = controller.MapRealWorldCoords; break; case MapType.Bitmap: case MapType.PDF: // Set bitmap as template. PointF centerPoint = Geometry.RectCenter(controller.MapDisplay.MapBounds); ImageFormat imageFormat; string mapFileName; float dpi; if (CreateBitmapFile()) { // Write a copy of the bitmap map. mapFileName = CreateBitmapFileName(out imageFormat); controller.MapDisplay.WriteBitmapMap(mapFileName, imageFormat, out dpi); } else { // Use existing map file. mapFileName = controller.MapFileName; dpi = controller.MapDpi; } AddTemplateToMap(map, new TemplateInfo(mapFileName, centerPoint, dpi, 0, true)); break; case MapType.None: break; default: Debug.Fail("Unexpected map type"); break; } } WriteImageBitmaps(map); InputOutput.WriteFile(outputFilename, map, creationSettings.fileFormat); }
// Update the names of all course views, and get the active course view, based on the active course id. void UpdateCourseViews() { List<KeyValuePair<Id<Course>,string>> courseViewPairs = new List<KeyValuePair<Id<Course>,string>>(); // Holds the list of course views and names, for sorting. Does NOT include all controls. // Get all the pairs of course ids in sorted order. Id<Course>[] courseIds = QueryEvent.SortedCourseIds(eventDB); // Copy to the names and ids arrays, adding in All Controls as the first element. courseViewNames = new string[courseIds.Length + 1]; courseViewIds = new Id<Course>[courseIds.Length + 1]; courseViewNames[0] = MiscText.AllControls; courseViewIds[0] = Id<Course>.None; for (int i = 1; i < courseViewIds.Length; ++i) { courseViewNames[i] = eventDB.GetCourse(courseIds[i - 1]).name; courseViewIds[i] = courseIds[i-1]; } // Figure out which course view is the active one. We have already validate that the active course id // is present. if (activeCourseDesignator.IsAllControls) { activeCourseViewIndex = 0; activeCourseView = CourseView.CreateViewingCourseView(eventDB, CourseDesignator.AllControls); } else { for (int i = 1; i < courseViewIds.Length; ++i) { if (courseViewIds[i] == activeCourseDesignator.CourseId) { activeCourseViewIndex = i; activeCourseView = CourseView.CreateViewingCourseView(eventDB, activeCourseDesignator); } } } // Get/create the topology course view. Not supported (null) for score and all controls. Always shows // all variations for a course with variations. if (activeCourseView.Kind == CourseView.CourseViewKind.Normal) { if (QueryEvent.HasVariations(activeCourseView.EventDB, activeCourseView.BaseCourseId) || !activeCourseView.CourseDesignator.AllParts) { topologyCourseView = CourseView.CreateViewingCourseView(eventDB, new CourseDesignator(activeCourseView.BaseCourseId)); } else { topologyCourseView = activeCourseView; } } else if (activeCourseView.Kind == CourseView.CourseViewKind.AllVariations) { topologyCourseView = activeCourseView; } else { topologyCourseView = null; } }
// Is the given control view in the specific variation. private bool ControlViewInSpecificVariation(CourseView.ControlView controlView) { if (controlViewsSpecificVariation == controlViewsAllVariationsAndParts) return true; foreach (Id<CourseControl> idCourseControl in controlView.courseControlIds) { if (courseControlIdsSpecificVariation.Contains(idCourseControl)) return true; } return false; }