using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Windows.Forms; using TradeIdeas.MiscSupport; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Interfaces; namespace TradeIdeas.TIProGUI { [ToolboxBitmap(typeof(TickPanel), "TickPanel")] public partial class TickPanel : UserControl { ToolTip tt = new ToolTip(); IConnectionMaster _connectionMaster; PointF _orbPosition; float _orbRadius; Bitmap _currentOrb; //Lightspeed private bool _lightSpeedEnabled = false; // This gets reset if we need to redraw or rescale the orb graphics. // We cache the current color/size until either the status changes // or the control is resized. StatusLevel _currentOrbStatus = StatusLevel.None; StatusLevel _updatedCurrentOrbStatus = StatusLevel.None; private bool _disconnected = true; public enum StatusLevel { None, Red, Yellow, Green, Blue } private struct TickDetails { public StatusLevel level; public UInt32 epoch; } private UInt32 _epoch; private TickDetails[] _ticks; private double? _ping; private UInt32 _pingEpoch; private Dictionary _orbBmps = new Dictionary(); private bool _currentAccountStatusIsGood = true; public Color? CustomPingStringColor { get; set; } private bool _useGradient = false; public bool UseGradient { get { return _useGradient; } set { _useGradient = value; } } private Color _bottomColor = Color.White; public Color BottomColor { get { return _bottomColor; } set { _bottomColor = value; } } public TickPanel() { InitializeComponent(); _orbBmps[StatusLevel.Green] = DrawOrb(StatusLevel.Green); _orbBmps[StatusLevel.Red] = DrawOrb(StatusLevel.Red); _orbBmps[StatusLevel.Yellow] = DrawOrb(StatusLevel.Yellow); _orbBmps[StatusLevel.None] = DrawOrb(StatusLevel.None); _orbBmps[StatusLevel.Blue] = DrawOrb(StatusLevel.Blue); _currentOrb = _orbBmps[StatusLevel.None]; _ticks = new TickDetails[40]; _ping = null; SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.DoubleBuffer, true); _orbPosition = new PointF(); tt.SetToolTip(this, "No Errors"); // todo string resource? } // creates original high resolution source orbs private Bitmap DrawOrb(StatusLevel level) { Bitmap returnValue = new Bitmap(512, 512); GraphicsPath path = new GraphicsPath(); path.AddPolygon(new PointF[] { new PointF(-128,-256), new PointF(512,-256), new PointF(512,512), new PointF(-256,512) }); PathGradientBrush pgb = new PathGradientBrush(path); pgb.CenterColor = Color.FromArgb(255,240,240,240); Color orbColor = MakeColor((int)(255 * 0.85f), level); pgb.SurroundColors = new Color[] { orbColor, orbColor, orbColor, orbColor }; Graphics gfx = Graphics.FromImage(returnValue); gfx.CompositingMode = CompositingMode.SourceCopy; // It seems like this should be 512, not 508. But the bottom and right edges // always looked a little flat. (The top and left always look smooth and curved.) // Changing this to 508 seems to help. gfx.FillEllipse(pgb, 0, 0, 508, 508); path.Dispose(); pgb.Dispose(); return returnValue; } // Creates scaled orb which will be cached until the // orb changes in size or color. private Bitmap ScaleOrb(Bitmap inputOrb, int size) { if (size > 0) { Bitmap scaledOrb = new Bitmap(size, size); Graphics bmpGfx = Graphics.FromImage(scaledOrb); bmpGfx.CompositingMode = CompositingMode.SourceCopy; bmpGfx.InterpolationMode = InterpolationMode.High; bmpGfx.DrawImage(inputOrb, 0, 0, size, size); return scaledOrb; } else return new Bitmap(1, 1); } public void InitializeConnection(IConnectionMaster connectionMaster) { _connectionMaster = connectionMaster; //_connectionMaster.LoginManager.AccountStatusUpdate += _LoginManager_AccountStatusUpdate; _connectionMaster.PingManager.PingUpdate += _pingManager_PingUpdate; _connectionMaster.ConnectionBase.Preview += _ConnectionBase_Preview; _connectionMaster.ConnectionBase.ConnectionStatusUpdate += _ConnectionBase_ConnectionStatusUpdate; } public void AddTick(StatusLevel status) { for (int i = 0; i < _ticks.Length - 1; i++) { _ticks[i] = _ticks[i + 1]; } _ticks[_ticks.Length - 1].level = status; _ticks[_ticks.Length - 1].epoch = _epoch; } protected override void OnResize(EventArgs e) { _currentOrbStatus = StatusLevel.None; base.OnResize(e); } protected override void OnPaint(PaintEventArgs e) { try { Graphics gfx = e.Graphics; if (_useGradient) { gfx.FillRectangle(new SolidBrush(Color.White), DisplayRectangle); gfx.DrawRoundedRectangleGradient(BackColor, _bottomColor, DisplayRectangle, 3, RoundedCorners.All); } float height = this.Height; float vertiSize = 7f / 10f * height; float fontSize = Math.Max(0.4f * vertiSize, Math.Min(vertiSize, 12f)); fontSize = Math.Min(this.Width / 11f, fontSize); float vertiGap = 3f / 20 * height; float pingWidth = 0; float right = 0; float heightOffset = 0; float orbX = 0; // If the control isn't wide enough we move the ping // and the orb to a second line and shrink everything // vertically bool twoLines = (Width / Height < 3.0f); if (twoLines) { vertiSize /= 2; vertiGap /= 2; } Font font = new Font(FontFamily.GenericSansSerif, fontSize); string pingString = ' ' + _ping.ToString() + "ms"; SizeF pingStringOffsets = gfx.MeasureString(pingString, font); orbX = Width - 2 * vertiGap - vertiSize; if (twoLines) { heightOffset = height / 2; right = Width; } else { pingWidth = pingStringOffsets.Width; right = orbX; } orbX = Math.Max(orbX, pingStringOffsets.Width); gfx.SmoothingMode = SmoothingMode.AntiAlias; gfx.PixelOffsetMode = PixelOffsetMode.HighQuality; gfx.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit; Color pingColor = Color.FromArgb(0, Math.Max(0, Math.Min(255 - (int)((_epoch - _pingEpoch) << 2), 255)), 0); if (CustomPingStringColor.HasValue) pingColor = CustomPingStringColor.Value; Brush pingBrush = new SolidBrush(pingColor); if (_ping != null) gfx.DrawString(pingString, font, pingBrush, new PointF(0, heightOffset + (2f * vertiGap + vertiSize) / 2f - pingStringOffsets.Height / 2f)); pingBrush.Dispose(); font.Dispose(); _orbPosition.X = orbX + vertiSize / 2; _orbPosition.Y = heightOffset + vertiGap + vertiSize / 2; _orbRadius = vertiSize / 2; int tickOffset = 0; if (_epoch - _ticks.Last().epoch <= 1) tickOffset = 1; float width = 0; //Scale the width of the tickmarks to scaled by the ratio of height to width try { width = 6f * (this.Width - pingWidth) * height / this.Width; } catch //Catch dividing by zero exception { width = this.Width - pingWidth; } //float width = this.Width - pingWidth; float horiSize = (width / 20) * (4f / 7f); float horiGap = (width / 20) * (3f / 7f); // Set status orb color _updatedCurrentOrbStatus = GetBallColor(_ticks.Last().level); if (_currentOrbStatus != _updatedCurrentOrbStatus) { _currentOrb = ScaleOrb(_orbBmps[_updatedCurrentOrbStatus], (int)vertiSize); _currentOrbStatus = _updatedCurrentOrbStatus; } gfx.DrawImageUnscaled(_currentOrb, (int)orbX, (int)(heightOffset + vertiGap)); gfx.PixelOffsetMode = PixelOffsetMode.Default; gfx.SmoothingMode = SmoothingMode.Default; float borderSize = Math.Min(vertiSize / 4f, horiSize / 4f); //float left = width - (2 - offset) * (horiGap + horiSize) + pingWidth; int left = (int)Math.Round(right - (2 - tickOffset) * (horiGap + horiSize)); int currentTick = _ticks.Length - 1; while (left >= pingWidth && currentTick >= 2 - tickOffset) { TickDetails tick = _ticks[currentTick]; if (tick.level != StatusLevel.None) { UInt32 intensity = 255 - (_epoch - tick.epoch); Color bright = MakeColor((int)intensity, tick.level); Color medium = MakeColor((int)(0.8f * intensity), tick.level); Color dark = MakeColor((int)(0.6f * intensity), tick.level); SolidBrush brushBright = new SolidBrush(MakeColor((int)(intensity), tick.level)); SolidBrush brushMedium = new SolidBrush(medium); SolidBrush brushDark = new SolidBrush(dark); if (borderSize >= 1f) { //middle gfx.FillRectangle(brushMedium, left + borderSize, vertiGap + borderSize, horiSize - 2 * borderSize, vertiSize - 2 * borderSize); //left side gfx.FillRectangle(brushBright, left, vertiGap, borderSize, vertiSize); //right side gfx.FillRectangle(brushDark, left + horiSize - borderSize, vertiGap, borderSize, vertiSize); //top gfx.FillRectangle(brushBright, left + borderSize, vertiGap, horiSize - 2 * borderSize, borderSize); //bottom gfx.FillRectangle(brushDark, left + borderSize, vertiGap + vertiSize - borderSize, horiSize - 2 * borderSize, borderSize); } else { // The total area will be covered with one color. gfx.FillRectangle(brushMedium, left, vertiGap, horiSize, vertiSize); } // Dispose of brushes brushBright.Dispose(); brushMedium.Dispose(); brushDark.Dispose(); } left -= (int)Math.Round(horiGap + horiSize); currentTick--; }; } catch { } } // Status orb now uses the AccountStatus instead of isServerDisconnect to determine color private StatusLevel GetBallColor(StatusLevel lastTickMarkColor) { if (!_currentAccountStatusIsGood && lastTickMarkColor == StatusLevel.Blue) return StatusLevel.Blue; else if (!_currentAccountStatusIsGood) return StatusLevel.Red; if (lastTickMarkColor == StatusLevel.Green) return StatusLevel.Green; if (lastTickMarkColor == StatusLevel.Red || lastTickMarkColor == StatusLevel.Yellow) return StatusLevel.Yellow; return StatusLevel.None; } private Color MakeColor(int intensity, StatusLevel level) { intensity = Math.Max(0, Math.Min(intensity, 255)); Color returnValue = Color.Empty; switch (level) { case StatusLevel.Red: returnValue = Color.FromArgb(intensity, intensity, 0, 0); break; case StatusLevel.Yellow: returnValue = Color.FromArgb(intensity, intensity, intensity, 0); break; case StatusLevel.Green: returnValue = Color.FromArgb(intensity, 0, intensity, 0); break; case StatusLevel.Blue: returnValue = Color.FromArgb(intensity, 0, 0,intensity); break; default: returnValue = Color.Gray; break; } return returnValue; } private Random _random = new Random(); private void tickTimer_Tick(object sender, EventArgs e) { if (DesignMode) { if (_random.NextDouble() > 0.8) { StatusLevel statusLevel; double r = _random.NextDouble(); if (r > 0.9) statusLevel = StatusLevel.Red; else if (r > 0.8) statusLevel = StatusLevel.Yellow; else statusLevel = StatusLevel.Green; AddTick(statusLevel); if (_random.NextDouble() > .667) { _ping = _random.Next(5, 15); _pingEpoch = _epoch; } } } if (_ticks.Last().level == StatusLevel.Blue) AddTick(StatusLevel.Blue); //UpdatePingColor(); _epoch++; Invalidate(); } private void _pingManager_PingUpdate(TimeSpan ping) { if (InvokeRequired) BeginInvoke((MethodInvoker)delegate { _pingManager_PingUpdate(ping); }); else { _ping = Math.Round(ping.TotalMilliseconds); _pingEpoch = _epoch; } } private void _ConnectionBase_Preview(ConnectionBase source, PreviewArgs args) { if (InvokeRequired) BeginInvoke((MethodInvoker)delegate { _ConnectionBase_Preview(source, args); }); else { if (args.goodMessage) { AddTick(StatusLevel.Green); } else { AddTick(StatusLevel.Yellow); } } } private void _ConnectionBase_ConnectionStatusUpdate(ConnectionBase source, ConnectionStatusCallbackArgs args) { if (InvokeRequired) BeginInvoke((MethodInvoker)delegate { _ConnectionBase_ConnectionStatusUpdate(source, args); }); else { //TODO error history tt.SetToolTip(this, ServerFormats.Now.ToString() + Environment.NewLine + args.message); if (args.isServerDisconnect) { if (!_disconnected && _ticks.Last().level != StatusLevel.None) { if (_currentAccountStatusIsGood && _connectionMaster.LoginManager.AccountStatus != AccountStatus.Waiting && _connectionMaster.LoginManager.AccountStatus != AccountStatus.BadUserName && _connectionMaster.LoginManager.AccountStatus != AccountStatus.BadPassword && _connectionMaster.LoginManager.AccountStatus != AccountStatus.AnotherUser) AddTick(StatusLevel.Yellow); else AddTick(StatusLevel.Red); } _disconnected = true; } else { _disconnected = false; } } } private bool isPointInButton(Point pt) { float xDiff = _orbPosition.X - pt.X; float yDiff = _orbPosition.Y - pt.Y; return (Math.Sqrt(xDiff * xDiff + yDiff * yDiff) < _orbRadius); } private void TickPanel_MouseMove(object sender, MouseEventArgs e) { if (isPointInButton(new Point(e.X, e.Y))) { tt.Active = true; } else { tt.Active = false; } } public void setLightSpeedStatus(bool lSpeed) { _lightSpeedEnabled = lSpeed; } public void TickPanel_MouseDoubleClick(object sender, MouseEventArgs e) { if (isPointInButton(new Point(e.X, e.Y)) && (_connectionMaster.LoginManager.AccountStatus != AccountStatus.Good || _disconnected)) { if (!_lightSpeedEnabled) { MakeHardReset(); } } } // Procedure is used to control status orb color based on account status public void SetCurrentAccountStatusIsGood(bool currentAccountStatusIsGood) { _currentAccountStatusIsGood = currentAccountStatusIsGood; } /// /// Restart the connection to the server /// public void MakeHardReset() { AddTick(StatusLevel.Blue); _connectionMaster.LoginManager.HardReset(); } } }