File: AuthoringOM\Design\MessageFilters\DynamicActionMessageFilter.cs
Project: ndp\cdf\src\WF\Common\System.Workflow.ComponentModel.csproj (System.Workflow.ComponentModel)
namespace System.Workflow.ComponentModel.Design
{
    using System;
    using System.Text;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Drawing.Drawing2D;
    using System.Collections.Generic;
    using System.ComponentModel.Design;
 
    #region Class DynamicActionMessageFilter
    //Behavior needs coordinates in client coordinate system
    internal sealed class DynamicActionMessageFilter : WorkflowDesignerMessageFilter
    {
        #region Members, Construction and Destruction
        private List<DynamicAction> actions = new List<DynamicAction>();
 
        private int draggedButtonIndex = -1;
        private int draggedActionIndex = -1;
 
        private bool infoTipSet = false;
 
        internal DynamicActionMessageFilter()
        {
        }
        #endregion
 
        #region Properties and Methods
        internal void AddAction(DynamicAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
 
            if (!this.actions.Contains(action))
            {
                if (IsButtonDragged)
                    SetDraggedButton(-1, -1);
 
                this.actions.Add(action);
                RefreshAction(action);
            }
        }
 
        internal bool ActionExists(DynamicAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
 
            return this.actions.Contains(action);
        }
 
        internal void RemoveAction(DynamicAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
 
            if (this.actions.Contains(action))
            {
                if (IsButtonDragged)
                    SetDraggedButton(-1, -1);
 
                RefreshAction(action);
                this.actions.Remove(action);
            }
        }
 
        internal void RefreshAction(DynamicAction action)
        {
            if (action == null)
                throw new ArgumentNullException("action");
 
            int actionIndex = this.actions.IndexOf(action);
            if (actionIndex >= 0)
                ParentView.InvalidateClientRectangle(GetActionBounds(actionIndex));
        }
        #endregion
 
        #region Behavior Overrides
        protected override void Initialize(WorkflowView parentView)
        {
            base.Initialize(parentView);
 
            IServiceContainer serviceContainer = GetService(typeof(IServiceContainer)) as IServiceContainer;
            if (serviceContainer != null)
            {
                serviceContainer.RemoveService(typeof(DynamicActionMessageFilter));
                serviceContainer.AddService(typeof(DynamicActionMessageFilter), this);
            }
        }
 
        protected override void Dispose(bool disposing)
        {
            try
            {
                IServiceContainer serviceContainer = GetService(typeof(IServiceContainer)) as IServiceContainer;
                if (serviceContainer != null)
                    serviceContainer.RemoveService(typeof(DynamicActionMessageFilter));
            }
            finally
            {
                base.Dispose(disposing);
            }
        }
 
        protected override bool OnMouseEnter(MouseEventArgs eventArgs)
        {
            UpdateTransparency(new Point(eventArgs.X, eventArgs.Y));
            Refresh();
            return false;
        }
 
        protected override bool OnMouseDown(MouseEventArgs eventArgs)
        {
            Point clientPoint = new Point(eventArgs.X, eventArgs.Y);
 
            Refresh();
            UpdateTransparency(clientPoint);
 
            bool retval = false;
            if ((eventArgs.Button & MouseButtons.Left) > 0)
            {
                for (int i = this.actions.Count - 1; i >= 0; i--)
                {
                    DynamicAction action = this.actions[i];
                    Rectangle actionBounds = GetActionBounds(i);
                    if (actionBounds.Contains(clientPoint))
                    {
                        //If we clicked on disabled button then no further action is needed
                        for (int j = 0; j < action.Buttons.Count; j++)
                        {
                            Rectangle buttonBounds = GetButtonBounds(i, j);
                            if (buttonBounds.Contains(clientPoint) && action.Buttons[j].State == ActionButton.States.Disabled)
                                return true;
                        }
 
                        //Now check all the buttons and update their states
                        for (int j = 0; j < action.Buttons.Count; j++)
                        {
                            ActionButton actionButton = action.Buttons[j];
                            if (actionButton.State != ActionButton.States.Disabled)
                            {
                                Rectangle buttonBounds = GetButtonBounds(i, j);
                                if (buttonBounds.Contains(clientPoint))
                                {
                                    actionButton.State = ActionButton.States.Pressed;
                                    if (action.ActionType != DynamicAction.ActionTypes.TwoState)
                                        SetDraggedButton(i, j);
                                }
                                else if (action.ActionType == DynamicAction.ActionTypes.TwoState)
                                {
                                    actionButton.State = ActionButton.States.Normal;
                                }
                            }
                        }
 
                        retval = true;
                    }
                }
            }
 
            return retval;
        }
 
