// Copyright (C) Stichting Deltares 2016. All rights reserved. // // This file is part of Ringtoets. // // Ringtoets 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. // // This program 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 this program. If not, see . // // All names, logos, and references to "Deltares" are registered trademarks of // Stichting Deltares and remain full property of Stichting Deltares at all times. // All rights reserved. using System; using System.Drawing; using System.Linq; using System.Windows.Forms; using Core.Common.Controls.TreeView; using Core.Common.Gui.ContextMenu; using Ringtoets.Common.Data.Calculation; using Ringtoets.Common.Data.FailureMechanism; using Ringtoets.Common.Forms.Helpers; using Ringtoets.Common.Forms.PresentationObjects; using Ringtoets.Common.Forms.Properties; using RingtoetsCommonDataResources = Ringtoets.Common.Data.Properties.Resources; using BaseResources = Core.Common.Base.Properties.Resources; namespace Ringtoets.Common.Forms.TreeNodeInfos { /// /// Factory for creating calculation related objects. /// public static class CalculationTreeNodeInfoFactory { /// /// Creates a object for a calculation group context of the type . /// /// The type of calculation group context to create a object for. /// The function for obtaining the child node objects. /// The function for obtaining the context menu strip. /// The action to perform on removing a node. /// A object. public static TreeNodeInfo CreateCalculationGroupContextTreeNodeInfo( Func childNodeObjects, Func contextMenuStrip, Action onNodeRemoved) where TCalculationGroupContext : ICalculationContext { return new TreeNodeInfo { Text = context => context.WrappedData.Name, Image = context => Resources.GeneralFolderIcon, EnsureVisibleOnCreate = (context, parent) => !(parent is IFailureMechanismContext), ChildNodeObjects = childNodeObjects, ContextMenuStrip = contextMenuStrip, CanRename = (context, parentData) => IsNestedGroup(parentData), OnNodeRenamed = (context, newName) => { context.WrappedData.Name = newName; context.NotifyObservers(); }, CanRemove = (context, parentData) => IsNestedGroup(parentData), OnNodeRemoved = onNodeRemoved, CanDrag = (context, parentData) => IsNestedGroup(parentData), CanInsert = CalculationGroupCanDropOrInsert, CanDrop = CalculationGroupCanDropOrInsert, OnDrop = CalculationGroupOnDrop }; } /// /// Creates a object for a calculation context of the type . /// /// The icon of the . /// The function for obtaining the child node objects. /// The function for obtaining the context menu strip. /// The action to perform on removing a node. /// The type of calculation context to create a object for. /// A object. public static TreeNodeInfo CreateCalculationContextTreeNodeInfo( Bitmap icon, Func childeNodeObjects, Func contextMenuStrip, Action onNodeRemoved) where TCalculationContext : ICalculationContext { return new TreeNodeInfo { Text = context => context.WrappedData.Name, Image = context => icon, EnsureVisibleOnCreate = (context, parent) => true, ChildNodeObjects = childeNodeObjects, ContextMenuStrip = contextMenuStrip, CanRename = (context, parent) => true, OnNodeRenamed = (context, newName) => { context.WrappedData.Name = newName; context.WrappedData.NotifyObservers(); }, CanRemove = (context, parentData) => CalculationContextCanRemove(context, parentData), OnNodeRemoved = onNodeRemoved, CanDrag = (context, parentData) => true }; } /// /// This method adds a context menu item for creating new calculation groups. /// /// The builder to add the context menu item to. /// The calculation group involved. public static void AddCreateCalculationGroupItem(IContextMenuBuilder builder, CalculationGroup calculationGroup) { var createCalculationGroupItem = new StrictContextMenuItem( Resources.CalculationGroup_Add_CalculationGroup, Resources.CalculationGroup_Add_CalculationGroup_Tooltip, Resources.AddFolderIcon, (o, args) => { var calculation = new CalculationGroup { Name = NamingHelper.GetUniqueName(calculationGroup.Children, RingtoetsCommonDataResources.CalculationGroup_DefaultName, c => c.Name) }; calculationGroup.Children.Add(calculation); calculationGroup.NotifyObservers(); }); builder.AddCustomItem(createCalculationGroupItem); } /// /// This method adds a context menu item for creating new calculations. /// /// The builder to add the context menu item to. /// The calculation group context involved. /// The action for adding a calculation to the calculation group. public static void AddCreateCalculationItem(IContextMenuBuilder builder, TCalculationGroupContext calculationGroupContext, Action addCalculation) where TCalculationGroupContext : ICalculationContext { var createCalculationItem = new StrictContextMenuItem( Resources.CalculationGroup_Add_Calculation, Resources.CalculationGroup_Add_Calculation_Tooltip, Resources.FailureMechanismIcon, (o, args) => { addCalculation(calculationGroupContext); }); builder.AddCustomItem(createCalculationItem); } /// /// This method adds a context menu item for clearing the output of all calculations in the calculation group. /// /// The builder to add the context menu item to. /// The calculation group involved. public static void AddClearAllCalculationOutputInGroupItem(IContextMenuBuilder builder, CalculationGroup calculationGroup) { var clearAllItem = new StrictContextMenuItem( Resources.Clear_all_output, Resources.CalculationGroup_ClearOutput_ToolTip, Resources.ClearIcon, (o, args) => { if (MessageBox.Show(Resources.CalculationGroup_ClearOutput_Are_you_sure_clear_all_output, BaseResources.Confirm, MessageBoxButtons.OKCancel) != DialogResult.OK) { return; } foreach (var calc in calculationGroup.GetCalculations().Where(c => c.HasOutput)) { calc.ClearOutput(); calc.NotifyObservers(); } }); if (!calculationGroup.GetCalculations().Any(c => c.HasOutput)) { clearAllItem.Enabled = false; clearAllItem.ToolTipText = Resources.CalculationGroup_ClearOutput_No_calculation_with_output_to_clear; } builder.AddCustomItem(clearAllItem); } /// /// This method adds a context menu item for performing all calculations in the calculation group. /// /// The builder to add the context menu item to. /// The calculation group involved. /// The action that performs all calculations. public static void AddPerformAllCalculationsInGroupItem(IContextMenuBuilder builder, CalculationGroup calculationGroup, Action calculateAll) { var performAllItem = new StrictContextMenuItem( Resources.Calculate_all, Resources.CalculationGroup_CalculateAll_ToolTip, Resources.CalculateAllIcon, (o, args) => { calculateAll(calculationGroup); }); if (!calculationGroup.GetCalculations().Any()) { performAllItem.Enabled = false; performAllItem.ToolTipText = Resources.CalculationGroup_CalculateAll_No_calculations_to_run; } builder.AddCustomItem(performAllItem); } # region Helper methods for CreateCalculationGroupContextTreeNodeInfo private static bool IsNestedGroup(object parentData) { return parentData is ICalculationContext; } private static bool CalculationGroupCanDropOrInsert(object draggedData, object targetData) { var calculationContext = draggedData as ICalculationContext; return calculationContext != null && ReferenceEquals(calculationContext.FailureMechanism, ((ICalculationContext) targetData).FailureMechanism); } private static void CalculationGroupOnDrop(object droppedData, object newParentData, object oldParentData, int position, TreeViewControl treeViewControl) { ICalculationBase calculationItem = ((ICalculationContext) droppedData).WrappedData; var originalOwnerContext = oldParentData as ICalculationContext; var targetContext = newParentData as ICalculationContext; if (calculationItem != null && originalOwnerContext != null && targetContext != null) { var sourceCalculationGroup = originalOwnerContext.WrappedData; var targetCalculationGroup = targetContext.WrappedData; var isMoveWithinSameContainer = ReferenceEquals(sourceCalculationGroup, targetCalculationGroup); DroppingCalculationInContainerStrategy dropHandler = GetDragDropStrategy(isMoveWithinSameContainer, sourceCalculationGroup, targetCalculationGroup); dropHandler.Execute(droppedData, calculationItem, position, treeViewControl); } } private static DroppingCalculationInContainerStrategy GetDragDropStrategy(bool isMoveWithinSameContainer, CalculationGroup sourceCalculationGroup, CalculationGroup targetCalculationGroup) { return isMoveWithinSameContainer ? (DroppingCalculationInContainerStrategy) new DroppingCalculationWithinSameContainer(sourceCalculationGroup, targetCalculationGroup) : new DroppingCalculationToNewContainer(sourceCalculationGroup, targetCalculationGroup); } # region Nested types: DroppingCalculationInContainerStrategy and implementations /// /// Strategy pattern implementation for dealing with drag and drop of a /// onto data. /// private abstract class DroppingCalculationInContainerStrategy { protected readonly CalculationGroup targetCalculationGroup; private readonly CalculationGroup sourceCalculationGroup; protected DroppingCalculationInContainerStrategy(CalculationGroup sourceCalculationGroup, CalculationGroup targetCalculationGroup) { this.sourceCalculationGroup = sourceCalculationGroup; this.targetCalculationGroup = targetCalculationGroup; } /// /// Performs the drag and drop operation. /// /// The dragged data. /// The calculation item wrapped by . /// The index of the new position within the new owner's collection. /// The tree view control which is at stake. public virtual void Execute(object draggedData, ICalculationBase calculationBase, int newPosition, TreeViewControl treeViewControl) { MoveCalculationItemToNewOwner(calculationBase, newPosition); NotifyObservers(); } /// /// Moves the instance to its new location. /// /// The instance to be relocated. /// The index in the new /// owner within its . protected void MoveCalculationItemToNewOwner(ICalculationBase calculationBase, int position) { sourceCalculationGroup.Children.Remove(calculationBase); targetCalculationGroup.Children.Insert(position, calculationBase); } /// /// Notifies observers of the change in state. /// protected virtual void NotifyObservers() { sourceCalculationGroup.NotifyObservers(); } } /// /// Strategy implementation for rearranging the order of an /// within a through a drag and drop action. /// private class DroppingCalculationWithinSameContainer : DroppingCalculationInContainerStrategy { /// /// Initializes a new instance of the class. /// /// The calculation group that is the target of the drag and drop operation. /// The calculation group that is the original owner of the dragged item. public DroppingCalculationWithinSameContainer(CalculationGroup sourceCalculationGroup, CalculationGroup targetCalculationGroup) : base(sourceCalculationGroup, targetCalculationGroup) {} } /// /// Strategy implementation for moving an from /// one to another using a drag and drop action. /// private class DroppingCalculationToNewContainer : DroppingCalculationInContainerStrategy { /// /// Initializes a new instance of the class. /// /// The calculation group that is the original owner of the dragged item. /// The calculation group that is the target of the drag and drop operation. public DroppingCalculationToNewContainer(CalculationGroup sourceCalculationGroup, CalculationGroup targetCalculationGroup) : base(sourceCalculationGroup, targetCalculationGroup) {} public override void Execute(object draggedData, ICalculationBase calculationBase, int newPosition, TreeViewControl treeViewControl) { MoveCalculationItemToNewOwner(calculationBase, newPosition); NotifyObservers(); // Try to start a name edit action when an item with the same name was already present if (targetCalculationGroup.Children.Except(new[] { calculationBase }).Any(c => c.Name.Equals(calculationBase.Name))) { treeViewControl.TryRenameNodeForData(draggedData); } } protected override void NotifyObservers() { base.NotifyObservers(); targetCalculationGroup.NotifyObservers(); } } # endregion # endregion #region Helper methods for CreateCalculationContextTreeNodeInfo private static bool CalculationContextCanRemove(ICalculationContext calculationContext, object parentNodeData) { var calculationGroupContext = parentNodeData as ICalculationContext; return calculationGroupContext != null && calculationGroupContext.WrappedData.Children.Contains(calculationContext.WrappedData); } #endregion } }