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