        protected override bool OnMouseMove(MouseEventArgs eventArgs)
        {
            Point clientPoint = new Point(eventArgs.X, eventArgs.Y);
 
            Refresh();
            UpdateTransparency(clientPoint);
 
            string infoTip = String.Empty;
            bool retval = IsButtonDragged;
            if (!IsButtonDragged)
            {
                for (int i = this.actions.Count - 1; i >= 0; i--)
                {
                    DynamicAction action = this.actions[i];
                    Rectangle actionBounds = GetActionBounds(i);
 
                    for (int j = 0; j < action.Buttons.Count; j++)
                    {
                        ActionButton actionButton = action.Buttons[j];
 
                        if (actionBounds.Contains(clientPoint))
                        {
                            Rectangle buttonBounds = GetButtonBounds(i, j);
                            bool buttonContainsPoint = buttonBounds.Contains(clientPoint);
 
                            if (buttonContainsPoint && infoTip.Length == 0)
                                infoTip = actionButton.Description;
 
                            if (actionButton.State != ActionButton.States.Disabled &&
                                actionButton.State != ActionButton.States.Pressed)
                            {
                                if (buttonContainsPoint)
                                    actionButton.State = ActionButton.States.Highlight;
                                else
                                    actionButton.State = ActionButton.States.Normal;
                            }
 
                            retval = true;
                        }
                        else
                        {
                            if (actionButton.State == ActionButton.States.Highlight)
                                actionButton.State = ActionButton.States.Normal;
                        }
                    }
                }
            }
 
            WorkflowView parentView = ParentView;
            if (infoTip.Length > 0)
            {
                this.infoTipSet = true;
                parentView.ShowInfoTip(infoTip);
            }
            else if (this.infoTipSet)
            {
                parentView.ShowInfoTip(String.Empty);
                this.infoTipSet = false;
            }
 
            return retval;
        }
 
        protected override bool OnMouseDoubleClick(MouseEventArgs eventArgs)
        {
            for (int i = this.actions.Count - 1; i >= 0; i--)
            {
                DynamicAction action = this.actions[i];
                Rectangle actionBounds = GetActionBounds(i);
                if (actionBounds.Contains(new Point(eventArgs.X, eventArgs.Y)))
                    return true;
            }
 
            return false;
        }
 
        protected override bool OnMouseUp(MouseEventArgs eventArgs)
        {
            Point clientPoint = new Point(eventArgs.X, eventArgs.Y);
 
            Refresh();
            UpdateTransparency(clientPoint);
 
            bool retval = false;
            if ((eventArgs.Button & MouseButtons.Left) > 0)
            {
                for (int i = this.actions.Count - 1; i >= 0; i--)
                {
                    DynamicAction action = this.actions[i];
                    Rectangle actionBounds = GetActionBounds(i);
                    if (actionBounds.Contains(clientPoint))
                    {
                        for (int j = 0; j < action.Buttons.Count; j++)
                        {
                            ActionButton actionButton = action.Buttons[j];
 
                            if (actionButton.State != ActionButton.States.Disabled)
                            {
                                Rectangle buttonBounds = GetButtonBounds(i, j);
 
                                if (buttonBounds.Contains(clientPoint) && action.ActionType != DynamicAction.ActionTypes.TwoState)
                                    actionButton.State = ActionButton.States.Highlight;
                                else if (actionButton.State == ActionButton.States.Highlight)
                                    actionButton.State = ActionButton.States.Normal;
                            }
                        }
 
                        retval = true;
                    }
                }
            }
 
            if (IsButtonDragged)
                SetDraggedButton(-1, -1);
 
            return retval;
        }
 
