1 namespace System.Workflow.ComponentModel.Design
2 {
3     using System;
4     using System.Text;
5     using System.Drawing;
6     using System.Windows.Forms;
7     using System.Drawing.Drawing2D;
8     using System.Collections.Generic;
9     using System.ComponentModel.Design;
10 
11     #region Class DynamicActionMessageFilter
12     //Behavior needs coordinates in client coordinate system
13     internal sealed class DynamicActionMessageFilter : WorkflowDesignerMessageFilter
14     {
15         #region Members, Construction and Destruction
16         private List<DynamicAction> actions = new List<DynamicAction>();
17 
18         private int draggedButtonIndex = -1;
19         private int draggedActionIndex = -1;
20 
21         private bool infoTipSet = false;
22 
DynamicActionMessageFilter()23         internal DynamicActionMessageFilter()
24         {
25         }
26         #endregion
27 
28         #region Properties and Methods
AddAction(DynamicAction action)29         internal void AddAction(DynamicAction action)
30         {
31             if (action == null)
32                 throw new ArgumentNullException("action");
33 
34             if (!this.actions.Contains(action))
35             {
36                 if (IsButtonDragged)
37                     SetDraggedButton(-1, -1);
38 
39                 this.actions.Add(action);
40                 RefreshAction(action);
41             }
42         }
43 
ActionExists(DynamicAction action)44         internal bool ActionExists(DynamicAction action)
45         {
46             if (action == null)
47                 throw new ArgumentNullException("action");
48 
49             return this.actions.Contains(action);
50         }
51 
RemoveAction(DynamicAction action)52         internal void RemoveAction(DynamicAction action)
53         {
54             if (action == null)
55                 throw new ArgumentNullException("action");
56 
57             if (this.actions.Contains(action))
58             {
59                 if (IsButtonDragged)
60                     SetDraggedButton(-1, -1);
61 
62                 RefreshAction(action);
63                 this.actions.Remove(action);
64             }
65         }
66 
RefreshAction(DynamicAction action)67         internal void RefreshAction(DynamicAction action)
68         {
69             if (action == null)
70                 throw new ArgumentNullException("action");
71 
72             int actionIndex = this.actions.IndexOf(action);
73             if (actionIndex >= 0)
74                 ParentView.InvalidateClientRectangle(GetActionBounds(actionIndex));
75         }
76         #endregion
77 
78         #region Behavior Overrides
Initialize(WorkflowView parentView)79         protected override void Initialize(WorkflowView parentView)
80         {
81             base.Initialize(parentView);
82 
83             IServiceContainer serviceContainer = GetService(typeof(IServiceContainer)) as IServiceContainer;
84             if (serviceContainer != null)
85             {
86                 serviceContainer.RemoveService(typeof(DynamicActionMessageFilter));
87                 serviceContainer.AddService(typeof(DynamicActionMessageFilter), this);
88             }
89         }
90 
Dispose(bool disposing)91         protected override void Dispose(bool disposing)
92         {
93             try
94             {
95                 IServiceContainer serviceContainer = GetService(typeof(IServiceContainer)) as IServiceContainer;
96                 if (serviceContainer != null)
97                     serviceContainer.RemoveService(typeof(DynamicActionMessageFilter));
98             }
99             finally
100             {
101                 base.Dispose(disposing);
102             }
103         }
104 
OnMouseEnter(MouseEventArgs eventArgs)105         protected override bool OnMouseEnter(MouseEventArgs eventArgs)
106         {
107             UpdateTransparency(new Point(eventArgs.X, eventArgs.Y));
108             Refresh();
109             return false;
110         }
111 
OnMouseDown(MouseEventArgs eventArgs)112         protected override bool OnMouseDown(MouseEventArgs eventArgs)
113         {
114             Point clientPoint = new Point(eventArgs.X, eventArgs.Y);
115 
116             Refresh();
117             UpdateTransparency(clientPoint);
118 
119             bool retval = false;
120             if ((eventArgs.Button & MouseButtons.Left) > 0)
121             {
122                 for (int i = this.actions.Count - 1; i >= 0; i--)
123                 {
124                     DynamicAction action = this.actions[i];
125                     Rectangle actionBounds = GetActionBounds(i);
126                     if (actionBounds.Contains(clientPoint))
127                     {
128                         //If we clicked on disabled button then no further action is needed
129                         for (int j = 0; j < action.Buttons.Count; j++)
130                         {
131                             Rectangle buttonBounds = GetButtonBounds(i, j);
132                             if (buttonBounds.Contains(clientPoint) && action.Buttons[j].State == ActionButton.States.Disabled)
133                                 return true;
134                         }
135 
136                         //Now check all the buttons and update their states
137                         for (int j = 0; j < action.Buttons.Count; j++)
138                         {
139                             ActionButton actionButton = action.Buttons[j];
140                             if (actionButton.State != ActionButton.States.Disabled)
141                             {
142                                 Rectangle buttonBounds = GetButtonBounds(i, j);
143                                 if (buttonBounds.Contains(clientPoint))
144                                 {
145                                     actionButton.State = ActionButton.States.Pressed;
146                                     if (action.ActionType != DynamicAction.ActionTypes.TwoState)
147                                         SetDraggedButton(i, j);
148                                 }
149                                 else if (action.ActionType == DynamicAction.ActionTypes.TwoState)
150                                 {
151                                     actionButton.State = ActionButton.States.Normal;
152                                 }
153                             }
154                         }
155 
156                         retval = true;
157                     }
158                 }
159             }
160 
161             return retval;
162         }
163 
OnMouseMove(MouseEventArgs eventArgs)164         protected override bool OnMouseMove(MouseEventArgs eventArgs)
165         {
166             Point clientPoint = new Point(eventArgs.X, eventArgs.Y);
167 
168             Refresh();
169             UpdateTransparency(clientPoint);
170 
171             string infoTip = String.Empty;
172             bool retval = IsButtonDragged;
173             if (!IsButtonDragged)
174             {
175                 for (int i = this.actions.Count - 1; i >= 0; i--)
176                 {
177                     DynamicAction action = this.actions[i];
178                     Rectangle actionBounds = GetActionBounds(i);
179 
180                     for (int j = 0; j < action.Buttons.Count; j++)
181                     {
182                         ActionButton actionButton = action.Buttons[j];
183 
184                         if (actionBounds.Contains(clientPoint))
185                         {
186                             Rectangle buttonBounds = GetButtonBounds(i, j);
187                             bool buttonContainsPoint = buttonBounds.Contains(clientPoint);
188 
189                             if (buttonContainsPoint && infoTip.Length == 0)
190                                 infoTip = actionButton.Description;
191 
192                             if (actionButton.State != ActionButton.States.Disabled &&
193                                 actionButton.State != ActionButton.States.Pressed)
194                             {
195                                 if (buttonContainsPoint)
196                                     actionButton.State = ActionButton.States.Highlight;
197                                 else
198                                     actionButton.State = ActionButton.States.Normal;
199                             }
200 
201                             retval = true;
202                         }
203                         else
204                         {
205                             if (actionButton.State == ActionButton.States.Highlight)
206                                 actionButton.State = ActionButton.States.Normal;
207                         }
208                     }
209                 }
210             }
211 
212             WorkflowView parentView = ParentView;
213             if (infoTip.Length > 0)
214             {
215                 this.infoTipSet = true;
216                 parentView.ShowInfoTip(infoTip);
217             }
218             else if (this.infoTipSet)
219             {
220                 parentView.ShowInfoTip(String.Empty);
221                 this.infoTipSet = false;
222             }
223 
224             return retval;
225         }
226 
OnMouseDoubleClick(MouseEventArgs eventArgs)227         protected override bool OnMouseDoubleClick(MouseEventArgs eventArgs)
228         {
229             for (int i = this.actions.Count - 1; i >= 0; i--)
230             {
231                 DynamicAction action = this.actions[i];
232                 Rectangle actionBounds = GetActionBounds(i);
233                 if (actionBounds.Contains(new Point(eventArgs.X, eventArgs.Y)))
234                     return true;
235             }
236 
237             return false;
238         }
239 
OnMouseUp(MouseEventArgs eventArgs)240         protected override bool OnMouseUp(MouseEventArgs eventArgs)
241         {
242             Point clientPoint = new Point(eventArgs.X, eventArgs.Y);
243 
244             Refresh();
245             UpdateTransparency(clientPoint);
246 
247             bool retval = false;
248             if ((eventArgs.Button & MouseButtons.Left) > 0)
249             {
250                 for (int i = this.actions.Count - 1; i >= 0; i--)
251                 {
252                     DynamicAction action = this.actions[i];
253                     Rectangle actionBounds = GetActionBounds(i);
254                     if (actionBounds.Contains(clientPoint))
255                     {
256                         for (int j = 0; j < action.Buttons.Count; j++)
257                         {
258                             ActionButton actionButton = action.Buttons[j];
259 
260                             if (actionButton.State != ActionButton.States.Disabled)
261                             {
262                                 Rectangle buttonBounds = GetButtonBounds(i, j);
263 
264                                 if (buttonBounds.Contains(clientPoint) && action.ActionType != DynamicAction.ActionTypes.TwoState)
265                                     actionButton.State = ActionButton.States.Highlight;
266                                 else if (actionButton.State == ActionButton.States.Highlight)
267                                     actionButton.State = ActionButton.States.Normal;
268                             }
269                         }
270 
271                         retval = true;
272                     }
273                 }
274             }
275 
276             if (IsButtonDragged)
277                 SetDraggedButton(-1, -1);
278 
279             return retval;
280         }
281 
OnMouseLeave()282         protected override bool OnMouseLeave()
283         {
284             ParentView.ShowInfoTip(String.Empty);
285             UpdateTransparency(Point.Empty);
286             Refresh();
287             return false;
288         }
289 
OnMouseCaptureChanged()290         protected override bool OnMouseCaptureChanged()
291         {
292             if (IsButtonDragged)
293                 SetDraggedButton(-1, -1);
294             return false;
295         }
296 
OnPaintWorkflowAdornments(PaintEventArgs e, Rectangle viewPort, AmbientTheme ambientTheme)297         protected override bool OnPaintWorkflowAdornments(PaintEventArgs e, Rectangle viewPort, AmbientTheme ambientTheme)
298         {
299             for (int i = 0; i < this.actions.Count; i++)
300             {
301                 GraphicsContainer graphicsState = e.Graphics.BeginContainer();
302                 Point actionLocation = GetActionBounds(i).Location;
303                 e.Graphics.TranslateTransform(actionLocation.X, actionLocation.Y);
304                 this.actions[i].Draw(e.Graphics);
305                 e.Graphics.EndContainer(graphicsState);
306             }
307             return false;
308         }
309         #endregion
310 
311         #region Helpers
Refresh()312         private void Refresh()
313         {
314             WorkflowView parentView = ParentView;
315             for (int i = 0; i < this.actions.Count; i++)
316                 parentView.InvalidateClientRectangle(GetActionBounds(i));
317         }
318 
GetActionBounds(int actionIndex)319         private Rectangle GetActionBounds(int actionIndex)
320         {
321             Rectangle bounds = new Rectangle(Point.Empty, ParentView.ViewPortSize);
322             DynamicAction action = this.actions[actionIndex];
323 
324             bounds.Inflate(-action.DockMargin.Width, -action.DockMargin.Height);
325             return new Rectangle(ActivityDesignerPaint.GetRectangleFromAlignment(action.DockAlignment, bounds, action.Bounds.Size).Location, action.Bounds.Size);
326         }
327 
GetButtonBounds(int actionIndex, int buttonIndex)328         private Rectangle GetButtonBounds(int actionIndex, int buttonIndex)
329         {
330             Rectangle bounds = GetActionBounds(actionIndex);
331             Rectangle buttonBounds = this.actions[actionIndex].GetButtonBounds(buttonIndex);
332             buttonBounds.Offset(bounds.Location);
333             return buttonBounds;
334         }
335 
UpdateTransparency(Point point)336         private void UpdateTransparency(Point point)
337         {
338             for (int i = 0; i < this.actions.Count; i++)
339             {
340                 float transparency = 0;
341                 if (!point.IsEmpty)
342                 {
343                     Rectangle actionBounds = GetActionBounds(i);
344                     if (actionBounds.Contains(point) || this.draggedActionIndex == i)
345                     {
346                         transparency = 1.0f;
347                     }
348                     else
349                     {
350                         Rectangle rectangle = ParentView.ViewPortRectangle;
351                         double distance = DesignerGeometryHelper.DistanceFromPointToRectangle(point, actionBounds);
352                         if (distance > rectangle.Width / 3 || distance > rectangle.Height / 3)
353                         {
354                             transparency = 0.3f;
355                         }
356                         else
357                         {
358                             //Uncomment the following code for fluctuating transparency
359                             //1.0f - ((float)Convert.ToInt32(distance)) / Math.Max(ParentView.ViewPortSize.Width, ParentView.ViewPortSize.Height);
360                             transparency = 1.0f;
361                         }
362                     }
363                 }
364 
365                 this.actions[i].Transparency = transparency;
366             }
367         }
368 
369         private bool IsButtonDragged
370         {
371             get
372             {
373                 return (this.draggedActionIndex >= 0 && this.draggedButtonIndex >= 0);
374             }
375         }
376 
SetDraggedButton(int actionIndex, int buttonIndex)377         private void SetDraggedButton(int actionIndex, int buttonIndex)
378         {
379             if (this.draggedActionIndex == actionIndex && this.draggedButtonIndex == buttonIndex)
380                 return;
381 
382             WorkflowView parentView = ParentView;
383             if (this.draggedActionIndex >= 0 && this.draggedButtonIndex >= 0)
384             {
385                 if (this.draggedActionIndex < this.actions.Count)
386                     this.actions[this.draggedActionIndex].Buttons[this.draggedButtonIndex].State = ActionButton.States.Highlight;
387 
388                 this.draggedActionIndex = -1;
389                 this.draggedButtonIndex = -1;
390                 parentView.Capture = false;
391                 UpdateTransparency(parentView.PointToClient(Control.MousePosition));
392             }
393 
394             this.draggedActionIndex = actionIndex;
395             this.draggedButtonIndex = buttonIndex;
396 
397             if (this.draggedActionIndex >= 0 && this.draggedButtonIndex >= 0)
398                 parentView.Capture = true;
399         }
400         #endregion
401     }
402     #endregion
403 
404     #region Class DynamicAction
405     internal class DynamicAction : IDisposable
406     {
407         #region Members and constructor
408         private static float DefaultTransparency = 0.0f;
409         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) };
410         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) };
411         internal enum ButtonSizes { Small = 0, SmallMedium = 1, Medium = 2, MediumLarge = 3, Large = 4 };
412         internal enum ActionTypes { Standard = 1, TwoState = 2 };
413 
414         private ItemList<ActionButton> buttons = null;
415         private ButtonSizes buttonSizeType = ButtonSizes.Medium;
416         private DesignerContentAlignment dockAlignment = DesignerContentAlignment.TopLeft;
417         private float minimumTransparency = DynamicAction.DefaultTransparency;
418         private float transparency = DynamicAction.DefaultTransparency;
419         private ActionTypes actionType = ActionTypes.Standard;
420 
421         private Size borderSize = new Size(2, 2);
422         private Size dockMargin = DynamicAction.Sizes[(int)ButtonSizes.Medium];
423         private Size buttonSize = DynamicAction.Sizes[(int)ButtonSizes.Medium];
424         private Size margin = DynamicAction.Margins[(int)ButtonSizes.Medium];
425 
DynamicAction()426         internal DynamicAction()
427         {
428             this.buttons = new ItemList<ActionButton>(this);
429         }
430 
~DynamicAction()431         ~DynamicAction()
432         {
433             Dispose(false);
434         }
435 
Dispose()436         public void Dispose()
437         {
438             Dispose(true);
439             GC.SuppressFinalize(this);
440         }
441 
Dispose(bool disposing)442         protected virtual void Dispose(bool disposing)
443         {
444             foreach (ActionButton button in this.buttons)
445                 ((IDisposable)button).Dispose();
446             this.buttons.Clear();
447         }
448         #endregion
449 
450         #region Properties and Methods
451         internal IList<ActionButton> Buttons
452         {
453             get
454             {
455                 return this.buttons;
456             }
457         }
458 
459         internal Size DockMargin
460         {
461             get
462             {
463                 return this.dockMargin;
464             }
465 
466             set
467             {
468                 this.dockMargin = value;
469             }
470         }
471 
472         internal ActionTypes ActionType
473         {
474             get
475             {
476                 return this.actionType;
477             }
478         }
479 
480         internal ButtonSizes ButtonSize
481         {
482             get
483             {
484                 return this.buttonSizeType;
485             }
486 
487             set
488             {
489                 if (this.buttonSizeType == value)
490                     return;
491 
492                 this.buttonSizeType = value;
493                 this.buttonSize = DynamicAction.Sizes[(int)this.buttonSizeType];
494                 this.margin = DynamicAction.Margins[(int)this.buttonSizeType];
495             }
496         }
497 
498         internal DesignerContentAlignment DockAlignment
499         {
500             get
501             {
502                 return this.dockAlignment;
503             }
504 
505             set
506             {
507                 if (this.dockAlignment == value)
508                     return;
509 
510                 this.dockAlignment = value;
511             }
512         }
513 
514         internal float Transparency
515         {
516             get
517             {
518                 return this.transparency;
519             }
520 
521             set
522             {
523                 if (this.transparency == value)
524                     return;
525 
526                 this.transparency = Math.Max(DynamicAction.DefaultTransparency, value);
527             }
528         }
529 
Draw(Graphics graphics)530         internal void Draw(Graphics graphics)
531         {
532             if (this.transparency == 0 || this.buttons.Count == 0)
533                 return;
534 
535             ActivityDesignerPaint.Draw3DButton(graphics, null, Bounds, this.transparency - 0.1f, ButtonState.Normal);
536 
537             for (int i = 0; i < this.buttons.Count; i++)
538             {
539                 Rectangle buttonBounds = GetButtonBounds(i);
540                 ActionButton button = this.buttons[i];
541                 if (button.StateImages.Length == 1)
542                 {
543                     Image buttonImage = button.StateImages[0];
544                     if (button.State == ActionButton.States.Normal || button.State == ActionButton.States.Disabled)
545                     {
546                         buttonBounds.Inflate(-2, -2);
547                         ActivityDesignerPaint.DrawImage(graphics, buttonImage, buttonBounds, new Rectangle(Point.Empty, buttonImage.Size), DesignerContentAlignment.Fill, transparency, (button.State == ActionButton.States.Disabled));
548                     }
549                     else
550                     {
551                         ButtonState state = (button.State == ActionButton.States.Highlight) ? ButtonState.Normal : ButtonState.Pushed;
552                         ActivityDesignerPaint.Draw3DButton(graphics, buttonImage, buttonBounds, this.transparency, state);
553                     }
554                 }
555                 else
556                 {
557                     Image buttonImage = this.buttons[i].StateImages[(int)this.buttons[i].State];
558                     buttonBounds.Inflate(-2, -2);
559                     ActivityDesignerPaint.DrawImage(graphics, buttonImage, buttonBounds, new Rectangle(Point.Empty, buttonImage.Size), DesignerContentAlignment.Fill, this.transparency, false);
560                 }
561             }
562         }
563 
564         internal Rectangle Bounds
565         {
566             get
567             {
568                 Size size = Size.Empty;
569                 int buttonCount = Math.Max(1, this.buttons.Count);
570                 size.Width = (2 * borderSize.Width) + (buttonCount * this.buttonSize.Width) + ((buttonCount + 1) * this.margin.Width);
571                 size.Height = (2 * borderSize.Height) + this.buttonSize.Height + (2 * this.margin.Height);
572                 return new Rectangle(Point.Empty, size);
573             }
574         }
575 
GetButtonBounds(int buttonIndex)576         internal Rectangle GetButtonBounds(int buttonIndex)
577         {
578             if (buttonIndex < 0 || buttonIndex >= this.buttons.Count)
579                 throw new ArgumentOutOfRangeException("buttonIndex");
580 
581             Rectangle rectangle = Rectangle.Empty;
582             rectangle.X = this.borderSize.Width + (buttonIndex * this.buttonSize.Width) + ((buttonIndex + 1) * this.margin.Width);
583             rectangle.Y = this.borderSize.Height + this.margin.Height;
584             rectangle.Size = this.buttonSize;
585             return rectangle;
586         }
587         #endregion
588     }
589     #endregion
590 
591     #region Class ActionButton
592     internal sealed class ActionButton : IDisposable
593     {
594         #region Members, Constructor and Destruction
595         internal enum States { Normal = 0, Highlight = 1, Pressed = 2, Disabled = 3 };
596 
597         internal event EventHandler StateChanged;
598 
599         private Image[] stateImages = null;
600         private string description = String.Empty;
601         private States buttonState = States.Normal;
602 
ActionButton(Image[] stateImages)603         internal ActionButton(Image[] stateImages)
604         {
605             StateImages = stateImages;
606         }
607 
IDisposable.Dispose()608         void IDisposable.Dispose()
609         {
610         }
611         #endregion
612 
613         #region Properties and Methods
614         internal Image[] StateImages
615         {
616             get
617             {
618                 return this.stateImages;
619             }
620 
621             set
622             {
623                 if (value == null)
624                     throw new ArgumentNullException("value");
625 
626                 if (value.Length != 1 && value.Length != 4)
627                     throw new ArgumentException(SR.GetString(SR.Error_InvalidStateImages), "value");
628 
629                 this.stateImages = value;
630                 foreach (Image image in this.stateImages)
631                 {
632                     Bitmap bitmap = image as Bitmap;
633                     if (bitmap != null)
634                         bitmap.MakeTransparent(AmbientTheme.TransparentColor);
635                 }
636             }
637         }
638 
639         internal States State
640         {
641             get
642             {
643                 return this.buttonState;
644             }
645 
646             set
647             {
648                 if (this.buttonState == value)
649                     return;
650 
651                 this.buttonState = value;
652 
653                 if (StateChanged != null)
654                     StateChanged(this, EventArgs.Empty);
655             }
656         }
657 
658         internal string Description
659         {
660             get
661             {
662                 return this.description;
663             }
664 
665             set
666             {
667                 this.description = value;
668             }
669         }
670         #endregion
671     }
672     #endregion
673 }
674