context: /*  This file is part of Chummer5a.
   *
   *  Chummer5a is free software: you can redistribute it and/or modify
   *  it under the terms of the GNU General Public License as published by
   *  the Free Software Foundation, either version 3 of the License, or
   *  (at your option) any later version.
   *
   *  Chummer5a is distributed in the hope that it will be useful,
   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   *  GNU General Public License for more details.
   *
   *  You should have received a copy of the GNU General Public License
   *  along with Chummer5a.  If not, see <http://www.gnu.org/licenses/>.
   *
   *  You can obtain the full source code for Chummer5a at
   *  https://github.com/chummer5a/chummer5a
   */
  
  using System;
  using System.ComponentModel;
  using System.Drawing;
  using System.Windows.Forms;
  using System.Windows.Forms.VisualStyles;
  
  //Get the latest version of SplitButton at: http://wyday.com/splitbutton/
  
  namespace Chummer
  {
      public class SplitButton : Button
      {
          private PushButtonState _state;
  
          private const int SplitSectionWidth = 18;
  
          private static readonly int BorderSize = SystemInformation.Border3DSize.Width * 2;
          private bool _skipNextOpen;
          private Rectangle _dropDownRectangle;
          private bool _showSplit;
  
          private bool _isSplitMenuVisible;
  
          private ContextMenuStrip m_SplitMenuStrip;
          private ContextMenu m_SplitMenu;
  
          private TextFormatFlags _textFormatFlags = TextFormatFlags.Default;
  
          /*
      public sealed override bool AutoSize
      {
          get { return base.AutoSize; }
          set { base.AutoSize = value; }
      }
      */
  
          #region Properties
  
          [Browsable(false)]
          public override ContextMenuStrip ContextMenuStrip
          {
              get => SplitMenuStrip;
              set => SplitMenuStrip = value;
          }
  
          [DefaultValue(null)]
          public ContextMenu SplitMenu
          {
              get => m_SplitMenu;
              set
              {
                  //remove the event handlers for the old SplitMenu
                  if (m_SplitMenu != null)
                  {
                      m_SplitMenu.Popup -= SplitMenu_Popup;
                  }
  
                  //add the event handlers for the new SplitMenu
                  if (value != null)
                  {
                      ShowSplit = true;
                      value.Popup += SplitMenu_Popup;
                  }
                  else
                      ShowSplit = false;
  
                  m_SplitMenu = value;
              }
          }
  
          [DefaultValue(null)]
          public ContextMenuStrip SplitMenuStrip
          {
              get => m_SplitMenuStrip;
              set
              {
                  //remove the event handlers for the old SplitMenuStrip
                  if (m_SplitMenuStrip != null)
                  {
                      m_SplitMenuStrip.Closing -= SplitMenuStrip_Closing;
                      m_SplitMenuStrip.Opening -= SplitMenuStrip_Opening;
                  }
  
                  //add the event handlers for the new SplitMenuStrip
                  if (value != null)
                  {
                      ShowSplit = true;
                      value.Closing += SplitMenuStrip_Closing;
                      value.Opening += SplitMenuStrip_Opening;
                  }
                  else
                      ShowSplit = false;
  
                  m_SplitMenuStrip = value;
              }
          }
  
          [DefaultValue(false)]
          public bool ShowSplit
          {
              set
              {
                  if (value != _showSplit)
                  {
                      _showSplit = value;
                      Invalidate();
  
                      Parent?.PerformLayout();
                  }
              }
          }
  
          private PushButtonState State
          {
              get => _state;
              set
              {
                  if (!_state.Equals(value))
                  {
                      _state = value;
                      Invalidate();
                  }
              }
          }
  
          #endregion Properties
  
          protected override bool IsInputKey(Keys keyData)
          {
              if (keyData.Equals(Keys.Down) && _showSplit)
                  return true;
  
              return base.IsInputKey(keyData);
          }
  
          protected override void OnGotFocus(EventArgs e)
          {
              if (!_showSplit)
              {
                  base.OnGotFocus(e);
                  return;
              }
  
              if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled))
              {
                  State = PushButtonState.Default;
              }
          }
  
          protected override void OnKeyDown(KeyEventArgs kevent)
          {
              if (_showSplit)
              {
                  switch (kevent.KeyCode)
                  {
                      case Keys.Down when !_isSplitMenuVisible:
                          ShowContextMenuStrip();
                          break;
  
                      case Keys.Space when kevent.Modifiers == Keys.None:
                          State = PushButtonState.Pressed;
                          break;
                  }
              }
  
              base.OnKeyDown(kevent);
          }
  
          protected override void OnKeyUp(KeyEventArgs kevent)
          {
              switch (kevent.KeyCode)
              {
                  case Keys.Space:
                  {
                      if (MouseButtons == MouseButtons.None)
                      {
                          State = PushButtonState.Normal;
                      }
  
                      break;
                  }
                  case Keys.Apps when MouseButtons == MouseButtons.None && !_isSplitMenuVisible:
                      ShowContextMenuStrip();
                      break;
              }
  
              base.OnKeyUp(kevent);
          }
  
          protected override void OnEnabledChanged(EventArgs e)
          {
              State = Enabled ? PushButtonState.Normal : PushButtonState.Disabled;
  
              base.OnEnabledChanged(e);
          }
  
          protected override void OnLostFocus(EventArgs e)
          {
              if (!_showSplit)
              {
                  base.OnLostFocus(e);
                  return;
              }
  
              if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled))
              {
                  State = PushButtonState.Normal;
              }
          }
  
          private bool isMouseEntered;
  
          protected override void OnMouseEnter(EventArgs e)
          {
              if (!_showSplit)
              {
                  base.OnMouseEnter(e);
                  return;
              }
  
              isMouseEntered = true;
  
              if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled))
              {
                  State = PushButtonState.Hot;
              }
          }
  
          protected override void OnMouseLeave(EventArgs e)
          {
              if (!_showSplit)
              {
                  base.OnMouseLeave(e);
                  return;
              }
  
              isMouseEntered = false;
  
              if (!State.Equals(PushButtonState.Pressed) && !State.Equals(PushButtonState.Disabled))
              {
                  State = Focused ? PushButtonState.Default : PushButtonState.Normal;
              }
          }
  
          protected override void OnMouseDown(MouseEventArgs mevent)
          {
              if (!_showSplit)
              {
                  base.OnMouseDown(mevent);
                  return;
              }
  
              //handle ContextMenu re-clicking the drop-down region to close the menu
              if (m_SplitMenu != null && mevent.Button == MouseButtons.Left && !isMouseEntered)
                  _skipNextOpen = true;
  
              if (_dropDownRectangle.Contains(mevent.Location) && !_isSplitMenuVisible && mevent.Button == MouseButtons.Left)
              {
                  ShowContextMenuStrip();
              }
              else
              {
                  State = PushButtonState.Pressed;
              }
          }
  
          protected override void OnMouseUp(MouseEventArgs mevent)
          {
              if (!_showSplit)
              {
                  base.OnMouseUp(mevent);
                  return;
              }
  
              // if the right button was released inside the button
              if (mevent.Button == MouseButtons.Right && ClientRectangle.Contains(mevent.Location) && !_isSplitMenuVisible)
              {
                  ShowContextMenuStrip();
              }
              else if (m_SplitMenuStrip == null && m_SplitMenu == null || !_isSplitMenuVisible)
              {
                  SetButtonDrawState();
  
                  if (ClientRectangle.Contains(mevent.Location) && !_dropDownRectangle.Contains(mevent.Location))
                  {
                      OnClick(EventArgs.Empty);
                  }
              }
          }
  
          protected override void OnPaint(PaintEventArgs pevent)
          {
              base.OnPaint(pevent);
  
              if (!_showSplit)
                  return;
  
              Graphics g = pevent.Graphics;
              Rectangle bounds = ClientRectangle;
  
              // draw the button background as according to the current state.
              if (State != PushButtonState.Pressed && IsDefault && !Application.RenderWithVisualStyles)
              {
                  Rectangle backgroundBounds = bounds;
                  backgroundBounds.Inflate(-1, -1);
                  ButtonRenderer.DrawButton(g, backgroundBounds, State);
  
                  // button renderer doesnt draw the black frame when themes are off
                  g.DrawRectangle(SystemPens.WindowFrame, 0, 0, bounds.Width - 1, bounds.Height - 1);
              }
              else
              {
                  ButtonRenderer.DrawButton(g, bounds, State);
              }
  
              // calculate the current dropdown rectangle.
              _dropDownRectangle = new Rectangle(bounds.Right - SplitSectionWidth, 0, SplitSectionWidth, bounds.Height);
  
              int internalBorder = BorderSize;
              Rectangle focusRect =
                  new Rectangle(internalBorder - 1,
                                internalBorder - 1,
                                bounds.Width - _dropDownRectangle.Width - internalBorder,
                                bounds.Height - (internalBorder * 2) + 2);
  
              bool drawSplitLine = (State == PushButtonState.Hot || State == PushButtonState.Pressed || !Application.RenderWithVisualStyles);
  
              if (RightToLeft == RightToLeft.Yes)
              {
                  _dropDownRectangle.X = bounds.Left + 1;
                  focusRect.X = _dropDownRectangle.Right;
  
                  if (drawSplitLine)
                  {
                      // draw two lines at the edge of the dropdown button
                      g.DrawLine(SystemPens.ButtonShadow, bounds.Left + SplitSectionWidth, BorderSize, bounds.Left + SplitSectionWidth, bounds.Bottom - BorderSize);
                      g.DrawLine(SystemPens.ButtonFace, bounds.Left + (SplitSectionWidth + 1), BorderSize, bounds.Left + (SplitSectionWidth + 1), bounds.Bottom - BorderSize);
                  }
              }
              else
              {
                  if (drawSplitLine)
                  {
                      // draw two lines at the edge of the dropdown button
                      g.DrawLine(SystemPens.ButtonShadow, bounds.Right - SplitSectionWidth, BorderSize, bounds.Right - SplitSectionWidth, bounds.Bottom - BorderSize);
                      g.DrawLine(SystemPens.ButtonFace, bounds.Right - (SplitSectionWidth - 1), BorderSize, bounds.Right - (SplitSectionWidth - 1), bounds.Bottom - BorderSize);
                  }
              }
  
              // Draw an arrow in the correct location
              PaintArrow(g, _dropDownRectangle);
  
              //paint the image and text in the "button" part of the splitButton
              PaintTextandImage(g, new Rectangle(0, 0, ClientRectangle.Width - SplitSectionWidth, ClientRectangle.Height));
  
              // draw the focus rectangle.
              if (State != PushButtonState.Pressed && Focused && ShowFocusCues)
              {
                  ControlPaint.DrawFocusRectangle(g, focusRect);
              }
          }
  
          private void PaintTextandImage(Graphics g, Rectangle bounds)
          {
              // Figure out where our text and image should go
              CalculateButtonTextAndImageLayout(ref bounds, out Rectangle text_rectangle, out Rectangle image_rectangle);
  
              //draw the image
              if (Image != null)
              {
                  if (Enabled)
                      g.DrawImage(Image, image_rectangle.X, image_rectangle.Y, Image.Width, Image.Height);
                  else
                      ControlPaint.DrawImageDisabled(g, Image, image_rectangle.X, image_rectangle.Y, BackColor);
              }
  
              // If we dont' use mnemonic, set formatFlag to NoPrefix as this will show ampersand.
              if (!UseMnemonic)
                  _textFormatFlags |= TextFormatFlags.NoPrefix;
              else if (!ShowKeyboardCues)
                  _textFormatFlags |= TextFormatFlags.HidePrefix;
  
              //draw the text
              if (!string.IsNullOrEmpty(Text))
              {
                  if (Enabled)
                      TextRenderer.DrawText(g, Text, Font, text_rectangle, ForeColor, _textFormatFlags);
                  else
                      ControlPaint.DrawStringDisabled(g, Text, Font, BackColor, text_rectangle, _textFormatFlags);
              }
          }
  
          private void PaintArrow(Graphics g, Rectangle dropDownRect)
          {
              Point middle = new Point(dropDownRect.Left + dropDownRect.Width / 2, dropDownRect.Top + dropDownRect.Height / 2);
  
              //if the width is odd - favor pushing it over one pixel right.
              middle.X += (dropDownRect.Width & 1);
  
              Point[] arrow = {
                  new Point(middle.X - (int)(2 * g.DpiX / 96.0f), middle.Y - (int)(g.DpiY / 96.0f)),
                  new Point(middle.X + (int)(3 * g.DpiX / 96.0f), middle.Y - (int)(g.DpiY / 96.0f)),
                  new Point(middle.X, middle.Y + (int)(2 * g.DpiY / 96.0f)) };
  
              g.FillPolygon(Enabled ? SystemBrushes.ControlText : SystemBrushes.ButtonShadow, arrow);
          }
  
          public override Size GetPreferredSize(Size proposedSize)
          {
              Size preferredSize = base.GetPreferredSize(proposedSize);
  
              //autosize correctly for splitbuttons
              if (_showSplit)
              {
                  if (AutoSize)
                      return CalculateButtonAutoSize();
  
                  if (!string.IsNullOrEmpty(Text) && TextRenderer.MeasureText(Text, Font).Width + SplitSectionWidth > preferredSize.Width)
                      return preferredSize + new Size(SplitSectionWidth + BorderSize * 2, 0);
              }
  
              return preferredSize;
          }
  
          private Size CalculateButtonAutoSize()
          {
              Size ret_size = Size.Empty;
              Size text_size = TextRenderer.MeasureText(Text, Font);
              Size image_size = Image?.Size ?? Size.Empty;
  
              // Pad the text size
              if (Text.Length != 0)
              {
                  text_size.Height += 4;
                  text_size.Width += 4;
              }
  
              switch (TextImageRelation)
              {
                  case TextImageRelation.Overlay:
                      ret_size.Height = Math.Max(Text.Length == 0 ? 0 : text_size.Height, image_size.Height);
                      ret_size.Width = Math.Max(text_size.Width, image_size.Width);
                      break;
  
                  case TextImageRelation.ImageAboveText:
                  case TextImageRelation.TextAboveImage:
                      ret_size.Height = text_size.Height + image_size.Height;
  
gt:                     ret_size.Width = Math.Max(text_size.Width, image_size.Width);