        protected override bool OnMouseLeave()
        {
            ParentView.ShowInfoTip(String.Empty);
            UpdateTransparency(Point.Empty);
            Refresh();
            return false;
        }
 
        protected override bool OnMouseCaptureChanged()
        {
            if (IsButtonDragged)
                SetDraggedButton(-1, -1);
            return false;
        }
 
        protected override bool OnPaintWorkflowAdornments(PaintEventArgs e, Rectangle viewPort, AmbientTheme ambientTheme)
        {
            for (int i = 0; i < this.actions.Count; i++)
            {
                GraphicsContainer graphicsState = e.Graphics.BeginContainer();
                Point actionLocation = GetActionBounds(i).Location;
                e.Graphics.TranslateTransform(actionLocation.X, actionLocation.Y);
                this.actions[i].Draw(e.Graphics);
                e.Graphics.EndContainer(graphicsState);
            }
            return false;
        }
        #endregion
 
        #region Helpers
        private void Refresh()
        {
            WorkflowView parentView = ParentView;
            for (int i = 0; i < this.actions.Count; i++)
                parentView.InvalidateClientRectangle(GetActionBounds(i));
        }
 
        private Rectangle GetActionBounds(int actionIndex)
        {
            Rectangle bounds = new Rectangle(Point.Empty, ParentView.ViewPortSize);
            DynamicAction action = this.actions[actionIndex];
 
            bounds.Inflate(-action.DockMargin.Width, -action.DockMargin.Height);
            return new Rectangle(ActivityDesignerPaint.GetRectangleFromAlignment(action.DockAlignment, bounds, action.Bounds.Size).Location, action.Bounds.Size);
        }
 
        private Rectangle GetButtonBounds(int actionIndex, int buttonIndex)
        {
            Rectangle bounds = GetActionBounds(actionIndex);
            Rectangle buttonBounds = this.actions[actionIndex].GetButtonBounds(buttonIndex);
            buttonBounds.Offset(bounds.Location);
            return buttonBounds;
        }
 
        private void UpdateTransparency(Point point)
        {
            for (int i = 0; i < this.actions.Count; i++)
            {
                float transparency = 0;
                if (!point.IsEmpty)
                {
                    Rectangle actionBounds = GetActionBounds(i);
                    if (actionBounds.Contains(point) || this.draggedActionIndex == i)
                    {
                        transparency = 1.0f;
                    }
                    else
                    {
                        Rectangle rectangle = ParentView.ViewPortRectangle;
                        double distance = DesignerGeometryHelper.DistanceFromPointToRectangle(point, actionBounds);
                        if (distance > rectangle.Width / 3 || distance > rectangle.Height / 3)
                        {
                            transparency = 0.3f;
                        }
                        else
                        {
                            //Uncomment the following code for fluctuating transparency
                            //1.0f - ((float)Convert.ToInt32(distance)) / Math.Max(ParentView.ViewPortSize.Width, ParentView.ViewPortSize.Height);
                            transparency = 1.0f;
                        }
                    }
                }
 
                this.actions[i].Transparency = transparency;
            }
        }
 
        private bool IsButtonDragged
        {
            get
            {
                return (this.draggedActionIndex >= 0 && this.draggedButtonIndex >= 0);
            }
        }
 
