protected override void OnRender(DrawingContext drawingContext)
        {
            try
            {
                using (IsGraphDrawnScope isGraphDrawnScope = new IsGraphDrawnScope())
                {
                    if (RenderSize.Width < 10.0 || RenderSize.Height < 10.0)
                    {
                        return;
                    }

                    if (PBMCommand.Instance == null)
                    {
                        return;
                    }

                    DTE2 dte = (DTE2)PBMCommand.Instance.ServiceProvider.GetService(typeof(DTE));

                    if (dte == null)
                    {
                        return;
                    }

                    if (DataModel.Instance.AllProjectsCount == 0)
                    {
                        return;
                    }

                    int    linesCount  = DataModel.CurrentBuilds.Count + DataModel.FinishedBuilds.Count + 1 + 1 + 1 + 1; // 1 for header, 1 for status, 1 for CPU, 1 for HDD
                    double totalHeight = rowHeight * linesCount;

                    Height = totalHeight + penThickness;

                    long tickStep = 100000000;
                    long maxTick  = tickStep;
                    long nowTick  = DateTime.Now.Ticks;
                    long t        = nowTick - DataModel.StartTime.Ticks;
                    if (DataModel.CurrentBuilds.Count > 0)
                    {
                        if (t > maxTick)
                        {
                            maxTick = t;
                        }
                    }
                    int    ii;
                    bool   atLeastOneError   = false;
                    uint   maxBuildOrderNbr  = 1;
                    double projectNameMaxLen = 10;
                    for (ii = 0; ii < DataModel.FinishedBuilds.Count; ii++)
                    {
                        FormattedText iname = new FormattedText(DataModel.FinishedBuilds[ii].ProjectName, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush);
                        double        ll    = iname.Width;
                        t = DataModel.FinishedBuilds[ii].end;
                        atLeastOneError = atLeastOneError || !DataModel.FinishedBuilds[ii].success;
                        if (t > maxTick)
                        {
                            maxTick = t;
                        }

                        if (ll > projectNameMaxLen)
                        {
                            projectNameMaxLen = ll;
                        }

                        if (DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber > maxBuildOrderNbr)
                        {
                            maxBuildOrderNbr = DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber;
                        }
                    }
                    foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds)
                    {
                        FormattedText iname = new FormattedText(DataModel.GetHumanReadableProjectName(item.Key), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush);
                        double        ll    = iname.Width;
                        if (ll > projectNameMaxLen)
                        {
                            projectNameMaxLen = ll;
                        }

                        if (item.Value.Item1 > maxBuildOrderNbr)
                        {
                            maxBuildOrderNbr = item.Value.Item1;
                        }
                    }
                    if (isBuilding)
                    {
                        maxTick = (maxTick / tickStep + 1) * tickStep;
                    }

                    Spacings spacings = new Spacings();
                    { //setting spacings
                        string pattern = "> ";
                        int    len     = maxBuildOrderNbr.ToString().Length + pattern.Length;
                        pattern = pattern.PadLeft(len, '8'); // let's assume that 8 is the widest char
                        FormattedText bn = new FormattedText(pattern, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush);

                        spacings.lProjName = Spacings.lOrder + bn.Width + 3;                            // let's add 3 pix just in case 8 is not the widest
                        spacings.lGanttC   = spacings.lProjName + projectNameMaxLen + penThickness + 3; // let's add 3 pix just in case
                    }

                    int rowNbr = 0;                            //first row has number 0
                    {                                          // Draw header
                        DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                        string headerText = DataModel.GetSolutionNameWithMachineInfo("  |  ", false /*WithBuildStartedStr*/);
                        // Draw text
                        FormattedText itext = new FormattedText(headerText, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush);
                        {
                            // Cut text when it is too long for window. Probably correct solution is to add horizontal scrollbar to window.
                            // Set a maximum width and height. If the text overflows these values, an ellipsis "..." appears.
                            itext.MaxTextWidth  = RenderSize.Width - Spacings.lOrder - Spacings.rGanttC;
                            itext.MaxTextHeight = rowHeight;
                        }
                        drawingContext.DrawText(itext, new Point(Spacings.lOrder, rowNbr * rowHeight));

                        rowNbr++;
                    }

                    foreach (BuildInfo item in DataModel.FinishedBuilds)
                    {
                        DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                        DrawOrderAndProjectNameText(drawingContext, rowNbr, item.ProjectBuildOrderNumber, item.ProjectName, spacings, (item.success ? greenSolidBrush : redSolidBrush));
                        DrawBar(drawingContext, rowNbr, item.begin, item.end, maxTick, spacings, (item.success ? greenGradientBrush : redGradientBrush), DataModel.CriticalPath.Contains(item));

                        rowNbr++;
                    }

                    foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds)
                    {
                        DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                        DrawOrderAndProjectNameText(drawingContext, rowNbr, item.Value.Item1, DataModel.GetHumanReadableProjectName(item.Key), spacings, blueSolidBrush);
                        DrawBar(drawingContext, rowNbr, item.Value.Item2, (nowTick - DataModel.StartTime.Ticks), maxTick, spacings, blueGradientBrush, false /*markAsCriticalPath*/);

                        rowNbr++;
                    }

                    DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                    DrawGraph("CPU usage", drawingContext, DataModel.CpuUsage, cpuPen, cpuSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace);
                    rowNbr++;

                    DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                    DrawGraph("HDD usage", drawingContext, DataModel.HddUsage, hddPen, hddSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace);
                    rowNbr++;

                    if (DataModel.CurrentBuilds.Count > 0 || DataModel.FinishedBuilds.Count > 0)
                    {
                        string status = (isBuilding ? "Building..." : "Done");
                        if (DataModel.MaxParallelBuilds > 0)
                        {
                            status += " (" + DataModel.PercentageProcessorUse().ToString() + "% of " + DataModel.MaxParallelBuilds.ToString() + " CPUs)";
                        }

                        DrawText(drawingContext, status, rowNbr, Spacings.lOrder, blackBrush);
                    }

                    DrawVerticalSeparator(drawingContext, spacings.lGanttC - penThickness, rowNbr, false /*wholeSize*/);

                    drawingContext.DrawLine(new Pen(atLeastOneError ? redSolidBrush : greenSolidBrush, 1), new Point(0, rowNbr * rowHeight), new Point(RenderSize.Width, rowNbr * rowHeight));

                    DateTime      dt      = new DateTime(maxTick);
                    string        s       = Utils.SecondsToString(dt.Ticks);
                    FormattedText maxTime = new FormattedText(s, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush);
                    double        m       = maxTime.Width;
                    drawingContext.DrawText(maxTime, new Point(RenderSize.Width - m - Spacings.rGanttC, rowNbr * rowHeight));

                    {                                                                                                       // Draw frame around - it looks better in window and definitelly in saved .png. Draw frame as last to be clearly visible
                        DrawSeparator(drawingContext, -1);                                                                  // Top
                        DrawSeparator(drawingContext, rowNbr);                                                              // Bottom
                        DrawVerticalSeparator(drawingContext, 0, rowNbr, true /*wholeSize*/);                               // Left
                        DrawVerticalSeparator(drawingContext, RenderSize.Width - penThickness, rowNbr, true /*wholeSize*/); // Right
                    }

                    isGraphDrawnScope.IsDrawn = true;
                } //End of IsGraphDrawnScope
            }
            catch (Exception)
            {   // Keep this try{} catch{}!
                // Actually code in try{} bail from time to time on windows resize.
                // I guess because there is division by (x - y), while x equals y, but it might be not the only case.
                Debug.Assert(false, "Gantt chart not refreshed! Exception thrown while drawing Gantt chart.");
            }
        }
        private void DrawBar(DrawingContext drawingContext, int rowNbr, long startTime, long endTime, long maxTick, Spacings spacings, Brush color, bool markAsCriticalPath)
        {
            double pixelRange = RenderSize.Width - spacings.lGanttC - Spacings.rGanttC;

            if (pixelRange < minGanttWidth)
            {
                return;
            }

            Rect r = new Rect();

            // Why (int) and (long) is needed here? Let's try to simplify
            //r.X = spacings.lGanttC + (int)(startTime * (long)(pixelRange) / maxTick);
            //r.Width = spacings.lGanttC + (int)(endTime * (long)(pixelRange) / maxTick) - r.X;
            r.X     = spacings.lGanttC + (startTime * pixelRange / maxTick);
            r.Width = spacings.lGanttC + (endTime * pixelRange / maxTick) - r.X;
            if (r.Width == 0)
            {
                r.Width = 1;
            }

            r.Y      = rowNbr * rowHeight + 1;
            r.Height = rowHeight - 1;
            drawingContext.DrawRectangle(color, null, r); //Draw Gantt graph

            if (markAsCriticalPath)
            {
                Rect   cPR        = new Rect(r.Location, r.Size);
                double cPRLHeight = r.Height / 8;
                cPR.Height = cPRLHeight;

                drawingContext.DrawRectangle(criticalPathGradientBrush, null, cPR); //Draw top yellow line in Gantt graph

                cPR.Y += (rowHeight - cPRLHeight - penThickness);
                drawingContext.DrawRectangle(criticalPathGradientBrush, null, cPR); //Draw bottom yellow line in Gantt graph
            }

            string        time    = Utils.SecondsToString(endTime - startTime);
            FormattedText itime   = new FormattedText(time, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, whiteBrush);
            double        timeLen = itime.Width;

            if (r.Width > timeLen)
            {
                drawingContext.DrawText(itime, new Point(r.Right - timeLen, rowNbr * rowHeight)); //Write elapsed time
            }
        }
        void DrawGraph(string title, DrawingContext drawingContext, ReadOnlyCollection <Tuple <long, float> > data, Pen pen, Pen softPen, int rowNbr, Size RenderSize, double rowHeight, Spacings spacings, long maxTick, long nowTick, Typeface fontFace)
        {
            if (data.Count < 1)
            {
                return;
            }

            double pixelRange = RenderSize.Width - spacings.lGanttC - Spacings.rGanttC;

            if (pixelRange < minGanttWidth)
            {
                DrawText(drawingContext, title, rowNbr, Spacings.lOrder, blackBrush);
                return;
            }

            drawingContext.PushClip(new RectangleGeometry(new Rect(0, rowNbr * rowHeight, RenderSize.Width, rowHeight)));

            double sumValues     = 0;
            long   sumTicks      = 0;
            long   previousTick  = DataModel.StartTime.Ticks;
            float  previousValue = 0.0f;

            int step = 1;

            if (data.Count > 10)                              // Do rarefy only when more than 10 samples
            {
                step = (int)(data.Count / (pixelRange / 10)); // Draw no frequent than 10 pixels
                if (step < 1)
                {
                    step = 1;
                }
            }

            var allPoints = new List <Point>();

            for (int nbr = 0; nbr < data.Count; nbr += step)
            {
                long spanL = previousTick - DataModel.StartTime.Ticks;
                long spanR = data[nbr].Item1 - DataModel.StartTime.Ticks;
                if (isBuilding && nbr >= data.Count - step)
                {
                    spanR = nowTick - DataModel.StartTime.Ticks;
                }

                // Why (int) and (long) is needed here? Let's try to simplify
                //double shiftL = (int)(spanL * (long)pixelRange / maxTick);
                //double shiftR = (int)(spanR * (long)pixelRange / maxTick);
                double shiftL = spanL * pixelRange / maxTick;
                double shiftR = spanR * pixelRange / maxTick;
                Point  p1     = new Point(spacings.lGanttC + shiftL, (rowNbr + 1) * rowHeight - previousValue * (rowHeight - 2) / 100 - 1);
                Point  p2     = new Point(spacings.lGanttC + shiftR, (rowNbr + 1) * rowHeight - data[nbr].Item2 * (rowHeight - 2) / 100 - 1);

                StreamGeometry streamGeometry = new StreamGeometry();
                using (StreamGeometryContext geometryContext = streamGeometry.Open())
                {
                    geometryContext.BeginFigure(p1, true, true);
                    Point           p3     = new Point(p2.X, (rowNbr + 1) * rowHeight);
                    Point           p4     = new Point(p1.X, (rowNbr + 1) * rowHeight);
                    PointCollection points = new PointCollection {
                        p2, p3, p4
                    };
                    geometryContext.PolyLineTo(points, true, true);
                }

                drawingContext.DrawGeometry(softPen.Brush, softPen, streamGeometry);

                if (nbr == 0)
                {
                    allPoints.Add(p1);
                }
                allPoints.Add(p2);

                if (nbr > 0)
                {
                    sumValues += (previousValue + data[nbr].Item2) * (data[nbr].Item1 - previousTick);
                    sumTicks  += data[nbr].Item1 - previousTick;
                }

                previousTick  = data[nbr].Item1;
                previousValue = data[nbr].Item2;
            }

            for (int nbr = 1; nbr < allPoints.Count; ++nbr)
            {
                drawingContext.DrawLine(pen, allPoints[nbr - 1], allPoints[nbr]);
            }

            if (sumTicks > 0)
            {
                long          average = (long)(sumValues / 2 / sumTicks);
                FormattedText avg     = new FormattedText(" (Avg. " + average.ToString() + "%)", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush);
                title += avg.Text;
            }

            DrawText(drawingContext, title, rowNbr, Spacings.lOrder, blackBrush);
            drawingContext.Pop();
        }
        private void DrawOrderAndProjectNameText(DrawingContext drawingContext, int rowNbr, uint projectBuildOrderNumber, string projectName, Spacings margins, Brush textColor)
        {
            // project build Order
            DrawText(drawingContext, projectBuildOrderNumber.ToString() + ">", rowNbr, Spacings.lOrder, textColor);

            // project Name
            DrawText(drawingContext, projectName, rowNbr, margins.lProjName, textColor);
        }