        private void SetDraggedButton(int actionIndex, int buttonIndex)
        {
            if (this.draggedActionIndex == actionIndex && this.draggedButtonIndex == buttonIndex)
                return;
 
            WorkflowView parentView = ParentView;
            if (this.draggedActionIndex >= 0 && this.draggedButtonIndex >= 0)
            {
                if (this.draggedActionIndex < this.actions.Count)
                    this.actions[this.draggedActionIndex].Buttons[this.draggedButtonIndex].State = ActionButton.States.Highlight;
 
                this.draggedActionIndex = -1;
                this.draggedButtonIndex = -1;
                parentView.Capture = false;
                UpdateTransparency(parentView.PointToClient(Control.MousePosition));
            }
 
            this.draggedActionIndex = actionIndex;
            this.draggedButtonIndex = buttonIndex;
 
            if (this.draggedActionIndex >= 0 && this.draggedButtonIndex >= 0)
                parentView.Capture = true;
        }
        #endregion
    }
    #endregion
 
    #region Class DynamicAction
    internal class DynamicAction : IDisposable
    {
        #region Members and constructor
        private static float DefaultTransparency = 0.0f;
        private static Size[] Sizes = new Size[] { new Size(20, 20), new Size(24, 24), new Size(28, 28), new Size(32, 32), new Size(36, 36) };
        private static Size[] Margins = new Size[] { new Size(1, 1), new Size(1, 1), new Size(2, 2), new Size(2, 2), new Size(3, 3) };
        internal enum ButtonSizes { Small = 0, SmallMedium = 1, Medium = 2, MediumLarge = 3, Large = 4 };
        internal enum ActionTypes { Standard = 1, TwoState = 2 };
 
        private ItemList<ActionButton> buttons = null;
        private ButtonSizes buttonSizeType = ButtonSizes.Medium;
        private DesignerContentAlignment dockAlignment = DesignerContentAlignment.TopLeft;
        private float minimumTransparency = DynamicAction.DefaultTransparency;
        private float transparency = DynamicAction.DefaultTransparency;
        private ActionTypes actionType = ActionTypes.Standard;
 
        private Size borderSize = new Size(2, 2);
        private Size dockMargin = DynamicAction.Sizes[(int)ButtonSizes.Medium];
        private Size buttonSize = DynamicAction.Sizes[(int)ButtonSizes.Medium];
        private Size margin = DynamicAction.Margins[(int)ButtonSizes.Medium];
 
        internal DynamicAction()
        {
            this.buttons = new ItemList<ActionButton>(this);
        }
 
        ~DynamicAction()
        {
            Dispose(false);
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        protected virtual void Dispose(bool disposing)
        {
            foreach (ActionButton button in this.buttons)
                ((IDisposable)button).Dispose();
            this.buttons.Clear();
        }
        #endregion
 
        #region Properties and Methods
        internal IList<ActionButton> Buttons
        {
            get
            {
                return this.buttons;
            }
        }
 
        internal Size DockMargin
        {
            get
            {
                return this.dockMargin;
            }
 
            set
            {
                this.dockMargin = value;
            }
        }
 
        internal ActionTypes ActionType
        {
            get
            {
                return this.actionType;
            }
        }
 
        internal ButtonSizes ButtonSize
        {
            get
            {
                return this.buttonSizeType;
            }
 
            set
            {
                if (this.buttonSizeType == value)
                    return;
 
                this.buttonSizeType = value;
                this.buttonSize = DynamicAction.Sizes[(int)this.buttonSizeType];
                this.margin = DynamicAction.Margins[(int)this.buttonSizeType];
            }
        }
 
        internal DesignerContentAlignment DockAlignment
        {
            get
            {
                return this.dockAlignment;
            }
 
            set
            {
                if (this.dockAlignment == value)
                    return;
 
                this.dockAlignment = value;
            }
        }
 
        internal float Transparency
        {
            get
            {
                return this.transparency;
            }
 
            set
            {
                if (this.transparency == value)
                    return;
 
                this.transparency = Math.Max(DynamicAction.DefaultTransparency, value);
            }
        }
 
        internal void Draw(Graphics graphics)
        {
            if (this.transparency == 0 || this.buttons.Count == 0)
                return;
 
            ActivityDesignerPaint.Draw3DButton(graphics, null, Bounds, this.transparency - 0.1f, ButtonState.Normal);
 
            for (int i = 0; i < this.buttons.Count; i++)
            {
                Rectangle buttonBounds = GetButtonBounds(i);
                ActionButton button = this.buttons[i];
                if (button.StateImages.Length == 1)
                {
                    Image buttonImage = button.StateImages[0];
                    if (button.State == ActionButton.States.Normal || button.State == ActionButton.States.Disabled)
                    {
                        buttonBounds.Inflate(-2, -2);
                        ActivityDesignerPaint.DrawImage(graphics, buttonImage, buttonBounds, new Rectangle(Point.Empty, buttonImage.Size), DesignerContentAlignment.Fill, transparency, (button.State == ActionButton.States.Disabled));
                    }
                    else
                    {
                        ButtonState state = (button.State == ActionButton.States.Highlight) ? ButtonState.Normal : ButtonState.Pushed;
                        ActivityDesignerPaint.Draw3DButton(graphics, buttonImage, buttonBounds, this.transparency, state);
                    }
                }
                else
                {
                    Image buttonImage = this.buttons[i].StateImages[(int)this.buttons[i].State];
                    buttonBounds.Inflate(-2, -2);
                    ActivityDesignerPaint.DrawImage(graphics, buttonImage, buttonBounds, new Rectangle(Point.Empty, buttonImage.Size), DesignerContentAlignment.Fill, this.transparency, false);
                }
            }
        }
 
        internal Rectangle Bounds
        {
            get
            {
                Size size = Size.Empty;
                int buttonCount = Math.Max(1, this.buttons.Count);
                size.Width = (2 * borderSize.Width) + (buttonCount * this.buttonSize.Width) + ((buttonCount + 1) * this.margin.Width);
                size.Height = (2 * borderSize.Height) + this.buttonSize.Height + (2 * this.margin.Height);
                return new Rectangle(Point.Empty, size);
            }
        }
 
        internal Rectangle GetButtonBounds(int buttonIndex)
        {
            if (buttonIndex < 0 || buttonIndex >= this.buttons.Count)
                throw new ArgumentOutOfRangeException("buttonIndex");
 
            Rectangle rectangle = Rectangle.Empty;
            rectangle.X = this.borderSize.Width + (buttonIndex * this.buttonSize.Width) + ((buttonIndex + 1) * this.margin.Width);
            rectangle.Y = this.borderSize.Height + this.margin.Height;
            rectangle.Size = this.buttonSize;
            return rectangle;
        }
        #endregion
    }
    #endregion
 
    #region Class ActionButton
    internal sealed class ActionButton : IDisposable
    {
        #region Members, Constructor and Destruction
        internal enum States { Normal = 0, Highlight = 1, Pressed = 2, Disabled = 3 };
 
        internal event EventHandler StateChanged;
 
        private Image[] stateImages = null;
        private string description = String.Empty;
        private States buttonState = States.Normal;
 
        internal ActionButton(Image[] stateImages)
        {
            StateImages = stateImages;
        }
 
        void IDisposable.Dispose()
        {
        }
        #endregion
 
        #region Properties and Methods
        internal Image[] StateImages
        {
            get
            {
                return this.stateImages;
            }
 
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
 
                if (value.Length != 1 && value.Length != 4)
                    throw new ArgumentException(SR.GetString(SR.Error_InvalidStateImages), "value");
 
                this.stateImages = value;
                foreach (Image image in this.stateImages)
                {
                    Bitmap bitmap = image as Bitmap;
                    if (bitmap != null)
                        bitmap.MakeTransparent(AmbientTheme.TransparentColor);
                }
            }
        }
 
        internal States State
        {
            get
            {
                return this.buttonState;
            }
 
            set
            {
                if (this.buttonState == value)
                    return;
 
                this.buttonState = value;
 
                if (StateChanged != null)
                    StateChanged(this, EventArgs.Empty);
            }
        }
 
        internal string Description
        {
            get
            {
                return this.description;
            }
 
            set
            {
                this.description = value;
            }
        }
        #endregion
    }
    #endregion
}