Ejemplo n.º 5
0
        protected override void OnRender(DrawingContext drawingContext)
        {
            try
            {
                using (IsGraphDrawnScope isGraphDrawnScope = new IsGraphDrawnScope())
                {
                    if (RenderSize.Width < 10.0 || RenderSize.Height < 10.0)
                    {
                        return;
                    }

                    if (IsEmptyBuilds())
                    { // Case when no single build was started yet  -  display some info to ensure user that everything is OK
                        FormattedText captionFT = new FormattedText(emptyGanttMsg, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip);
                        drawingContext.DrawText(captionFT, new Point(50.0, rowNbrNoProjectsFiller / 2 * rowHeight));

                        return;
                    }

                    int    linesCount  = DataModel.CurrentBuilds.Count + DataModel.FinishedBuilds.Count + 1 + 1 + 1 + 1; // 1 for header, 1 for status, 1 for CPU, 1 for HDD
                    double totalHeight = rowHeight * linesCount;

                    Height = totalHeight + penThickness;

                    long tickStep = 100000000;
                    long maxTick  = tickStep;
                    long nowTick  = ((nowTickForTest > 0) ? nowTickForTest : DateTime.Now.Ticks);
                    long t        = nowTick - DataModel.StartTime.Ticks;
                    if (DataModel.CurrentBuilds.Count > 0)
                    {
                        if (t > maxTick)
                        {
                            maxTick = t;
                        }
                    }
                    int    ii;
                    bool   atLeastOneError   = false;
                    uint   maxBuildOrderNbr  = 1;
                    double projectNameMaxLen = 10;
                    for (ii = 0; ii < DataModel.FinishedBuilds.Count; ii++)
                    {
                        FormattedText iname = new FormattedText(DataModel.FinishedBuilds[ii].ProjectName, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip);
                        double        ll    = iname.Width;
                        t = DataModel.FinishedBuilds[ii].end;
                        atLeastOneError = atLeastOneError || !DataModel.FinishedBuilds[ii].success;
                        if (t > maxTick)
                        {
                            maxTick = t;
                        }

                        if (ll > projectNameMaxLen)
                        {
                            projectNameMaxLen = ll;
                        }

                        if (DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber > maxBuildOrderNbr)
                        {
                            maxBuildOrderNbr = DataModel.FinishedBuilds[ii].ProjectBuildOrderNumber;
                        }
                    }
                    foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds)
                    {
                        FormattedText iname = new FormattedText(DataModel.GetHumanReadableProjectName(item.Key), CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip);
                        double        ll    = iname.Width;
                        if (ll > projectNameMaxLen)
                        {
                            projectNameMaxLen = ll;
                        }

                        if (item.Value.Item1 > maxBuildOrderNbr)
                        {
                            maxBuildOrderNbr = item.Value.Item1;
                        }
                    }
                    if (DataModel.IsBuilding)
                    {
                        maxTick = (maxTick / tickStep + 1) * tickStep;
                    }

                    Spacings spacings = new Spacings();
                    { //setting spacings
                        string pattern = "> ";
                        int    len     = maxBuildOrderNbr.ToString().Length + pattern.Length;
                        pattern = pattern.PadLeft(len, '8'); // let's assume that 8 is the widest char
                        FormattedText bn = new FormattedText(pattern, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip);

                        spacings.lProjName = Spacings.lOrder + bn.Width + 3;                            // let's add 3 pix just in case 8 is not the widest
                        spacings.lGanttC   = spacings.lProjName + projectNameMaxLen + penThickness + 3; // let's add 3 pix just in case
                    }

                    // check if usage text is longer than the longest project name, and yes, values greater than 1000% are possible
                    double usageTextLen = new FormattedText("HDD Usage (Avg. 1000%)", CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip).Width;
                    spacings.lGanttC = Math.Max(spacings.lGanttC, Spacings.lOrder + usageTextLen + penThickness);

                    int rowNbr = 0;                            //first row has number 0

                    {                                          // Draw header
                        DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                        DrawMachineInfo(drawingContext, rowNbr);

                        rowNbr++;
                    }

                    foreach (BuildInfo item in DataModel.FinishedBuilds)
                    {
                        DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                        DrawOrderAndProjectNameText(drawingContext, rowNbr, item.ProjectBuildOrderNumber, item.ProjectName, spacings, (item.success ? greenSolidBrush : redSolidBrush));
                        DrawBar(drawingContext, rowNbr, item.begin, item.end, maxTick, spacings, (item.success ? greenGradientBrush : redGradientBrush), DataModel.CriticalPath.Contains(item));

                        rowNbr++;
                    }

                    foreach (KeyValuePair <string, Tuple <uint, long> > item in DataModel.CurrentBuilds)
                    {
                        DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                        DrawOrderAndProjectNameText(drawingContext, rowNbr, item.Value.Item1, DataModel.GetHumanReadableProjectName(item.Key), spacings, blueSolidBrush);
                        DrawBar(drawingContext, rowNbr, item.Value.Item2, (nowTick - DataModel.StartTime.Ticks), maxTick, spacings, blueGradientBrush, false /*markAsCriticalPath*/);

                        rowNbr++;
                    }

                    DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                    DrawGraph("CPU usage", drawingContext, DataModel.CpuUsage, cpuPen, cpuSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace);
                    rowNbr++;

                    DrawSeparator(drawingContext, rowNbr); // draw backgroud things first due to anty-aliasing
                    DrawGraph("HDD usage", drawingContext, DataModel.HddUsage, hddPen, hddSoftPen, rowNbr, RenderSize, rowHeight, spacings, maxTick, nowTick, fontFace);
                    rowNbr++;

                    if (DataModel.CurrentBuilds.Count > 0 || DataModel.FinishedBuilds.Count > 0)
                    {
                        string status = (DataModel.IsBuilding ? "Building..." : "Done.");
                        if (DataModel.MaxParallelBuilds > 0)
                        {
                            status += " Max. no. of parallel projects: " + DataModel.MaxParallelBuilds.ToString() + " (Avg. " +
                                      Math.Round(DataModel.MaxParallelBuilds / 100.0 * DataModel.PercentageProcessorUse(), 1).ToString(format: "0.0") + ")";
                        }

                        DrawText(drawingContext, status, rowNbr, Spacings.lOrder, blackBrush);
                    }

                    DrawVerticalSeparator(drawingContext, spacings.lGanttC - penThickness, rowNbr, false /*wholeSize*/);

                    drawingContext.DrawLine(new Pen(atLeastOneError ? redSolidBrush : greenSolidBrush, 1), new Point(0, rowNbr * rowHeight), new Point(RenderSize.Width, rowNbr * rowHeight));

                    DateTime      dt      = new DateTime(maxTick);
                    string        s       = Utils.SecondsToString(dt.Ticks);
                    FormattedText maxTime = new FormattedText(s, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, fontFace, FontSize, blackBrush, VisualTreeHelper.GetDpi(this).PixelsPerDip);
                    double        m       = maxTime.Width;
                    drawingContext.DrawText(maxTime, new Point(RenderSize.Width - m - Spacings.rGanttC, rowNbr * rowHeight));

                    {                                                                                                       // Draw frame around - it looks better in window and definitelly in saved .png. Draw frame as last to be clearly visible
                        DrawSeparator(drawingContext, -1);                                                                  // Top
                        DrawSeparator(drawingContext, rowNbr);                                                              // Bottom
                        DrawVerticalSeparator(drawingContext, 0, rowNbr, true /*wholeSize*/);                               // Left
                        DrawVerticalSeparator(drawingContext, RenderSize.Width - penThickness, rowNbr, true /*wholeSize*/); // Right
                    }

                    isGraphDrawnScope.IsDrawn = true;
                } //End of IsGraphDrawnScope
            }
            catch (Exception)
            {
                Debug.Assert(false, "Gantt chart not refreshed! Exception thrown while drawing Gantt chart.");
            }
        }