From 1ed2e4e634e69455246fbe3565cf6ed8b3b7a6ea Mon Sep 17 00:00:00 2001 From: Arne Maes Date: Thu, 5 Jun 2025 12:14:03 +0200 Subject: [PATCH 1/2] Merged the panel changes from the preview version to this one. This has one breaking change in case some script depends on the CurrentDialog property of the InteractiveController as that one has been changed from the concrete Dialog class to the interface IDialog. --- .../Components/Component.cs | 9 + .../Components/IComponent.cs | 15 + .../Interfaces/IIsReadonlyWidget.cs | 13 - .../Components/Panels/FormPanel.cs | 192 ++++ .../Components/Panels/GridPanel.cs | 268 ++++++ .../Components/Panels/IFormPanel.cs | 49 + .../Components/Panels/IGridPanel.cs | 157 ++++ .../Components/Panels/IPanel.cs | 146 +++ .../Components/Panels/IStackPanel.cs | 57 ++ .../Components/Panels/Panel.cs | 182 ++++ .../Components/Panels/StackPanel.cs | 338 +++++++ .../Components/{ => Widgets}/Button.cs | 0 .../Components/{ => Widgets}/Calendar.cs | 0 .../Components/{ => Widgets}/CheckBox.cs | 0 .../Components/{ => Widgets}/CheckBoxList.cs | 0 .../{ => Widgets}/CheckBoxListBase.cs | 0 .../{ => Widgets}/CollapseButton.cs | 0 .../{ => Widgets}/DateTimePicker.cs | 0 .../{ => Widgets}/DownloadButton.cs | 0 .../Components/{ => Widgets}/DropDown.cs | 0 .../Components/{ => Widgets}/DropDownBase.cs | 0 .../Components/{ => Widgets}/FileSelector.cs | 0 .../{ => Widgets}/Generics/EnumDropDown.cs | 0 .../Generics/EnumRadioButtonList.cs | 0 .../Generics/GenericCheckBoxList.cs | 0 .../{ => Widgets}/Generics/GenericDropDown.cs | 0 .../Generics/GenericRadioButtonList.cs | 0 .../{ => Widgets/Generics}/Option.cs | 0 .../Generics}/OptionCollection.cs | 0 .../Components/{ => Widgets}/Hyperlink.cs | 0 .../{ => Widgets}/InteractiveWidget.cs | 2 +- .../{ => Widgets}/Interfaces/ICheckBoxList.cs | 0 .../Interfaces/ICheckBoxListBase.cs | 0 .../{ => Widgets}/Interfaces/IDropDown.cs | 0 .../{ => Widgets}/Interfaces/IDropDownBase.cs | 0 .../Widgets/Interfaces/IInteractiveWidget.cs | 15 + .../Widgets/Interfaces/IIsReadonlyWidget.cs | 13 + .../Components/Widgets/Interfaces/ILabel.cs | 28 + .../{ => Widgets}/Interfaces/IOptionWidget.cs | 0 .../Interfaces/IRadioButtonList.cs | 0 .../Interfaces/IRadioButtonListBase.cs | 0 .../Interfaces/IValidationWidget.cs | 0 .../Components/Widgets/Interfaces/IWidget.cs | 93 ++ .../Components/{ => Widgets}/Label.cs | 2 +- .../Components/{ => Widgets}/Numeric.cs | 0 .../Components/{ => Widgets}/Parameter.cs | 0 .../Components/{ => Widgets}/PasswordBox.cs | 0 .../{ => Widgets}/RadioButtonList.cs | 0 .../{ => Widgets}/RadioButtonListBase.cs | 0 .../Components/{ => Widgets}/Section.cs | 107 +-- .../Components/{ => Widgets}/TextBox.cs | 0 .../Components/{ => Widgets}/Time.cs | 2 - .../Components/{ => Widgets}/TimePicker.cs | 2 - .../{ => Widgets}/TimePickerBase.cs | 0 .../Components/{ => Widgets}/TreeView.cs | 2 - .../Components/{ => Widgets}/WhiteSpace.cs | 0 .../Components/{ => Widgets}/Widget.cs | 87 +- .../Dialogs/Dialog [Obsolete].cs | 379 ++++++++ .../Dialogs/Dialog.cs | 866 +++++------------- .../Dialogs/ExceptionDialog.cs | 6 +- .../Dialogs/IDialog.cs | 182 ++++ .../Dialogs/MessageDialog.cs | 6 +- .../Dialogs/OkCancelDialog.cs | 28 +- .../Dialogs/ProgressDialog.cs | 17 +- .../Dialogs/YesNoDialog.cs | 28 +- .../Exceptions/OverlappingWidgetsException.cs | 60 ++ .../InteractiveAutomationToolkit.csproj | 3 + .../InteractiveController.cs | 25 +- .../Layout/Direction.cs | 11 + .../Layout/ILayout.cs | 3 + .../Layout/IWidgetLayout.cs | 1 + .../Layout/PanelLocation.cs | 112 +++ .../Layout/SectionLayout.cs | 1 + .../Layout/WidgetLayout.cs | 1 + .../Layout/WidgetLocation.cs | 171 ++++ .../Layout/WidgetLocationPair.cs | 63 ++ 76 files changed, 2960 insertions(+), 782 deletions(-) create mode 100644 InteractiveAutomationToolkit/Components/Component.cs create mode 100644 InteractiveAutomationToolkit/Components/IComponent.cs delete mode 100644 InteractiveAutomationToolkit/Components/Interfaces/IIsReadonlyWidget.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/FormPanel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/GridPanel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/IFormPanel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/IGridPanel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/IPanel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/IStackPanel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/Panel.cs create mode 100644 InteractiveAutomationToolkit/Components/Panels/StackPanel.cs rename InteractiveAutomationToolkit/Components/{ => Widgets}/Button.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Calendar.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/CheckBox.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/CheckBoxList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/CheckBoxListBase.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/CollapseButton.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/DateTimePicker.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/DownloadButton.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/DropDown.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/DropDownBase.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/FileSelector.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Generics/EnumDropDown.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Generics/EnumRadioButtonList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Generics/GenericCheckBoxList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Generics/GenericDropDown.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Generics/GenericRadioButtonList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets/Generics}/Option.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets/Generics}/OptionCollection.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Hyperlink.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/InteractiveWidget.cs (96%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/ICheckBoxList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/ICheckBoxListBase.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/IDropDown.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/IDropDownBase.cs (100%) create mode 100644 InteractiveAutomationToolkit/Components/Widgets/Interfaces/IInteractiveWidget.cs create mode 100644 InteractiveAutomationToolkit/Components/Widgets/Interfaces/IIsReadonlyWidget.cs create mode 100644 InteractiveAutomationToolkit/Components/Widgets/Interfaces/ILabel.cs rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/IOptionWidget.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/IRadioButtonList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/IRadioButtonListBase.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Interfaces/IValidationWidget.cs (100%) create mode 100644 InteractiveAutomationToolkit/Components/Widgets/Interfaces/IWidget.cs rename InteractiveAutomationToolkit/Components/{ => Widgets}/Label.cs (98%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Numeric.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Parameter.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/PasswordBox.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/RadioButtonList.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/RadioButtonListBase.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Section.cs (81%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/TextBox.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Time.cs (99%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/TimePicker.cs (99%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/TimePickerBase.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/TreeView.cs (99%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/WhiteSpace.cs (100%) rename InteractiveAutomationToolkit/Components/{ => Widgets}/Widget.cs (75%) create mode 100644 InteractiveAutomationToolkit/Dialogs/Dialog [Obsolete].cs create mode 100644 InteractiveAutomationToolkit/Dialogs/IDialog.cs create mode 100644 InteractiveAutomationToolkit/Layout/Direction.cs create mode 100644 InteractiveAutomationToolkit/Layout/PanelLocation.cs create mode 100644 InteractiveAutomationToolkit/Layout/WidgetLocation.cs create mode 100644 InteractiveAutomationToolkit/Layout/WidgetLocationPair.cs diff --git a/InteractiveAutomationToolkit/Components/Component.cs b/InteractiveAutomationToolkit/Components/Component.cs new file mode 100644 index 0000000..88bf5ad --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Component.cs @@ -0,0 +1,9 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + /// + public class Component : IComponent + { + /// + public IPanel Parent { get; set; } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/IComponent.cs b/InteractiveAutomationToolkit/Components/IComponent.cs new file mode 100644 index 0000000..cabff3e --- /dev/null +++ b/InteractiveAutomationToolkit/Components/IComponent.cs @@ -0,0 +1,15 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + /// + /// Represents a UI component that can be added to panels. + /// + public interface IComponent + { + /// + /// Gets or sets the parent panel of the component. + /// The value will be null if the component has not been added to a . + /// + /// This property should only be set by a via . + IPanel Parent { get; set; } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IIsReadonlyWidget.cs b/InteractiveAutomationToolkit/Components/Interfaces/IIsReadonlyWidget.cs deleted file mode 100644 index a38331a..0000000 --- a/InteractiveAutomationToolkit/Components/Interfaces/IIsReadonlyWidget.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Skyline.DataMiner.Utils.InteractiveAutomationScript -{ - public interface IIsReadonlyWidget - { - /// - /// Gets or sets a value indicating whether the control is displayed in read-only mode. - /// Read-only mode causes the widgets to appear read-write but the user won't be able to change their value. - /// This only affects interactive scripts running in a web environment. - /// - /// Available from DataMiner 10.4.1 onwards. - bool IsReadOnly { get; set; } - } -} diff --git a/InteractiveAutomationToolkit/Components/Panels/FormPanel.cs b/InteractiveAutomationToolkit/Components/Panels/FormPanel.cs new file mode 100644 index 0000000..151bbf5 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/FormPanel.cs @@ -0,0 +1,192 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Linq; + using System.Reflection.Emit; + + /// + public class FormPanel : Panel, IFormPanel + { + private readonly List components = new List(); + private readonly List labels = new List(); + private readonly Dictionary panelLocations = new Dictionary(); + private readonly List spans = new List(); + + private readonly Dictionary + widgetLocations = new Dictionary(); + + /// + public void Add(string label, IComponent component) + { + Add(new Label(label), component); + } + + /// + public void Add(ILabel label, IComponent component) + { + if (label == null) + { + throw new ArgumentNullException(nameof(label)); + } + + if (component == null) + { + throw new ArgumentNullException(nameof(component)); + } + + AddParentTo(component); + components.Add(component); + AddParentTo(label); + labels.Add(label); + spans.Add(1); + } + + /// + public void Add(string label, IWidget widget, int span) + { + Add(new Label(label), widget, span); + } + + /// + public void Add(ILabel label, IWidget widget, int span) + { + if (label == null) + { + throw new ArgumentNullException(nameof(label)); + } + + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + if (span <= 0) + { + throw new ArgumentOutOfRangeException(nameof(span)); + } + + AddParentTo(widget); + components.Add(widget); + AddParentTo(label); + labels.Add(label); + spans.Add(span); + } + + /// + public override void Clear() + { + foreach (IComponent component in components) + { + RemoveParentFrom(component); + } + components.Clear(); + + foreach (ILabel label in labels) + { + RemoveParentFrom(label); + } + + labels.Clear(); + spans.Clear(); + } + + /// + public override IEnumerable GetPanels(bool includeNested) + { + return includeNested + ? components.OfType().Concat(components.OfType().SelectMany(panel => panel.GetPanels())) + : components.OfType(); + } + + /// + public override IEnumerable GetWidgetLocationPairs() + { + AssignLocations(); + foreach (KeyValuePair pair in widgetLocations.Where(pair => pair.Key.IsVisible)) + { + yield return new WidgetLocationPair(pair.Key, pair.Value); + } + + foreach (KeyValuePair pair in panelLocations) + { + IPanel panel = pair.Key; + PanelLocation panelLocation = pair.Value; + foreach (WidgetLocationPair widgetLocationPair in panel.GetWidgetLocationPairs()) + { + IWidget widget = widgetLocationPair.Widget; + WidgetLocation widgetLocation = widgetLocationPair.Location; + yield return new WidgetLocationPair(widget, widgetLocation.AddOffset(panelLocation)); + } + } + } + + /// + public override IEnumerable GetWidgets(bool includeNested) + { + return includeNested + ? GetWidgets(false).Concat(GetPanels(false).SelectMany(panel => panel.GetWidgets())) + : components.OfType().Concat(labels); + } + + /// + public override bool Remove(IComponent component) + { + int i = components.IndexOf(component); + if (i == -1) + { + return false; + } + + RemoveParentFrom(components[i]); + components.RemoveAt(i); + RemoveParentFrom(labels[i]); + labels.RemoveAt(i); + spans.RemoveAt(i); + return true; + } + + private void AssignLocations() + { + widgetLocations.Clear(); + panelLocations.Clear(); + + var row = 0; + for (var i = 0; i < components.Count; i++) + { + object component = components[i]; + ILabel label = labels[i]; + switch (component) + { + case IWidget widget when !widget.IsVisible: + continue; + + case IWidget widget: + widgetLocations.Add(label, new WidgetLocation(row, 0)); + widgetLocations.Add(widget, new WidgetLocation(row, 1, 1, spans[i])); + row++; + continue; + + case IPanel panel: + { + int rowCount = panel.GetRowCount(); + if (rowCount == 0) + { + continue; + } + + widgetLocations.Add(label, new WidgetLocation(row, 0)); + panelLocations.Add(panel, new PanelLocation(row, 1)); + row += rowCount; + continue; + } + + default: + Debug.Fail("Unexpected component type."); + return; + } + } + } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/GridPanel.cs b/InteractiveAutomationToolkit/Components/Panels/GridPanel.cs new file mode 100644 index 0000000..0eb55d9 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/GridPanel.cs @@ -0,0 +1,268 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + public class GridPanel : Panel, IGridPanel + { + private readonly HashSet panels = new HashSet(); + + private readonly Dictionary panelLocations = + new Dictionary(); + + private readonly HashSet widgets = new HashSet(); + + private readonly Dictionary + widgetLocations = new Dictionary(); + + /// + public override IEnumerable GetWidgetLocationPairs() + { + foreach (KeyValuePair pair in widgetLocations.Where(pair => pair.Key.IsVisible)) + { + yield return new WidgetLocationPair(pair.Key, pair.Value); + } + + foreach (KeyValuePair pair in panelLocations) + { + IPanel panel = pair.Key; + PanelLocation panelLocation = pair.Value; + foreach (WidgetLocationPair widgetLocationPair in panel.GetWidgetLocationPairs()) + { + IWidget widget = widgetLocationPair.Widget; + WidgetLocation widgetLocation = widgetLocationPair.Location; + yield return new WidgetLocationPair(widget, widgetLocation.AddOffset(panelLocation)); + } + } + } + + [Obsolete("This method is only here for backward compatibility", false)] + public IEnumerable GetAllWidgetLocationPairs() + { + foreach (KeyValuePair pair in widgetLocations) + { + yield return new WidgetLocationPair(pair.Key, pair.Value); + } + + foreach (KeyValuePair pair in panelLocations) + { + IPanel panel = pair.Key; + PanelLocation panelLocation = pair.Value; + foreach (WidgetLocationPair widgetLocationPair in panel.GetWidgetLocationPairs()) + { + IWidget widget = widgetLocationPair.Widget; + WidgetLocation widgetLocation = widgetLocationPair.Location; + yield return new WidgetLocationPair(widget, widgetLocation.AddOffset(panelLocation)); + } + } + } + + /// + public override void Clear() + { + widgetLocations.Clear(); + foreach (IWidget widget in widgets) + { + RemoveParentFrom(widget); + } + + widgets.Clear(); + panelLocations.Clear(); + + foreach (IPanel panel in panels) + { + RemoveParentFrom(panel); + } + + panels.Clear(); + } + + /// + public void Add(IPanel panel, PanelLocation location) + { + if (panel == null) + { + throw new ArgumentNullException(nameof(panel)); + } + + AddParentTo(panel); + panels.Add(panel); + panelLocations.Add(panel, location); + } + + /// + public void Add(IPanel panel, int fromRow, int fromColumn) + { + Add(panel, new PanelLocation(fromRow, fromColumn)); + } + + /// + public void Add(IWidget widget, WidgetLocation location) + { + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + AddParentTo(widget); + widgets.Add(widget); + widgetLocations.Add(widget, location); + } + + /// + public void Add(IWidget widget, int row, int column) + { + Add(widget, new WidgetLocation(row, column)); + } + + /// + public void Add(IWidget widget, int fromRow, int fromColumn, int rowSpan, int colSpan) + { + Add(widget, new WidgetLocation(fromRow, fromColumn, rowSpan, colSpan)); + } + + /// + public override IEnumerable GetPanels(bool includeNested) + { + return includeNested + ? panels.Concat(panels.SelectMany(panel => panel.GetPanels())) + : panels; + } + + /// + public override IEnumerable GetWidgets(bool includeNested) + { + return includeNested + ? widgets.Concat(panels.SelectMany(panel => panel.GetWidgets())) + : widgets; + } + + /// + public void Move(IPanel panel, PanelLocation location) + { + if (panel == null) + { + throw new ArgumentNullException(nameof(panel)); + } + + if (!panels.Contains(panel)) + { + throw new ArgumentException("Widget is not part of this component."); + } + + panelLocations[panel] = location; + } + + /// + public void Move(IPanel panel, int fromRow, int fromColumn) + { + Move(panel, new PanelLocation(fromRow, fromColumn)); + } + + /// + public void Move(IWidget widget, WidgetLocation widgetLocation) + { + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + if (!widgets.Contains(widget)) + { + throw new ArgumentException("Widget is not part of this component."); + } + + widgetLocations[widget] = widgetLocation; + } + + /// + public void Move(IWidget widget, int row, int column) + { + Move(widget, row, column, 1, 1); + } + + /// + public void Move(IWidget widget, int fromRow, int fromColumn, int rowSpan, int colSpan) + { + Move(widget, new WidgetLocation(fromRow, fromColumn, rowSpan, colSpan)); + } + + /// + public void Remove(IPanel panel) + { + if (panel == null) + { + throw new ArgumentNullException(nameof(panel)); + } + + RemoveParentFrom(panel); + panels.Remove(panel); + panelLocations.Remove(panel); + } + + /// + public void Remove(IWidget widget) + { + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + RemoveParentFrom(widget); + widgets.Remove(widget); + widgetLocations.Remove(widget); + } + + /// + public override bool Remove(IComponent component) + { + switch (component) + { + case IPanel panel: + Remove(panel); + return true; + + case IWidget widget: + Remove(widget); + return true; + + default: + return false; + } + } + + /// + public PanelLocation GetLocation(IPanel panel) + { + if (panel == null) + { + throw new ArgumentNullException(nameof(panel)); + } + + if (!panelLocations.TryGetValue(panel, out PanelLocation location)) + { + throw new ArgumentException("Widget is not part of this component."); + } + + return location; + } + + /// + public WidgetLocation GetLocation(IWidget widget) + { + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + if (!widgetLocations.TryGetValue(widget, out WidgetLocation widgetLocation)) + { + throw new ArgumentException("Widget is not part of this component."); + } + + return widgetLocation; + } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/IFormPanel.cs b/InteractiveAutomationToolkit/Components/Panels/IFormPanel.cs new file mode 100644 index 0000000..edc6bf3 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/IFormPanel.cs @@ -0,0 +1,49 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Reflection.Emit; + + /// + /// Arranges child components into a form of widgets and their associated labels. + /// + public interface IFormPanel : IPanel + { + /// + /// Adds a component to the form with the given label. + /// + /// The label shown next to the component. + /// The component to be added to the form. + /// When is null. + void Add(string label, IComponent component); + + /// + /// Adds a component to the form with the given label. + /// + /// The label shown next to the component. + /// The component to be added to the form. + /// When is null. + /// When is null. + void Add(ILabel label, IComponent component); + + /// + /// Adds a widget with specified span and with the given label to the form. + /// + /// The label shown next to the component. + /// The widget to be added to the form. + /// The number of columns the widget spans. + /// When is smaller than 1. + /// When is null. + void Add(string label, IWidget widget, int span); + + /// + /// Adds a widget with specified span and with the given label to the form. + /// + /// The label shown next to the component. + /// The widget to be added to the form. + /// The number of columns the widget spans. + /// When is smaller than 1. + /// When is null. + /// When is null. + void Add(ILabel label, IWidget widget, int span); + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/IGridPanel.cs b/InteractiveAutomationToolkit/Components/Panels/IGridPanel.cs new file mode 100644 index 0000000..5b91ba3 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/IGridPanel.cs @@ -0,0 +1,157 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + + /// + /// Defines a flexible grid area that consists of columns and rows. + /// + public interface IGridPanel : IPanel + { + /// + /// Adds a panel to the grid. + /// + /// Panel to be added to the grid. + /// Starting location of the panel within the grid. + /// When is null. + /// When trying to add a panel to itself. + /// When the panel is already added. + /// Unlike and , surrounding widgets will not be automatically moved to avoid overlap. + void Add(IPanel panel, PanelLocation location); + + /// + /// Adds a panel to the grid. + /// + /// Panel to be added to the grid. + /// Row location of widget on the grid. + /// Column location of widget on the grid. + /// When is null. + /// When trying to add a panel to itself. + /// When the panel is already added. + /// When or is smaller than 0. + /// Unlike and , surrounding widgets will not be automatically moved to avoid overlap. + void Add(IPanel panel, int fromRow, int fromColumn); + + /// + /// Adds a widget to the grid. + /// + /// Widget to be added to the grid. + /// Location of the widget on the grid. + /// When is null. + /// When trying to add a panel to itself. + /// When the panel is already added. + void Add(IWidget widget, WidgetLocation location); + + /// + /// Adds a widget to the grid. + /// + /// Widget to be added to the grid. + /// Row location of widget on the grid. + /// Column location of the widget on the grid. + /// When is null. + /// When trying to add a panel to itself. + /// When the panel is already added. + /// When or is smaller than 0. + void Add(IWidget widget, int row, int column); + + /// + /// Adds a widget to the grid. + /// + /// Widget to be added to the grid. + /// Row location of widget on the grid. + /// Column location of the widget on the grid. + /// Number of rows the widget will use. + /// Number of columns the widget will use. + /// When is null. + /// When trying to add a panel to itself. + /// When the panel is already added. + /// When or is smaller than 0. + /// When or is smaller than 1. + void Add(IWidget widget, int fromRow, int fromColumn, int rowSpan, int colSpan); + + /// + /// Changes the relative location of the panel within the grid. + /// + /// A panel that is part of the grid. + /// The new location of the panel. + /// When panel is null. + /// When the panel is not part of the grid. + void Move(IPanel panel, PanelLocation location); + + /// + /// Changes the relative location of the panel within the grid. + /// + /// A panel that is part of the grid. + /// New row where the panel should be moved. + /// New column where the panel should be moved. + /// When panel is null. + /// When the panel is not part of the grid. + /// When or is smaller than 0. + void Move(IPanel panel, int fromRow, int fromColumn); + + /// + /// Moves the widget within the grid. + /// + /// A widget that is part of the grid. + /// The new location of the widget. + /// When widget is null. + /// When the widget is not part of the grid. + void Move(IWidget widget, WidgetLocation widgetLocation); + + /// + /// Moves the widget within the grid. + /// + /// A widget that is part of the grid. + /// New row location of widget on the grid. + /// New column location of the widget on the grid. + /// When widget is null. + /// When the widget is not part of the grid. + /// When or is smaller than 0. + void Move(IWidget widget, int row, int column); + + /// + /// Moves the widget within the grid. + /// + /// A widget that is part of the grid. + /// New row location of widget on the grid. + /// New column location of the widget on the grid. + /// New number of rows the widget will use. + /// New number of columns the widget will use. + /// When widget is null. + /// When the widget is not part of the grid. + /// When or is smaller than 0. + /// When or is smaller than 1. + void Move(IWidget widget, int fromRow, int fromColumn, int rowSpan, int colSpan); + + /// + /// Gets the location of the panel in the grid. + /// + /// A panel that is part of the grid. + /// The panel location in the grid. + /// When the panel is null. + /// When the panel is not part of the grid. + PanelLocation GetLocation(IPanel panel); + + /// + /// Gets the location of the widget in the grid. + /// + /// A widget that is part of the grid. + /// The widget location in the grid. + /// When the widget is null. + /// When the widget is not part of the grid. + WidgetLocation GetLocation(IWidget widget); + + /// + /// Removes a panel from the grid. + /// + /// Panel to remove. + /// When is null. + void Remove(IPanel panel); + + /// + /// Removes a widget from the grid. + /// + /// Widget to remove.` + /// When the widget is null. + void Remove(IWidget widget); + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/IPanel.cs b/InteractiveAutomationToolkit/Components/Panels/IPanel.cs new file mode 100644 index 0000000..fd7be37 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/IPanel.cs @@ -0,0 +1,146 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System.Collections.Generic; + using System.ComponentModel; + + /// + /// Represents a special component that can group widgets together. + /// + public interface IPanel : IComponent + { + /// + /// Gets the current number of columns allocated in the panel. + /// + /// The current number of columns allocated in the panel. + int GetColumnCount(); + + /// + /// Gets the current number of rows allocated in the panel. + /// + /// The current number of rows allocated in the panel. + int GetRowCount(); + + /// + /// Gets all widgets and their locations as if they are part of one big grid. + /// This is used by the toolkit to build the UI. + /// + /// All widgets and their locations as if they are part of one big grid. + [EditorBrowsable(EditorBrowsableState.Never)] + IEnumerable GetWidgetLocationPairs(); + + /// + /// Removes all widgets and panels from the panel. + /// + void Clear(); + + /// + /// Removes a component from the form. + /// + /// The component to remove. + bool Remove(IComponent component); + + /// + /// Disables all widgets added to this panel. + /// Sets the property of all to + /// false. + /// + /// Also changes nested widgets. + void DisableWidgets(); + + /// + /// Disables widgets added to this panel. + /// Sets the property of all to + /// false. + /// + /// true if nested widgets should be included. false otherwise. + void DisableWidgets(bool includeNested); + + /// + /// Enables all widgets added to this panel. + /// Sets the property of all to + /// true. + /// + /// Also changes nested widgets. + void EnableWidgets(); + + /// + /// Enables widgets added to this panel. + /// Sets the property of all to + /// true. + /// + /// true if nested widgets should be included. false otherwise. + void EnableWidgets(bool includeNested); + + /// + /// Gets all panels added to this panel. + /// + /// Panels added to this panel. + /// Also returns nested panels. + IEnumerable GetPanels(); + + /// + /// Gets panels added to this panel. + /// + /// Include nested panels. + /// Panels added to this panel. + IEnumerable GetPanels(bool includeNested); + + /// + /// Gets all widgets added to this panel. + /// + /// Widgets added to this panel. + /// Also returns widgets from nested panels. + IEnumerable GetWidgets(); + + /// + /// Gets the widgets added to this panel. + /// + /// Include widgets from nested panels. + /// Widgets added to this panel. + IEnumerable GetWidgets(bool includeNested); + + /// + /// Hides all widgets added to this panel. + /// Sets the property of all to false. + /// + /// Also changes nested widgets. + void HideWidgets(); + + /// + /// Hides widgets added to this panel. + /// Sets the property of all to false. + /// + /// true if nested widgets should be included. false otherwise. + void HideWidgets(bool includeNested); + + /// + /// Enables or disables all widgets added to this panel. + /// Sets the property of all . + /// + /// true if the widgets should be set enabled. false otherwise. + /// true if nested widgets should be included. false otherwise. + void SetWidgetsEnabled(bool enabled, bool includeNested); + + /// + /// Shows or hides all widgets added to this panel. + /// Sets the property of all . + /// + /// true if the widgets should be set visible. false otherwise. + /// true if nested widgets should be included. false otherwise. + void SetWidgetsVisible(bool visible, bool includeNested); + + /// + /// Shows all widgets added to this panel. + /// Sets the property of all to true. + /// + /// Also changes nested widgets. + void ShowWidgets(); + + /// + /// Shows widgets added to this panel. + /// Sets the property of all to true. + /// + /// true if nested widgets should be included. false otherwise. + void ShowWidgets(bool includeNested); + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/IStackPanel.cs b/InteractiveAutomationToolkit/Components/Panels/IStackPanel.cs new file mode 100644 index 0000000..747775a --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/IStackPanel.cs @@ -0,0 +1,57 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Collections.Generic; + + using Skyline.DataMiner.Analytics.Arrows; + + /// + /// Arranges child components into a single line that can be oriented horizontally or vertically. + /// + public interface IStackPanel : IPanel, IList, IReadOnlyList + { + /// + /// Gets or sets a value indication the orientation by which the child components are stacked. + /// + Direction Direction { get; set; } + + /// + new int Count { get; } + + /// + new IComponent this[int index] { get; } + + /// + /// Adds a widget to the stack and specified how many rows or columns the widget must span. + /// + /// Widget to be added to the stack. + /// Number of rows for a horizontal orientation or number of columns for a vertical orientation. + /// When is null. + /// When trying to add a panel to itself. + /// When the component is already added. + /// When is smaller than 1. + void Add(IWidget widget, int span); + + /// + /// Gets the component at the specified index of the stack. + /// + /// The zero-based index of the component. + /// The component at the specified index of the stack. + /// When is outside the range of valid indexes. + IComponent ComponentAt(int index); + + /// + /// Inserts a widget into the stack at the specified index and specified how many rows or columns the widget must span. + /// + /// The zero-based index at which the widget needs to be inserted. + /// The widget to insert. + /// Number of rows for a horizontal orientation or number of columns for a vertical orientation. + /// When is less than 0. + /// When is is greater than . + /// When is smaller than 1. + void Insert(int index, IWidget widget, int span); + + /// + new void Clear(); + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/Panel.cs b/InteractiveAutomationToolkit/Components/Panels/Panel.cs new file mode 100644 index 0000000..3741477 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/Panel.cs @@ -0,0 +1,182 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + + /// + public abstract class Panel : Component, IPanel + { + /// + public virtual int GetColumnCount() + { + try + { + return GetWidgetLocationPairs() + .Max(pair => pair.Location.Column + (pair.Location.ColumnSpan - 1) + 1); + } + catch (InvalidOperationException) + { + return 0; + } + } + + /// + public virtual int GetRowCount() + { + try + { + return GetWidgetLocationPairs() + .Max(pair => pair.Location.Row + (pair.Location.RowSpan - 1) + 1); + } + catch (InvalidOperationException) + { + return 0; + } + } + + /// + public abstract IEnumerable GetWidgetLocationPairs(); + + /// + public abstract void Clear(); + + /// + public abstract bool Remove(IComponent component); + + /// + public void DisableWidgets() + { + DisableWidgets(true); + } + + /// + public void DisableWidgets(bool includeNested) + { + SetWidgetsEnabled(false, includeNested); + } + + /// + public void EnableWidgets() + { + EnableWidgets(true); + } + + /// + public void EnableWidgets(bool includeNested) + { + SetWidgetsEnabled(true, includeNested); + } + + /// + public IEnumerable GetPanels() + { + return GetPanels(true); + } + + /// + public abstract IEnumerable GetPanels(bool includeNested); + + /// + public IEnumerable GetWidgets() + { + return GetWidgets(true); + } + + /// + public abstract IEnumerable GetWidgets(bool includeNested); + + /// + public void HideWidgets() + { + HideWidgets(true); + } + + /// + public void HideWidgets(bool includeNested) + { + SetWidgetsVisible(false, includeNested); + } + + /// + public void SetWidgetsEnabled(bool enabled, bool includeNested) + { + foreach (InteractiveWidget widget in GetWidgets(includeNested).OfType()) + { + widget.IsEnabled = enabled; + } + } + + /// + public void SetWidgetsVisible(bool visible, bool includeNested) + { + foreach (IWidget widget in GetWidgets(includeNested)) + { + widget.IsVisible = visible; + } + } + + /// + public void ShowWidgets() + { + ShowWidgets(true); + } + + /// + public void ShowWidgets(bool includeNested) + { + SetWidgetsVisible(true, includeNested); + } + + /// + /// Sets the property of to null. + /// + /// A component that is going to be removed from this panel. + protected static void RemoveParentFrom(IComponent component) + { + component.Parent = null; + } + + /// + /// Sets the property of to this. + /// + /// A component that is going to be added to this panel. + /// When trying to add a component more than once. + /// When trying to add a component recursively. + protected void AddParentTo(IComponent component) + { + if (component.Parent != null) + { + throw new InvalidOperationException("Component cannot be added to more than once."); + } + + if (component == this) + { + throw new InvalidOperationException("Panel cannot be added to itself."); + } + + if (WalkParents(this).Any(c => c == component)) + { + throw new InvalidOperationException("Panel cannot recurse."); + } + + component.Parent = this; + } + + private static IEnumerable WalkParents(IComponent component) + { + if (component.Parent == null) + { + yield break; + } + + yield return component.Parent; + + foreach (IComponent parent in WalkParents(component.Parent)) + { + yield return parent; + } + } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Panels/StackPanel.cs b/InteractiveAutomationToolkit/Components/Panels/StackPanel.cs new file mode 100644 index 0000000..6df4d28 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Panels/StackPanel.cs @@ -0,0 +1,338 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.ComponentModel; + using System.Diagnostics; + using System.Linq; + + using Skyline.DataMiner.Analytics.Arrows; + + /// + public class StackPanel : Panel, IStackPanel + { + private readonly List components = new List(); + private readonly Dictionary panelLocations = new Dictionary(); + private readonly List spans = new List(); + + private readonly Dictionary + widgetLocations = new Dictionary(); + + private Direction direction = Direction.Vertical; + + /// + /// Initializes a new instance of the class. + /// + public StackPanel() + { + } + + /// + /// Initializes a new instance of the class with the specified direction. + /// + /// The orientation by which the child components are stacked. + public StackPanel(Direction direction) => Direction = direction; + + /// + public Direction Direction + { + get => direction; + + set + { + if (!Enum.IsDefined(typeof(Direction), direction)) + { + throw new InvalidEnumArgumentException(nameof(direction), (int)direction, typeof(Direction)); + } + + direction = value; + } + } + + /// + /// Gets the number of components in the panel. + /// + public int Count => components.Count; + + /// + public bool IsReadOnly => false; + + /// + /// Gets or sets the component at the specified index. + /// + /// The zero-based index of the component to get or set. + /// The component at the specified index. + /// is not a valid index in the stack. + public IComponent this[int index] + { + get => components[index]; + + set + { + IComponent component = components[index]; + RemoveParentFrom(component); + AddParentTo(value); + components[index] = value; + spans[index] = 1; + } + } + + /// + /// Adds a component to the stack. + /// + /// Component to be added to the stack. + /// When is null. + /// When trying to add a panel to itself. + /// When the component is already added. + public void Add(IComponent component) + { + if (component == null) + { + throw new ArgumentNullException(nameof(component)); + } + + AddParentTo(component); + components.Add(component); + spans.Add(1); + } + + /// + public void Add(IWidget widget, int span) + { + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + if (span <= 0) + { + throw new ArgumentOutOfRangeException(nameof(span)); + } + + AddParentTo(widget); + components.Add(widget); + spans.Add(span); + } + + /// /> + public override void Clear() + { + foreach (IComponent component in components) + { + RemoveParentFrom(component); + } + + components.Clear(); + spans.Clear(); + } + + /// + /// Determines whether the stack has the specified component. + /// + /// Component to locate in the stack. + /// true if the component is found, false otherwise. + public bool Contains(IComponent item) + { + return components.Contains(item); + } + + /// + public void CopyTo(IComponent[] array, int arrayIndex) + { + components.CopyTo(array, arrayIndex); + } + + /// + public IComponent ComponentAt(int index) + { + return components[index]; + } + + /// + public IEnumerator GetEnumerator() + { + return components.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + public override IEnumerable GetPanels(bool includeNested) + { + return includeNested + ? components.OfType().Concat(components.OfType().SelectMany(panel => panel.GetPanels())) + : components.OfType(); + } + + /// + public override IEnumerable GetWidgetLocationPairs() + { + AssignLocations(); + foreach (KeyValuePair pair in widgetLocations.Where(pair => pair.Key.IsVisible)) + { + yield return new WidgetLocationPair(pair.Key, pair.Value); + } + + foreach (KeyValuePair pair in panelLocations) + { + IPanel panel = pair.Key; + PanelLocation panelLocation = pair.Value; + foreach (WidgetLocationPair widgetLocationPair in panel.GetWidgetLocationPairs()) + { + IWidget widget = widgetLocationPair.Widget; + WidgetLocation widgetLocation = widgetLocationPair.Location; + yield return new WidgetLocationPair(widget, widgetLocation.AddOffset(panelLocation)); + } + } + } + + /// + public override IEnumerable GetWidgets(bool includeNested) + { + return includeNested + ? components.OfType().Concat(GetPanels(false).SelectMany(panel => panel.GetWidgets())) + : components.OfType(); + } + + /// + /// Returns the zero-based index of component in the stack. + /// + /// The component to locate in the stack. + /// The zero-based index of the component in the stack, if found; otherwise, -1. + public int IndexOf(IComponent component) + { + return components.IndexOf(component); + } + + /// + /// Inserts a component into the stack at the specified index. + /// + /// The zero-based index at which the component needs to be inserted. + /// The component to insert. + /// When is less than 0. + /// When is is greater than . + public void Insert(int index, IComponent component) + { + if (component == null) + { + throw new ArgumentNullException(nameof(component)); + } + + AddParentTo(component); + components.Insert(index, component); + spans.Insert(index, 1); + } + + /// + public void Insert(int index, IWidget widget, int span) + { + if (widget == null) + { + throw new ArgumentNullException(nameof(widget)); + } + + if (span <= 0) + { + throw new ArgumentOutOfRangeException(nameof(span)); + } + + AddParentTo(widget); + components.Insert(index, widget); + spans.Insert(index, span); + } + + /// + /// Removes a component from the panel. + /// + /// Component to remove. + /// true if the component was removed, false otherwise. + public override bool Remove(IComponent component) + { + int i = components.IndexOf(component); + if (i == -1) + { + return false; + } + + RemoveParentFrom(component); + components.RemoveAt(i); + spans.RemoveAt(i); + return true; + } + + /// + /// Removes the component at the specified index. + /// + /// The zero-based index of the component to remove. + /// When is less than 0. + /// When is is greater than . + public void RemoveAt(int index) + { + RemoveParentFrom(components[index]); + components.RemoveAt(index); + spans.RemoveAt(index); + } + + private void AssignLocations() + { + widgetLocations.Clear(); + panelLocations.Clear(); + + var location = 0; + for (var i = 0; i < components.Count; i++) + { + object component = components[i]; + switch (component) + { + case IWidget widget when !widget.IsVisible: + continue; + + case IWidget widget when Direction == Direction.Vertical: + widgetLocations.Add(widget, new WidgetLocation(location, 0, 1, spans[i])); + location++; + continue; + + case IWidget widget when Direction == Direction.Horizontal: + widgetLocations.Add(widget, new WidgetLocation(0, location, spans[i], 1)); + location++; + continue; + + case IPanel panel when Direction == Direction.Vertical: + { + int rowCount = panel.GetRowCount(); + if (rowCount == 0) + { + continue; + } + + panelLocations.Add(panel, new PanelLocation(location, 0)); + location += rowCount; + continue; + } + + case IPanel panel when Direction == Direction.Horizontal: + { + int columnCount = panel.GetColumnCount(); + if (columnCount == 0) + { + continue; + } + + panelLocations.Add(panel, new PanelLocation(0, location)); + location += columnCount; + continue; + } + + default: + Debug.Fail("Unexpected component type or direction."); + return; + } + } + } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Button.cs b/InteractiveAutomationToolkit/Components/Widgets/Button.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Button.cs rename to InteractiveAutomationToolkit/Components/Widgets/Button.cs diff --git a/InteractiveAutomationToolkit/Components/Calendar.cs b/InteractiveAutomationToolkit/Components/Widgets/Calendar.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Calendar.cs rename to InteractiveAutomationToolkit/Components/Widgets/Calendar.cs diff --git a/InteractiveAutomationToolkit/Components/CheckBox.cs b/InteractiveAutomationToolkit/Components/Widgets/CheckBox.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/CheckBox.cs rename to InteractiveAutomationToolkit/Components/Widgets/CheckBox.cs diff --git a/InteractiveAutomationToolkit/Components/CheckBoxList.cs b/InteractiveAutomationToolkit/Components/Widgets/CheckBoxList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/CheckBoxList.cs rename to InteractiveAutomationToolkit/Components/Widgets/CheckBoxList.cs diff --git a/InteractiveAutomationToolkit/Components/CheckBoxListBase.cs b/InteractiveAutomationToolkit/Components/Widgets/CheckBoxListBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/CheckBoxListBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/CheckBoxListBase.cs diff --git a/InteractiveAutomationToolkit/Components/CollapseButton.cs b/InteractiveAutomationToolkit/Components/Widgets/CollapseButton.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/CollapseButton.cs rename to InteractiveAutomationToolkit/Components/Widgets/CollapseButton.cs diff --git a/InteractiveAutomationToolkit/Components/DateTimePicker.cs b/InteractiveAutomationToolkit/Components/Widgets/DateTimePicker.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/DateTimePicker.cs rename to InteractiveAutomationToolkit/Components/Widgets/DateTimePicker.cs diff --git a/InteractiveAutomationToolkit/Components/DownloadButton.cs b/InteractiveAutomationToolkit/Components/Widgets/DownloadButton.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/DownloadButton.cs rename to InteractiveAutomationToolkit/Components/Widgets/DownloadButton.cs diff --git a/InteractiveAutomationToolkit/Components/DropDown.cs b/InteractiveAutomationToolkit/Components/Widgets/DropDown.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/DropDown.cs rename to InteractiveAutomationToolkit/Components/Widgets/DropDown.cs diff --git a/InteractiveAutomationToolkit/Components/DropDownBase.cs b/InteractiveAutomationToolkit/Components/Widgets/DropDownBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/DropDownBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/DropDownBase.cs diff --git a/InteractiveAutomationToolkit/Components/FileSelector.cs b/InteractiveAutomationToolkit/Components/Widgets/FileSelector.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/FileSelector.cs rename to InteractiveAutomationToolkit/Components/Widgets/FileSelector.cs diff --git a/InteractiveAutomationToolkit/Components/Generics/EnumDropDown.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/EnumDropDown.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Generics/EnumDropDown.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/EnumDropDown.cs diff --git a/InteractiveAutomationToolkit/Components/Generics/EnumRadioButtonList.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/EnumRadioButtonList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Generics/EnumRadioButtonList.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/EnumRadioButtonList.cs diff --git a/InteractiveAutomationToolkit/Components/Generics/GenericCheckBoxList.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/GenericCheckBoxList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Generics/GenericCheckBoxList.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/GenericCheckBoxList.cs diff --git a/InteractiveAutomationToolkit/Components/Generics/GenericDropDown.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/GenericDropDown.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Generics/GenericDropDown.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/GenericDropDown.cs diff --git a/InteractiveAutomationToolkit/Components/Generics/GenericRadioButtonList.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/GenericRadioButtonList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Generics/GenericRadioButtonList.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/GenericRadioButtonList.cs diff --git a/InteractiveAutomationToolkit/Components/Option.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/Option.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Option.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/Option.cs diff --git a/InteractiveAutomationToolkit/Components/OptionCollection.cs b/InteractiveAutomationToolkit/Components/Widgets/Generics/OptionCollection.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/OptionCollection.cs rename to InteractiveAutomationToolkit/Components/Widgets/Generics/OptionCollection.cs diff --git a/InteractiveAutomationToolkit/Components/Hyperlink.cs b/InteractiveAutomationToolkit/Components/Widgets/Hyperlink.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Hyperlink.cs rename to InteractiveAutomationToolkit/Components/Widgets/Hyperlink.cs diff --git a/InteractiveAutomationToolkit/Components/InteractiveWidget.cs b/InteractiveAutomationToolkit/Components/Widgets/InteractiveWidget.cs similarity index 96% rename from InteractiveAutomationToolkit/Components/InteractiveWidget.cs rename to InteractiveAutomationToolkit/Components/Widgets/InteractiveWidget.cs index 3f192d9..55ee718 100644 --- a/InteractiveAutomationToolkit/Components/InteractiveWidget.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/InteractiveWidget.cs @@ -5,7 +5,7 @@ /// /// A widget that requires user input. /// - public abstract class InteractiveWidget : Widget + public abstract class InteractiveWidget : Widget, IInteractiveWidget { /// /// Initializes a new instance of the class. diff --git a/InteractiveAutomationToolkit/Components/Interfaces/ICheckBoxList.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/ICheckBoxList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/ICheckBoxList.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/ICheckBoxList.cs diff --git a/InteractiveAutomationToolkit/Components/Interfaces/ICheckBoxListBase.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/ICheckBoxListBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/ICheckBoxListBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/ICheckBoxListBase.cs diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IDropDown.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IDropDown.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/IDropDown.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/IDropDown.cs diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IDropDownBase.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IDropDownBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/IDropDownBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/IDropDownBase.cs diff --git a/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IInteractiveWidget.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IInteractiveWidget.cs new file mode 100644 index 0000000..6def8be --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IInteractiveWidget.cs @@ -0,0 +1,15 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + /// + /// Represents a widget that requires user input. + /// + public interface IInteractiveWidget : IWidget + { + /// + /// Gets or sets a value indicating whether the control is enabled in the UI. + /// Disabling causes the widgets to be grayed out and disables user interaction. + /// + /// Available from DataMiner 9.5.3 onwards. + bool IsEnabled { get; set; } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IIsReadonlyWidget.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IIsReadonlyWidget.cs new file mode 100644 index 0000000..5e3a865 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IIsReadonlyWidget.cs @@ -0,0 +1,13 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + public interface IIsReadonlyWidget + { + /// + /// Gets or sets a value indicating whether the control is displayed in read-only mode. + /// Read-only mode causes the widgets to appear read-write but the user won't be able to change their value. + /// This only affects interactive scripts running in a web environment. + /// + /// Available from DataMiner 10.4.1 onwards. + bool IsReadOnly { get; set; } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Widgets/Interfaces/ILabel.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/ILabel.cs new file mode 100644 index 0000000..15cd91d --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/ILabel.cs @@ -0,0 +1,28 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System.ComponentModel; + + /// + /// Represents a label is used to display text in different styles. + /// + public interface ILabel : IWidget + { + /// + /// Gets or sets the text style of the label. + /// + /// When does not specify a valid member of . + TextStyle Style { get; set; } + + /// + /// Gets or sets the displayed text. + /// + string Text { get; set; } + + /// + /// Gets or sets the tooltip. + /// + /// This property only works for web compliant scripts or when launched from a DataMiner web app. + /// Available from DataMiner Feature Release 10.0.8 and Main Release 10.1.0 onwards. + string Tooltip { get; set; } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IOptionWidget.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IOptionWidget.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/IOptionWidget.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/IOptionWidget.cs diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IRadioButtonList.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IRadioButtonList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/IRadioButtonList.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/IRadioButtonList.cs diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IRadioButtonListBase.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IRadioButtonListBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/IRadioButtonListBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/IRadioButtonListBase.cs diff --git a/InteractiveAutomationToolkit/Components/Interfaces/IValidationWidget.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IValidationWidget.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Interfaces/IValidationWidget.cs rename to InteractiveAutomationToolkit/Components/Widgets/Interfaces/IValidationWidget.cs diff --git a/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IWidget.cs b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IWidget.cs new file mode 100644 index 0000000..c8e4b64 --- /dev/null +++ b/InteractiveAutomationToolkit/Components/Widgets/Interfaces/IWidget.cs @@ -0,0 +1,93 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.ComponentModel; + + using Skyline.DataMiner.Automation; + + /// + /// Represents a widget. + /// + public interface IWidget : IComponent + { + /// + /// Gets the internal DataMiner representation of the widget. + /// This object should not be used! + /// This library exists so you don't need to use this object. + /// + /// A widget should implement everything, so you don't need to use this object. + [EditorBrowsable(EditorBrowsableState.Never)] + UIBlockDefinition BlockDefinition { get; } + + /// + /// Gets the UIBlockType of the widget. + /// + UIBlockType Type { get; } + + /// + /// Gets or sets the fixed height (in pixels) of the widget. + /// + /// When the value is smaller than 1. + int Height { get; set; } + + /// + /// Gets or sets the horizontal alignment of the widget. + /// + HorizontalAlignment HorizontalAlignment { get; set; } + + /// + /// Gets or sets a value indicating whether the widget is visible in the dialog. + /// + bool IsVisible { get; set; } + + /// + /// Gets or sets the margin of the widget. + /// + Margin Margin { get; set; } + + /// + /// Gets or sets the maximum height (in pixels) of the widget. + /// + /// When the value is smaller than 1. + int MaxHeight { get; set; } + + /// + /// Gets or sets the maximum width (in pixels) of the widget. + /// + /// When the value is smaller than 1. + int MaxWidth { get; set; } + + /// + /// Gets or sets the minimum height (in pixels) of the widget. + /// + /// When the value is smaller than 1. + int MinHeight { get; set; } + + /// + /// Gets or sets the minimum width (in pixels) of the widget. + /// + /// When the value is smaller than 1. + int MinWidth { get; set; } + + /// + /// Gets or sets the vertical alignment of the widget. + /// + VerticalAlignment VerticalAlignment { get; set; } + + /// + /// Gets or sets the fixed width (in pixels) of the widget. + /// + /// When the value is smaller than 1. + int Width { get; set; } + + /// + /// Set the height of the widget based on its content. + /// + void SetHeightAuto(); + + /// + /// Set the width of the widget based on its content. + /// + void SetWidthAuto(); + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Components/Label.cs b/InteractiveAutomationToolkit/Components/Widgets/Label.cs similarity index 98% rename from InteractiveAutomationToolkit/Components/Label.cs rename to InteractiveAutomationToolkit/Components/Widgets/Label.cs index 7da3508..00dc8ac 100644 --- a/InteractiveAutomationToolkit/Components/Label.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/Label.cs @@ -8,7 +8,7 @@ /// A label is used to display text. /// Text can have different styles. /// - public class Label : Widget + public class Label : Widget, ILabel { private TextStyle style; diff --git a/InteractiveAutomationToolkit/Components/Numeric.cs b/InteractiveAutomationToolkit/Components/Widgets/Numeric.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Numeric.cs rename to InteractiveAutomationToolkit/Components/Widgets/Numeric.cs diff --git a/InteractiveAutomationToolkit/Components/Parameter.cs b/InteractiveAutomationToolkit/Components/Widgets/Parameter.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/Parameter.cs rename to InteractiveAutomationToolkit/Components/Widgets/Parameter.cs diff --git a/InteractiveAutomationToolkit/Components/PasswordBox.cs b/InteractiveAutomationToolkit/Components/Widgets/PasswordBox.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/PasswordBox.cs rename to InteractiveAutomationToolkit/Components/Widgets/PasswordBox.cs diff --git a/InteractiveAutomationToolkit/Components/RadioButtonList.cs b/InteractiveAutomationToolkit/Components/Widgets/RadioButtonList.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/RadioButtonList.cs rename to InteractiveAutomationToolkit/Components/Widgets/RadioButtonList.cs diff --git a/InteractiveAutomationToolkit/Components/RadioButtonListBase.cs b/InteractiveAutomationToolkit/Components/Widgets/RadioButtonListBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/RadioButtonListBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/RadioButtonListBase.cs diff --git a/InteractiveAutomationToolkit/Components/Section.cs b/InteractiveAutomationToolkit/Components/Widgets/Section.cs similarity index 81% rename from InteractiveAutomationToolkit/Components/Section.cs rename to InteractiveAutomationToolkit/Components/Widgets/Section.cs index 49b486d..b1ae408 100644 --- a/InteractiveAutomationToolkit/Components/Section.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/Section.cs @@ -7,10 +7,9 @@ /// /// A section is a special component that can be used to group widgets together. /// - public class Section + [Obsolete("Use panels instead", false)] + public class Section : GridPanel { - private readonly Dictionary widgetLayouts = new Dictionary(); - private bool isEnabled = true; private bool isVisible = true; private bool isReadOnly = false; @@ -18,12 +17,12 @@ public class Section /// /// Gets the number of columns that are currently defined by the widgets that have been added to this section. /// - public int ColumnCount { get; private set; } + public int ColumnCount { get => GetColumnCount(); } /// /// Gets the number of rows that are currently defined by the widgets that have been added to this section. /// - public int RowCount { get; private set; } + public int RowCount { get => GetRowCount(); } /// /// Gets or sets a value indicating whether the widgets within the section are visible or not. @@ -38,9 +37,13 @@ public virtual bool IsVisible set { isVisible = value; - foreach (Widget widget in Widgets) + if (isVisible) + { + ShowWidgets(true); + } + else { - widget.IsVisible = isVisible; + HideWidgets(true); } } } @@ -58,13 +61,13 @@ public virtual bool IsEnabled set { isEnabled = value; - foreach (Widget widget in Widgets) + if (isEnabled) + { + EnableWidgets(true); + } + else { - InteractiveWidget interactiveWidget = widget as InteractiveWidget; - if (interactiveWidget != null) - { - interactiveWidget.IsEnabled = isEnabled; - } + EnableWidgets(true); } } } @@ -99,7 +102,7 @@ public IEnumerable Widgets { get { - return widgetLayouts.Keys; + return GetWidgetLocationPairs().Select(x => (Widget)x.Widget); } } @@ -118,13 +121,16 @@ public Section AddWidget(Widget widget, IWidgetLayout widgetLayout) throw new ArgumentNullException("widget"); } - if (widgetLayouts.ContainsKey(widget)) + var existingWidget = Widgets.FirstOrDefault(w => w == widget); + if (existingWidget != null) { throw new ArgumentException("Widget is already added to the section"); } - widgetLayouts.Add(widget, widgetLayout); - UpdateRowAndColumnCount(); + widget.HorizontalAlignment = widgetLayout.HorizontalAlignment; + widget.VerticalAlignment = widgetLayout.VerticalAlignment; + widget.Margin = widgetLayout.Margin; + Add(widget, new WidgetLocation(widgetLayout.Row, widgetLayout.Column, widgetLayout.RowSpan, widgetLayout.ColumnSpan)); return this; } @@ -189,19 +195,7 @@ public Section AddWidget( /// The updated section. public Section AddSection(Section section, ILayout layout) { - foreach (Widget widget in section.Widgets) - { - IWidgetLayout widgetLayout = section.GetWidgetLayout(widget); - AddWidget( - widget, - new WidgetLayout( - widgetLayout.Row + layout.Row, - widgetLayout.Column + layout.Column, - widgetLayout.RowSpan, - widgetLayout.ColumnSpan, - widgetLayout.HorizontalAlignment, - widgetLayout.VerticalAlignment)); - } + Add(section, new PanelLocation(layout.Row, layout.Column)); return this; } @@ -228,7 +222,9 @@ public Section AddSection(Section section, int row, int column) public IWidgetLayout GetWidgetLayout(Widget widget) { CheckWidgetExits(widget); - return widgetLayouts[widget]; + + var pair = GetWidgetLocationPairs().FirstOrDefault(w => w.Widget == widget); + return GetWidgetLayout(pair.Widget, pair.Location); } /// @@ -243,8 +239,11 @@ public void RemoveWidget(Widget widget) throw new ArgumentNullException("widget"); } - widgetLayouts.Remove(widget); - UpdateRowAndColumnCount(); + Remove(widget); + foreach (var panel in GetPanels()) + { + panel.Remove(widget); + } } /// @@ -263,47 +262,35 @@ public void SetWidgetLayout(Widget widget, IWidgetLayout widgetLayout) } CheckWidgetExits(widget); - widgetLayouts[widget] = widgetLayout; - } - - /// - /// Removes all widgets from the section. - /// - public void Clear() - { - widgetLayouts.Clear(); - RowCount = 0; - ColumnCount = 0; + Move(widget, widgetLayout.Row, widgetLayout.Column, widgetLayout.RowSpan, widgetLayout.ColumnSpan); + widget.HorizontalAlignment = widgetLayout.HorizontalAlignment; + widget.VerticalAlignment = widgetLayout.VerticalAlignment; + widget.Margin = widgetLayout.Margin; } private void CheckWidgetExits(Widget widget) { - if (widget == null) + if (widget is null) { throw new ArgumentNullException("widget"); } - if (!widgetLayouts.ContainsKey(widget)) + var existingWidget = Widgets.FirstOrDefault(w => w == widget); + if (existingWidget is null) { throw new ArgumentException("Widget is not part of this dialog"); } } - /// - /// Used to update the RowCount and ColumnCount properties based on the Widgets added to the section. - /// - private void UpdateRowAndColumnCount() + private IWidgetLayout GetWidgetLayout(IWidget widget, WidgetLocation location) { - if (widgetLayouts.Any()) - { - RowCount = widgetLayouts.Values.Max(w => w.Row + w.RowSpan); - ColumnCount = widgetLayouts.Values.Max(w => w.Column + w.ColumnSpan); - } - else - { - RowCount = 0; - ColumnCount = 0; - } + return new WidgetLayout( + location.Row, + location.Column, + location.RowSpan, + location.ColumnSpan, + widget.HorizontalAlignment, + widget.VerticalAlignment); } } } diff --git a/InteractiveAutomationToolkit/Components/TextBox.cs b/InteractiveAutomationToolkit/Components/Widgets/TextBox.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/TextBox.cs rename to InteractiveAutomationToolkit/Components/Widgets/TextBox.cs diff --git a/InteractiveAutomationToolkit/Components/Time.cs b/InteractiveAutomationToolkit/Components/Widgets/Time.cs similarity index 99% rename from InteractiveAutomationToolkit/Components/Time.cs rename to InteractiveAutomationToolkit/Components/Widgets/Time.cs index d9ec637..d789620 100644 --- a/InteractiveAutomationToolkit/Components/Time.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/Time.cs @@ -38,9 +38,7 @@ public Time() : this(default) { } - /// /// - /// public virtual bool IsReadOnly { get diff --git a/InteractiveAutomationToolkit/Components/TimePicker.cs b/InteractiveAutomationToolkit/Components/Widgets/TimePicker.cs similarity index 99% rename from InteractiveAutomationToolkit/Components/TimePicker.cs rename to InteractiveAutomationToolkit/Components/Widgets/TimePicker.cs index f722c4c..6b55431 100644 --- a/InteractiveAutomationToolkit/Components/TimePicker.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/TimePicker.cs @@ -299,9 +299,7 @@ public string ValidationText } } - /// /// - /// public virtual bool IsReadOnly { get diff --git a/InteractiveAutomationToolkit/Components/TimePickerBase.cs b/InteractiveAutomationToolkit/Components/Widgets/TimePickerBase.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/TimePickerBase.cs rename to InteractiveAutomationToolkit/Components/Widgets/TimePickerBase.cs diff --git a/InteractiveAutomationToolkit/Components/TreeView.cs b/InteractiveAutomationToolkit/Components/Widgets/TreeView.cs similarity index 99% rename from InteractiveAutomationToolkit/Components/TreeView.cs rename to InteractiveAutomationToolkit/Components/Widgets/TreeView.cs index 566d686..d083553 100644 --- a/InteractiveAutomationToolkit/Components/TreeView.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/TreeView.cs @@ -231,9 +231,7 @@ public string Tooltip } } - /// /// - /// public virtual bool IsReadOnly { get diff --git a/InteractiveAutomationToolkit/Components/WhiteSpace.cs b/InteractiveAutomationToolkit/Components/Widgets/WhiteSpace.cs similarity index 100% rename from InteractiveAutomationToolkit/Components/WhiteSpace.cs rename to InteractiveAutomationToolkit/Components/Widgets/WhiteSpace.cs diff --git a/InteractiveAutomationToolkit/Components/Widget.cs b/InteractiveAutomationToolkit/Components/Widgets/Widget.cs similarity index 75% rename from InteractiveAutomationToolkit/Components/Widget.cs rename to InteractiveAutomationToolkit/Components/Widgets/Widget.cs index 1038114..337cddb 100644 --- a/InteractiveAutomationToolkit/Components/Widget.cs +++ b/InteractiveAutomationToolkit/Components/Widgets/Widget.cs @@ -1,6 +1,7 @@ namespace Skyline.DataMiner.Utils.InteractiveAutomationScript { using System; + using System.ComponentModel; using System.Reflection; using Skyline.DataMiner.Automation; @@ -8,9 +9,11 @@ /// /// Base class for widgets. /// - public class Widget + public class Widget : Component, IWidget { private UIBlockDefinition blockDefinition = new UIBlockDefinition(); + private HorizontalAlignment horizontalAlignment; + private VerticalAlignment verticalAlignment; /// /// Initializes a new instance of the class. @@ -23,6 +26,36 @@ protected Widget() SetWidthAuto(); } + /// + public HorizontalAlignment HorizontalAlignment + { + get + { + return horizontalAlignment; + } + + set + { + horizontalAlignment = value; + BlockDefinition.HorizontalAlignment = AlignmentToUiString(value); + } + } + + /// + public VerticalAlignment VerticalAlignment + { + get + { + return verticalAlignment; + } + + set + { + verticalAlignment = value; + BlockDefinition.VerticalAlignment = AlignmentToUiString(value); + } + } + /// /// Gets or sets the fixed height (in pixels) of the widget. /// @@ -196,7 +229,7 @@ public string DebugTag /// /// Gets or sets the UIBlockType of the widget. /// - internal UIBlockType Type + public UIBlockType Type { get { @@ -215,7 +248,7 @@ internal UIBlockType Type /// This library exists so you don't need to use this object. /// /// A widget should implement everything, so you don't need to use this object. - protected internal UIBlockDefinition BlockDefinition + public UIBlockDefinition BlockDefinition { get { @@ -261,5 +294,53 @@ protected void RecreateUiBlock() blockDefinition = newUiBlockDefinition; } + + private static string AlignmentToUiString(HorizontalAlignment horizontalAlignment) + { + switch (horizontalAlignment) + { + case HorizontalAlignment.Center: + return "Center"; + + case HorizontalAlignment.Left: + return "Left"; + + case HorizontalAlignment.Right: + return "Right"; + + case HorizontalAlignment.Stretch: + return "Stretch"; + + default: + throw new InvalidEnumArgumentException( + nameof(horizontalAlignment), + (int)horizontalAlignment, + typeof(HorizontalAlignment)); + } + } + + private static string AlignmentToUiString(VerticalAlignment verticalAlignment) + { + switch (verticalAlignment) + { + case VerticalAlignment.Center: + return "Center"; + + case VerticalAlignment.Top: + return "Top"; + + case VerticalAlignment.Bottom: + return "Bottom"; + + case VerticalAlignment.Stretch: + return "Stretch"; + + default: + throw new InvalidEnumArgumentException( + nameof(verticalAlignment), + (int)verticalAlignment, + typeof(VerticalAlignment)); + } + } } } diff --git a/InteractiveAutomationToolkit/Dialogs/Dialog [Obsolete].cs b/InteractiveAutomationToolkit/Dialogs/Dialog [Obsolete].cs new file mode 100644 index 0000000..029c74a --- /dev/null +++ b/InteractiveAutomationToolkit/Dialogs/Dialog [Obsolete].cs @@ -0,0 +1,379 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Linq; + + using Skyline.DataMiner.Automation; + using Skyline.DataMiner.Net.Exceptions; + + /// + /// A dialog represents a single window that can be shown. + /// You can show widgets in the window by adding them to the dialog. + /// The dialog uses a grid to determine the layout of its widgets. + /// + [Obsolete("Use Dialog instead", false)] + public abstract class Dialog : Dialog + { + private bool isEnabled = true; + + /// + /// Initializes a new instance of the class. + /// + /// Allows interaction with the DataMiner System. + protected Dialog(IEngine engine) : base(engine) + { + if (engine == null) + { + throw new ArgumentNullException("engine"); + } + + Title = "Dialog"; + AllowOverlappingWidgets = false; + } + + /// + /// Gets the number of columns of the grid layout. + /// + public int ColumnCount + { + get + { + var widgets = Panel.GetWidgetLocationPairs(); + if (!widgets.Any()) + { + return 0; + } + + var columns = new bool[widgets.Max(p => p.Location.Column + p.Location.ColumnSpan)]; + foreach (var widget in widgets) + { + for (int i = widget.Location.Column; i < widget.Location.Column + widget.Location.ColumnSpan; i++) + { + columns[i] = true; + } + } + + return columns.Count(c => c); + } + } + + /// + /// Gets the number of rows in the grid layout. + /// + public int RowCount + { + get + { + var widgets = Panel.GetWidgetLocationPairs(); + if (!widgets.Any()) + { + return 0; + } + + var rows = new bool[widgets.Max(p => p.Location.Row + p.Location.RowSpan)]; + foreach (var widget in widgets) + { + for (int i = widget.Location.Row; i < widget.Location.Row + widget.Location.RowSpan; i++) + { + rows[i] = true; + } + } + + return rows.Count(c => c); + } + } + + /// + /// Gets or sets a value indicating whether the interactive widgets within the dialog are enabled or not. + /// + public bool IsEnabled + { + get + { + return isEnabled; + } + + set + { + isEnabled = value; + foreach (Widget widget in Widgets) + { + InteractiveWidget interactiveWidget = widget as InteractiveWidget; + if (interactiveWidget != null && !(interactiveWidget is CollapseButton)) + { + interactiveWidget.IsEnabled = isEnabled; + } + } + } + } + + /// + /// Gets widgets that are added to the dialog. + /// + public IEnumerable Widgets + { + get + { + return Panel.GetAllWidgetLocationPairs().Select(w => (Widget)w.Widget); + } + } + + /// + /// Adds a widget to the dialog. + /// + /// Widget to add to the dialog. + /// Location of the widget on the grid layout. + /// The dialog. + /// When the widget is null. + /// When the widget has already been added to the dialog. + public Dialog AddWidget(Widget widget, IWidgetLayout widgetLayout) + { + if (widget == null) + { + throw new ArgumentNullException("widget"); + } + + var existingWidget = Widgets.FirstOrDefault(w => w == widget); + if (existingWidget != null) + { + throw new ArgumentException("Widget is already added to the dialog"); + } + + widget.HorizontalAlignment = widgetLayout.HorizontalAlignment; + widget.VerticalAlignment = widgetLayout.VerticalAlignment; + widget.Margin = widgetLayout.Margin; + widget.Parent = null; + Panel.Add(widget, new WidgetLocation(widgetLayout.Row, widgetLayout.Column, widgetLayout.RowSpan, widgetLayout.ColumnSpan)); + return this; + } + + /// + /// Adds a widget to the dialog. + /// + /// Widget to add to the dialog. + /// Row location of widget on the grid. + /// Column location of the widget on the grid. + /// Horizontal alignment of the widget. + /// Vertical alignment of the widget. + /// The dialog. + /// When the widget is null. + /// When the widget has already been added to the dialog. + public Dialog AddWidget( + Widget widget, + int row, + int column, + HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment verticalAlignment = VerticalAlignment.Center) + { + AddWidget(widget, new WidgetLayout(row, column, horizontalAlignment, verticalAlignment)); + return this; + } + + /// + /// Adds a widget to the dialog. + /// + /// Widget to add to the dialog. + /// Row location of widget on the grid. + /// Column location of the widget on the grid. + /// Number of rows the widget will use. + /// Number of columns the widget will use. + /// Horizontal alignment of the widget. + /// Vertical alignment of the widget. + /// The dialog. + /// When the widget is null. + /// When the widget has already been added to the dialog. + public Dialog AddWidget( + Widget widget, + int fromRow, + int fromColumn, + int rowSpan, + int colSpan, + HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment verticalAlignment = VerticalAlignment.Center) + { + AddWidget( + widget, + new WidgetLayout(fromRow, fromColumn, rowSpan, colSpan, horizontalAlignment, verticalAlignment)); + return this; + } + + /// + /// Gets the layout of the widget in the dialog. + /// + /// A widget that is part of the dialog. + /// The widget layout in the dialog. + /// When the widget is null. + /// When the widget is not part of the dialog. + public IWidgetLayout GetWidgetLayout(Widget widget) + { + if (widget == null) + { + throw new ArgumentNullException("widget"); + } + + var existingWidget = Panel.GetWidgetLocationPairs().FirstOrDefault(w => w.Widget == widget); + if (existingWidget.Widget is null) + { + throw new ArgumentException("Widget is not part of this dialog"); + } + + return new WidgetLayout( + existingWidget.Location.Row, + existingWidget.Location.Column, + existingWidget.Location.RowSpan, + existingWidget.Location.ColumnSpan, + existingWidget.Widget.HorizontalAlignment, + existingWidget.Widget.VerticalAlignment); + } + + /// + /// Removes a widget from the dialog. + /// + /// Widget to remove. + /// When the widget is null. + public void RemoveWidget(Widget widget) + { + if (widget == null) + { + throw new ArgumentNullException("widget"); + } + + Panel.Remove(widget); + foreach (var panel in Panel.GetPanels()) + { + switch (panel) + { + case GridPanel gridPanel: + gridPanel.Remove(widget); + break; + + case FormPanel formPanel: + formPanel.Remove(widget); + break; + + case StackPanel stackPanel: + stackPanel.Remove(widget); + break; + + default: + throw new ArgumentException("Widget is not part of this dialog"); + } + } + } + + /// + /// Adds the widgets from the section to the dialog. + /// + /// Section to be added to the dialog. + /// Left top position of the section within the dialog. + /// Updated dialog. + public Dialog AddSection(Section section, SectionLayout layout) + { + foreach (Widget widget in section.Widgets) + { + IWidgetLayout widgetLayout = section.GetWidgetLayout(widget); + AddWidget( + widget, + new WidgetLayout( + widgetLayout.Row + layout.Row, + widgetLayout.Column + layout.Column, + widgetLayout.RowSpan, + widgetLayout.ColumnSpan, + widgetLayout.HorizontalAlignment, + widgetLayout.VerticalAlignment)); + } + + return this; + } + + /// + /// Adds the widgets from the section to the dialog. + /// + /// Section to be added to the dialog. + /// Row in the dialog where the section should be added. + /// Column in the dialog where the section should be added. + /// Updated dialog. + public Dialog AddSection(Section section, int fromRow, int fromColumn) + { + return AddSection(section, new SectionLayout(fromRow, fromColumn)); + } + + /// + /// Sets the layout of the widget in the dialog. + /// + /// A widget that is part of the dialog. + /// The layout to apply to the widget. + /// When widget is null. + /// When the widget is not part of the dialog. + public void SetWidgetLayout(Widget widget, IWidgetLayout widgetLayout) + { + if (widget == null) + { + throw new ArgumentNullException("widget"); + } + + var existingWidget = Widgets.FirstOrDefault(w => w == widget); + if (existingWidget is null) + { + throw new ArgumentException("Widget is not part of this dialog"); + } + + Panel.Move(widget, widgetLayout.Row, widgetLayout.Column, widgetLayout.RowSpan, widgetLayout.ColumnSpan); + existingWidget.HorizontalAlignment = widgetLayout.HorizontalAlignment; + existingWidget.VerticalAlignment = widgetLayout.VerticalAlignment; + existingWidget.Margin = widgetLayout.Margin; + } + + /// + /// Shows the dialog window. + /// Also loads changes and triggers events when is true. + /// + /// If the dialog expects user interaction. + /// Should only be used when you create your own event loop. + public void Show(bool requireResponse = true) + { + UIBuilder uiBuilder = Build(); + uiBuilder.RequireResponse = requireResponse; + + IUIResults uiResults; + + try + { + uiResults = new WrappedUIResults(Engine.ShowUI(uiBuilder)); + } + catch (InteractiveUserDetachedException) + { + throw; + } + catch (DataMinerException e) + { + throw new InvalidOperationException($"{nameof(IEngine)}.{nameof(Engine.ShowUI)} failed with {nameof(UIBuilder)} argument {uiBuilder}", e); + } + + if (requireResponse) + { + LoadChanges(uiResults); + RaiseResultEvents(uiResults); + } + } + + /// + /// Hides the dialog. This does not block any background logic from running. + /// Use if you want to show the dialog again. + /// + public void Hide() + { + Engine.HideUI(); + } + + /// + /// Removes all widgets from the dialog. + /// + public void Clear() + { + Panel.Clear(); + } + } +} diff --git a/InteractiveAutomationToolkit/Dialogs/Dialog.cs b/InteractiveAutomationToolkit/Dialogs/Dialog.cs index 68685dd..da4f6da 100644 --- a/InteractiveAutomationToolkit/Dialogs/Dialog.cs +++ b/InteractiveAutomationToolkit/Dialogs/Dialog.cs @@ -2,24 +2,16 @@ { using System; using System.Collections.Generic; - using System.ComponentModel; using System.Linq; using Skyline.DataMiner.Automation; using Skyline.DataMiner.Net.Exceptions; - /// - /// A dialog represents a single window that can be shown. - /// You can show widgets in the window by adding them to the dialog. - /// The dialog uses a grid to determine the layout of its widgets. - /// - public abstract class Dialog + public class Dialog : IDialog where TPanel : IPanel, new() { private const string Auto = "auto"; private const string Stretch = "*"; - private readonly Dictionary widgetLayouts = new Dictionary(); - private readonly Dictionary columnDefinitions = new Dictionary(); private readonly Dictionary rowDefinitions = new Dictionary(); @@ -29,573 +21,278 @@ public abstract class Dialog private int minHeight; private int minWidth; private int width; - private bool isEnabled = true; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Allows interaction with the DataMiner System. - protected Dialog(IEngine engine) + public Dialog(IEngine engine) { - if (engine == null) - { - throw new ArgumentNullException("engine"); - } - - Engine = engine; + Engine = engine ?? throw new ArgumentNullException(nameof(engine)); width = -1; height = -1; MaxHeight = Int32.MaxValue; MinHeight = 1; MaxWidth = Int32.MaxValue; MinWidth = 1; - RowCount = 0; - ColumnCount = 0; - Title = "Dialog"; + Title = null; AllowOverlappingWidgets = false; } - /// - /// Triggered when the back button of the dialog is pressed. - /// + /// public event EventHandler Back; - /// - /// Triggered when the forward button of the dialog is pressed. - /// + /// public event EventHandler Forward; - /// - /// Triggered when there is any user interaction. - /// + /// public event EventHandler Interacted; - /// - /// Gets or sets a value indicating whether overlapping widgets are allowed or not. - /// Can be used in case you want to add multiple widgets to the same cell in the dialog. - /// You can use the Margin property on the widgets to place them apart. - /// - public bool AllowOverlappingWidgets { get; set; } + /// + IPanel IDialog.Panel => Panel; - /// - /// Gets the number of columns of the grid layout. - /// - public int ColumnCount { get; private set; } + /// + public TPanel Panel { get; } = new TPanel(); - /// - /// Gets the link to the SLAutomation process. - /// - public IEngine Engine { get; private set; } + /// + public IEngine Engine { get; } - /// - /// Gets or sets the fixed height (in pixels) of the dialog. - /// - /// - /// The user will still be able to resize the window, - /// but scrollbars will appear immediately. - /// should be used instead as it has a more desired effect. - /// - /// When the value is smaller than 1. + /// + public bool AllowOverlappingWidgets { get; set; } + + /// public int Height { - get - { - return height; - } + get => height; set { if (value <= 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } height = value; } } - /// - /// Gets or sets the maximum height (in pixels) of the dialog. - /// - /// - /// The user will still be able to resize the window past this limit. - /// - /// When the value is smaller than 1. - public int MaxHeight + /// + public int MinHeight { - get - { - return maxHeight; - } + get => minHeight; set { if (value <= 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } - maxHeight = value; + minHeight = value; } } - /// - /// Gets or sets the maximum width (in pixels) of the dialog. - /// - /// - /// The user will still be able to resize the window past this limit. - /// - /// When the value is smaller than 1. - public int MaxWidth + /// + public int MaxHeight { - get - { - return maxWidth; - } + get => maxHeight; set { if (value <= 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } - maxWidth = value; + maxHeight = value; } } - /// - /// Gets or sets the minimum height (in pixels) of the dialog. - /// - /// When the value is smaller than 1. - public int MinHeight + /// + public int Width { - get - { - return minHeight; - } + get => width; set { if (value <= 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } - minHeight = value; + width = value; } } - /// - /// Gets or sets the minimum width (in pixels) of the dialog. - /// - /// When the value is smaller than 1. + /// public int MinWidth { - get - { - return minWidth; - } + get => minWidth; set { if (value <= 0) { - throw new ArgumentOutOfRangeException("value"); + throw new ArgumentOutOfRangeException(nameof(value)); } minWidth = value; } } - /// - /// Gets the number of rows in the grid layout. - /// - public int RowCount { get; private set; } - - /// - /// Gets or sets a value indicating whether the interactive widgets within the dialog are enabled or not. - /// - public bool IsEnabled + /// + public int MaxWidth { - get - { - return isEnabled; - } + get => maxWidth; set { - isEnabled = value; - foreach (Widget widget in Widgets) + if (value <= 0) { - InteractiveWidget interactiveWidget = widget as InteractiveWidget; - if (interactiveWidget != null && !(interactiveWidget is CollapseButton)) - { - interactiveWidget.IsEnabled = isEnabled; - } + throw new ArgumentOutOfRangeException(nameof(value)); } + + maxWidth = value; } } - /// - /// Gets or sets the title at the top of the window. - /// - /// Available from DataMiner 9.6.6 onwards. + /// public string Title { get; set; } - /// - /// Gets or sets the value indicating whether a confirmation popup should be shown whenever a user decides to abort the script when this Dialog is shown. - /// Aborting an interactive script is done by closing the window in which the dialog is displayed. - /// - /// Setting the to or overrides this setting. - /// Available from DataMiner 10.4.12 onwards. + /// public bool ShowScriptAbortPopup { get; set; } = true; - /// - /// Gets widgets that are added to the dialog. - /// - public IEnumerable Widgets - { - get - { - return widgetLayouts.Keys; - } - } - - /// - /// Gets or sets the fixed width (in pixels) of the dialog. - /// - /// - /// The user will still be able to resize the window, - /// but scrollbars will appear immediately. - /// should be used instead as it has a more desired effect. - /// - /// When the value is smaller than 1. - public int Width - { - get - { - return width; - } + internal bool RequiresResponse => Panel.GetWidgets(true).OfType().Any(x => x.IsVisible && x.RequiresResponse); - set - { - if (value <= 0) - { - throw new ArgumentOutOfRangeException("value"); - } - - width = value; - } - } - - internal bool RequiresResponse => Widgets.OfType().Any(x => x.IsVisible && x.RequiresResponse); - - /// - /// Adds a widget to the dialog. - /// - /// Widget to add to the dialog. - /// Location of the widget on the grid layout. - /// The dialog. - /// When the widget is null. - /// When the widget has already been added to the dialog. - public Dialog AddWidget(Widget widget, IWidgetLayout widgetLayout) + /// + public void SetColumnWidth(int column, int columnWidth) { - if (widget == null) + if (column < 0) { - throw new ArgumentNullException("widget"); + throw new ArgumentOutOfRangeException(nameof(column)); } - if (widgetLayouts.ContainsKey(widget)) + if (columnWidth < 0) { - throw new ArgumentException("Widget is already added to the dialog"); + throw new ArgumentOutOfRangeException(nameof(columnWidth)); } - widgetLayouts.Add(widget, widgetLayout); - - SortedSet rowsInUse; - SortedSet columnsInUse; - this.FillRowsAndColumnsInUse(out rowsInUse, out columnsInUse); - - return this; - } - - /// - /// Adds a widget to the dialog. - /// - /// Widget to add to the dialog. - /// Row location of widget on the grid. - /// Column location of the widget on the grid. - /// Horizontal alignment of the widget. - /// Vertical alignment of the widget. - /// The dialog. - /// When the widget is null. - /// When the widget has already been added to the dialog. - public Dialog AddWidget( - Widget widget, - int row, - int column, - HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment verticalAlignment = VerticalAlignment.Center) - { - AddWidget(widget, new WidgetLayout(row, column, horizontalAlignment, verticalAlignment)); - return this; - } - - /// - /// Adds a widget to the dialog. - /// - /// Widget to add to the dialog. - /// Row location of widget on the grid. - /// Column location of the widget on the grid. - /// Number of rows the widget will use. - /// Number of columns the widget will use. - /// Horizontal alignment of the widget. - /// Vertical alignment of the widget. - /// The dialog. - /// When the widget is null. - /// When the widget has already been added to the dialog. - public Dialog AddWidget( - Widget widget, - int fromRow, - int fromColumn, - int rowSpan, - int colSpan, - HorizontalAlignment horizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment verticalAlignment = VerticalAlignment.Center) - { - AddWidget( - widget, - new WidgetLayout(fromRow, fromColumn, rowSpan, colSpan, horizontalAlignment, verticalAlignment)); - return this; - } - - /// - /// Gets the layout of the widget in the dialog. - /// - /// A widget that is part of the dialog. - /// The widget layout in the dialog. - /// When the widget is null. - /// When the widget is not part of the dialog. - public IWidgetLayout GetWidgetLayout(Widget widget) - { - CheckWidgetExists(widget); - return widgetLayouts[widget]; + columnDefinitions[column] = columnWidth.ToString(); } - /// - /// Removes a widget from the dialog. - /// - /// Widget to remove. - /// When the widget is null. - public void RemoveWidget(Widget widget) + /// + public void SetColumnWidthAuto(int column) { - if (widget == null) + if (column < 0) { - throw new ArgumentNullException("widget"); + throw new ArgumentOutOfRangeException(nameof(column)); } - widgetLayouts.Remove(widget); - - SortedSet rowsInUse; - SortedSet columnsInUse; - this.FillRowsAndColumnsInUse(out rowsInUse, out columnsInUse); + columnDefinitions[column] = Auto; } - /// - /// Adds the widgets from the section to the dialog. - /// - /// Section to be added to the dialog. - /// Left top position of the section within the dialog. - /// Updated dialog. - public Dialog AddSection(Section section, SectionLayout layout) + /// + public void SetColumnWidthStretch(int column) { - foreach (Widget widget in section.Widgets) + if (column < 0) { - IWidgetLayout widgetLayout = section.GetWidgetLayout(widget); - AddWidget( - widget, - new WidgetLayout( - widgetLayout.Row + layout.Row, - widgetLayout.Column + layout.Column, - widgetLayout.RowSpan, - widgetLayout.ColumnSpan, - widgetLayout.HorizontalAlignment, - widgetLayout.VerticalAlignment)); + throw new ArgumentOutOfRangeException(nameof(column)); } - return this; - } - - /// - /// Adds the widgets from the section to the dialog. - /// - /// Section to be added to the dialog. - /// Row in the dialog where the section should be added. - /// Column in the dialog where the section should be added. - /// Updated dialog. - public Dialog AddSection(Section section, int fromRow, int fromColumn) - { - return AddSection(section, new SectionLayout(fromRow, fromColumn)); + columnDefinitions[column] = Stretch; } - /// - /// Applies a fixed width (in pixels) to a column. - /// - /// The index of the column on the grid. - /// The width of the column. - /// When the column index does not exist. - /// When the column width is smaller than 0. - public void SetColumnWidth(int column, int columnWidth) + /// + public void SetRowHeight(int row, int rowHeight) { - if (column < 0) + if (row < 0) { - throw new ArgumentOutOfRangeException("column"); + throw new ArgumentOutOfRangeException(nameof(row)); } - if (columnWidth < 0) + if (rowHeight < 0) { - throw new ArgumentOutOfRangeException("columnWidth"); + throw new ArgumentOutOfRangeException(nameof(rowHeight)); } - if (columnDefinitions.ContainsKey(column)) - { - columnDefinitions[column] = columnWidth.ToString(); - } - else - { - columnDefinitions.Add(column, columnWidth.ToString()); - } + rowDefinitions[row] = rowHeight.ToString(); } - /// - /// The width of the column will be automatically adapted to the widest widget in that column. - /// - /// The index of the column on the grid. - /// When the column index does not exist. - public void SetColumnWidthAuto(int column) + /// + public void SetRowHeightAuto(int row) { - if (column < 0) + if (row < 0) { - throw new ArgumentOutOfRangeException("column"); + throw new ArgumentOutOfRangeException(nameof(row)); } - if (columnDefinitions.ContainsKey(column)) - { - columnDefinitions[column] = Auto; - } - else - { - columnDefinitions.Add(column, Auto); - } + rowDefinitions[row] = Auto; } - /// - /// The column will have the largest possible width, depending on the width of the other columns. - /// - /// The index of the column on the grid. - /// When the column index does not exist. - public void SetColumnWidthStretch(int column) + /// + public void SetRowHeightStretch(int row) { - if (column < 0) + if (row < 0) { - throw new ArgumentOutOfRangeException("column"); + throw new ArgumentOutOfRangeException(nameof(row)); } - if (columnDefinitions.ContainsKey(column)) - { - columnDefinitions[column] = Stretch; - } - else - { - columnDefinitions.Add(column, Stretch); - } + rowDefinitions[row] = Stretch; } - /// - /// Applies a fixed height (in pixels) to a row. - /// - /// The index of the row on the grid. - /// The height of the column. - /// When the row index is smaller than 0. - /// When the row height is smaller than 0. - public void SetRowHeight(int row, int rowHeight) + /// + public void ShowStatic(bool disabled) { - if (row < 0) - { - throw new ArgumentOutOfRangeException("row"); - } + IInteractiveWidget[] widgets = disabled + ? GetEnabledWidgets() + : Array.Empty(); - if (rowHeight <= 0) - { - throw new ArgumentOutOfRangeException("rowHeight"); - } + DisableWidgets(widgets); - if (rowDefinitions.ContainsKey(row)) - { - rowDefinitions[row] = rowHeight.ToString(); - } - else - { - rowDefinitions.Add(row, rowHeight.ToString()); - } - } + UIBuilder uiBuilder = Build(); + uiBuilder.RequireResponse = false; - /// - /// The height of the row will be automatically adapted to the highest widget in that row. - /// - /// The index of the row on the grid. - /// When the row index is smaller than 0. - public void SetRowHeightAuto(int row) - { - if (row < 0) + try { - throw new ArgumentOutOfRangeException("row"); + Engine.ShowUI(uiBuilder); + Engine.KeepAlive(); } - - if (rowDefinitions.ContainsKey(row)) + catch (InteractiveUserDetachedException) { - rowDefinitions[row] = Auto; + throw; } - else + catch (DataMinerException e) { - rowDefinitions.Add(row, Auto); + throw new InvalidOperationException($"{nameof(IEngine)}.{nameof(Engine.ShowUI)} failed with {nameof(UIBuilder)} argument {uiBuilder}", e); } + + EnableWidgets(widgets); } - /// - /// The row will have the largest possible height, depending on the height of the other rows. - /// - /// The index of the row on the grid. - /// When the row index is smaller than 0. - public void SetRowHeightStretch(int row) + /// + public void ShowInteractive() { - if (row < 0) + UIBuilder uiBuilder = Build(); + uiBuilder.RequireResponse = true; + + IUIResults uiResults; + + try { - throw new ArgumentOutOfRangeException("row"); + uiResults = new WrappedUIResults(Engine.ShowUI(uiBuilder)); + Engine.KeepAlive(); } - - if (rowDefinitions.ContainsKey(row)) + catch (InteractiveUserDetachedException) { - rowDefinitions[row] = Stretch; + throw; } - else + catch (DataMinerException e) { - rowDefinitions.Add(row, Stretch); + throw new InvalidOperationException($"{nameof(IEngine)}.{nameof(Engine.ShowUI)} failed with {nameof(UIBuilder)} argument {uiBuilder}", e); } - } - /// - /// Sets the layout of the widget in the dialog. - /// - /// A widget that is part of the dialog. - /// The layout to apply to the widget. - /// When widget is null. - /// When the widget is not part of the dialog. - public void SetWidgetLayout(Widget widget, IWidgetLayout widgetLayout) - { - CheckWidgetExists(widget); - widgetLayouts[widget] = widgetLayout; + LoadChanges(uiResults); + RaiseResultEvents(uiResults); } /// @@ -604,6 +301,7 @@ public void SetWidgetLayout(Widget widget, IWidgetLayout widgetLayout) /// /// If the dialog expects user interaction. /// Should only be used when you create your own event loop. + [Obsolete("Use 'ShowInteractive' or 'ShowStatic' instead.", false)] public void Show(bool requireResponse = true) { UIBuilder uiBuilder = Build(); @@ -631,303 +329,169 @@ public void Show(bool requireResponse = true) } } - /// - /// Hides the dialog. This does not block any background logic from running. - /// Use if you want to show the dialog again. - /// - public void Hide() - { - Engine.HideUI(); - } - - /// - /// Removes all widgets from the dialog. - /// - public void Clear() - { - widgetLayouts.Clear(); - RowCount = 0; - ColumnCount = 0; - } - - private static string AlignmentToUiString(HorizontalAlignment horizontalAlignment) + internal UIBuilder Build() { - switch (horizontalAlignment) - { - case HorizontalAlignment.Center: - return "Center"; - case HorizontalAlignment.Left: - return "Left"; - case HorizontalAlignment.Right: - return "Right"; - case HorizontalAlignment.Stretch: - return "Stretch"; - default: - throw new InvalidEnumArgumentException( - "horizontalAlignment", - (int)horizontalAlignment, - typeof(HorizontalAlignment)); - } - } + WidgetLocationPair[] visibleWidgetLocationPairs = Panel.GetWidgetLocationPairs() + .Where(pair => pair.Widget.IsVisible) + .ToArray(); - private static string AlignmentToUiString(VerticalAlignment verticalAlignment) - { - switch (verticalAlignment) - { - case VerticalAlignment.Center: - return "Center"; - case VerticalAlignment.Top: - return "Top"; - case VerticalAlignment.Bottom: - return "Bottom"; - case VerticalAlignment.Stretch: - return "Stretch"; - default: - throw new InvalidEnumArgumentException( - "verticalAlignment", - (int)verticalAlignment, - typeof(VerticalAlignment)); + if (!AllowOverlappingWidgets) + { + CheckIfWidgetsOverlap(visibleWidgetLocationPairs); } - } - /// - /// Checks if any visible widgets in the Dialog overlap. - /// - /// Thrown when two visible widgets overlap with each other. - private void CheckIfVisibleWidgetsOverlap() - { - if (AllowOverlappingWidgets) + // Initialize UI Builder + var builder = new UIBuilder { - return; - } + Height = Height, + MinHeight = MinHeight, + Width = Width, + MinWidth = MinWidth, + RowDefs = GetRowDefinitions(), + ColumnDefs = GetColumnDefinitions(), + Title = Title, + SkipAbortConfirmation = !ShowScriptAbortPopup, + }; - foreach (Widget widget in widgetLayouts.Keys) + foreach (WidgetLocationPair widgetLocationPair in visibleWidgetLocationPairs) { - if (!widget.IsVisible) + IWidget widget = widgetLocationPair.Widget; + WidgetLocation location = widgetLocationPair.Location; + + if (widget.Type == UIBlockType.Undefined) { continue; } - CheckIfVisibleWidgetOverlaps(widget); + UIBlockDefinition blockDefinition = widget.BlockDefinition; + blockDefinition.Row = location.Row; + blockDefinition.RowSpan = location.RowSpan; + blockDefinition.Column = location.Column; + blockDefinition.ColumnSpan = location.ColumnSpan; + builder.AppendBlock(blockDefinition); } + + return builder; } - private void CheckIfVisibleWidgetOverlaps(Widget widget) + internal void LoadChanges(IUIResults uir) { - IWidgetLayout widgetLayout = widgetLayouts[widget]; - for (int column = widgetLayout.Column; column < widgetLayout.Column + widgetLayout.ColumnSpan; column++) + foreach (InteractiveWidget interactiveWidget in Panel.GetWidgets().OfType()) { - for (int row = widgetLayout.Row; row < widgetLayout.Row + widgetLayout.RowSpan; row++) + if (interactiveWidget.IsVisible) { - CheckIfOtherWidgetsAreVisibleOnPosition(widget, widgetLayout, row, column); + interactiveWidget.LoadResult(uir); } } } - private void CheckIfOtherWidgetsAreVisibleOnPosition(Widget widget, IWidgetLayout layout, int row, int column) + internal void RaiseResultEvents(IUIResults uir) { - foreach (Widget otherWidget in widgetLayouts.Keys) - { - if (!otherWidget.IsVisible || widget.Equals(otherWidget)) - { - continue; - } + Interacted?.Invoke(this, EventArgs.Empty); - IWidgetLayout otherWidgetLayout = widgetLayouts[otherWidget]; - if (column >= otherWidgetLayout.Column && column < otherWidgetLayout.Column + otherWidgetLayout.ColumnSpan && row >= otherWidgetLayout.Row && row < otherWidgetLayout.Row + otherWidgetLayout.RowSpan) - { - throw new OverlappingWidgetsException(String.Format("The widget overlaps with another widget in the Dialog on Row {0}, Column {1}, RowSpan {2}, ColumnSpan {3}", layout.Row, layout.Column, layout.RowSpan, layout.ColumnSpan)); - } + if (uir.WasBack() && Back != null) + { + Back(this, EventArgs.Empty); + return; } - } - private string GetRowDefinitions(SortedSet rowsInUse) - { - string[] definitions = new string[rowsInUse.Count]; - int currentIndex = 0; - foreach (int rowInUse in rowsInUse) + if (uir.WasForward() && Forward != null) { - string value; - if (rowDefinitions.TryGetValue(rowInUse, out value)) - { - definitions[currentIndex] = value; - } - else - { - definitions[currentIndex] = Auto; - } - - currentIndex++; + Forward(this, EventArgs.Empty); + return; } - return String.Join(";", definitions); - } + // ToList is necessary to prevent InvalidOperationException when adding or removing widgets from a event handler. + List intractableWidgets = Panel.GetWidgets() + .OfType() + .Where(widget => widget.BlockDefinition.WantsOnChange) + .ToList(); - private string GetColumnDefinitions(SortedSet columnsInUse) - { - string[] definitions = new string[columnsInUse.Count]; - int currentIndex = 0; - foreach (int columnInUse in columnsInUse) + foreach (InteractiveWidget intractable in intractableWidgets) { - string value; - if (columnDefinitions.TryGetValue(columnInUse, out value)) - { - definitions[currentIndex] = value; - } - else - { - definitions[currentIndex] = Auto; - } - - currentIndex++; + intractable.RaiseResultEvents(); } - - return String.Join(";", definitions); } - internal UIBuilder Build() + private static void CheckIfWidgetsOverlap(WidgetLocationPair[] widgetLocationPairs) { - // Check rows and columns in use - SortedSet rowsInUse; - SortedSet columnsInUse; - this.FillRowsAndColumnsInUse(out rowsInUse, out columnsInUse); - - // Check if visible widgets overlap and throw exception if this is the case - CheckIfVisibleWidgetsOverlap(); - - // Initialize UI Builder - var uiBuilder = new UIBuilder - { - Height = Height, - MinHeight = MinHeight, - Width = Width, - MinWidth = MinWidth, - RowDefs = GetRowDefinitions(rowsInUse), - ColumnDefs = GetColumnDefinitions(columnsInUse), - Title = Title, - SkipAbortConfirmation = !ShowScriptAbortPopup - }; + var builder = new OverlappingWidgetsException.Builder(); - KeyValuePair defaultKeyValuePair = default(KeyValuePair); - int rowIndex = 0; - int columnIndex = 0; - foreach (int rowInUse in rowsInUse) + for (var i = 0; i < widgetLocationPairs.Length; i++) { - columnIndex = 0; - foreach (int columnInUse in columnsInUse) + IWidget widget = widgetLocationPairs[i].Widget; + WidgetLocation location = widgetLocationPairs[i].Location; + for (int j = i + 1; j < widgetLocationPairs.Length; j++) { - foreach (KeyValuePair keyValuePair in widgetLayouts.Where(x => x.Key.IsVisible && x.Key.Type != UIBlockType.Undefined && x.Value.Row.Equals(rowInUse) && x.Value.Column.Equals(columnInUse))) + IWidget otherWidget = widgetLocationPairs[j].Widget; + WidgetLocation otherLocation = widgetLocationPairs[j].Location; + if (location.Overlaps(otherLocation)) { - if (keyValuePair.Equals(defaultKeyValuePair)) - { - continue; - } - - // Can be removed once we retrieve all collapsed states from the UI - TreeView treeView = keyValuePair.Key as TreeView; - if (treeView != null) - { - treeView.UpdateItemCache(); - } - - UIBlockDefinition widgetBlockDefinition = keyValuePair.Key.BlockDefinition; - IWidgetLayout widgetLayout = keyValuePair.Value; - - widgetBlockDefinition.Column = columnIndex; - widgetBlockDefinition.ColumnSpan = widgetLayout.ColumnSpan; - widgetBlockDefinition.Row = rowIndex; - widgetBlockDefinition.RowSpan = widgetLayout.RowSpan; - widgetBlockDefinition.HorizontalAlignment = AlignmentToUiString(widgetLayout.HorizontalAlignment); - widgetBlockDefinition.VerticalAlignment = AlignmentToUiString(widgetLayout.VerticalAlignment); - widgetBlockDefinition.Margin = widgetLayout.Margin.ToString(); - - uiBuilder.AppendBlock(widgetBlockDefinition); + builder.Add(widget, location, otherWidget, otherLocation); } - - columnIndex++; } - - rowIndex++; } - return uiBuilder; + if (builder.Count != 0) + { + throw builder.Build(); + } } - /// - /// Used to retrieve the rows and columns that are being used and updates the RowCount and ColumnCount properties based on the Widgets added to the dialog. - /// - /// Collection containing the rows that are defined by the Widgets in the Dialog. - /// Collection containing the columns that are defined by the Widgets in the Dialog. - private void FillRowsAndColumnsInUse(out SortedSet rowsInUse, out SortedSet columnsInUse) + private static void EnableWidgets(IEnumerable widgets) { - rowsInUse = new SortedSet(); - columnsInUse = new SortedSet(); - foreach (KeyValuePair keyValuePair in this.widgetLayouts) + foreach (IInteractiveWidget widget in widgets) { - if (keyValuePair.Key.IsVisible && keyValuePair.Key.Type != UIBlockType.Undefined) - { - for (int i = keyValuePair.Value.Row; i < keyValuePair.Value.Row + keyValuePair.Value.RowSpan; i++) - { - rowsInUse.Add(i); - } - - for (int i = keyValuePair.Value.Column; i < keyValuePair.Value.Column + keyValuePair.Value.ColumnSpan; i++) - { - columnsInUse.Add(i); - } - } + widget.IsEnabled = true; } - - this.RowCount = rowsInUse.Count; - this.ColumnCount = columnsInUse.Count; } - // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local - private void CheckWidgetExists(Widget widget) + private static void DisableWidgets(IEnumerable widgets) { - if (widget == null) + foreach (IInteractiveWidget widget in widgets) { - throw new ArgumentNullException("widget"); + widget.IsEnabled = false; } + } - if (!widgetLayouts.ContainsKey(widget)) - { - throw new ArgumentException("Widget is not part of this dialog"); - } + private string GetColumnDefinitions() + { + return GetDefinitions(columnDefinitions, Panel.GetColumnCount()); } - private void LoadChanges(IUIResults uir) + private string GetDefinitions(Dictionary definitions, int amount) { - foreach (InteractiveWidget interactiveWidget in Widgets.OfType()) + return String.Join(";", GetDefinitionsEnumerator() ?? Array.Empty()); + + // ReSharper disable once RedundantNameQualifier + // DIS code generation fails to generate this local function if the return type is not fully Qualified + System.Collections.Generic.IEnumerable GetDefinitionsEnumerator() { - if (interactiveWidget.IsVisible) + for (var i = 0; i < amount; i++) { - interactiveWidget.LoadResult(uir); + if (definitions.TryGetValue(i, out string s)) + { + yield return s; + } + else + { + yield return Auto; + } } } } - private void RaiseResultEvents(IUIResults uir) + private string GetRowDefinitions() { - Interacted?.Invoke(this, EventArgs.Empty); - - if (uir.WasBack() && Back != null) - { - Back(this, EventArgs.Empty); - return; - } - - if (uir.WasForward() && Forward != null) - { - Forward(this, EventArgs.Empty); - return; - } + return GetDefinitions(rowDefinitions, Panel.GetRowCount()); + } - // ToList is necessary to prevent InvalidOperationException when adding or removing widgets from a event handler. - foreach (InteractiveWidget intractable in Widgets.OfType().ToList()) - { - intractable.RaiseResultEvents(); - } + private IInteractiveWidget[] GetEnabledWidgets() + { + return Panel.GetWidgets(true) + .OfType() + .Where(widget => widget.IsEnabled) + .ToArray(); } } } diff --git a/InteractiveAutomationToolkit/Dialogs/ExceptionDialog.cs b/InteractiveAutomationToolkit/Dialogs/ExceptionDialog.cs index dfeee5a..b65351f 100644 --- a/InteractiveAutomationToolkit/Dialogs/ExceptionDialog.cs +++ b/InteractiveAutomationToolkit/Dialogs/ExceptionDialog.cs @@ -7,7 +7,7 @@ /// /// Dialog used to display an exception. /// - public class ExceptionDialog : Dialog + public class ExceptionDialog : Dialog { private readonly Label exceptionLabel = new Label(); private Exception exception; @@ -21,8 +21,8 @@ public ExceptionDialog(IEngine engine) : base(engine) Title = "Exception Occurred"; OkButton = new Button("OK") { Width = 130, Style = ButtonStyle.CallToAction }; - AddWidget(exceptionLabel, 0, 0); - AddWidget(OkButton, 1, 0); + Panel.Add(exceptionLabel, 0, 0); + Panel.Add(OkButton, 1, 0); } /// diff --git a/InteractiveAutomationToolkit/Dialogs/IDialog.cs b/InteractiveAutomationToolkit/Dialogs/IDialog.cs new file mode 100644 index 0000000..30d33bf --- /dev/null +++ b/InteractiveAutomationToolkit/Dialogs/IDialog.cs @@ -0,0 +1,182 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + + using Skyline.DataMiner.Automation; + + /// + /// A dialog represents a single window that can be shown. + /// You can add widgets to the dialog by adding them to the of the dialog. + /// + public interface IDialog + { + /// + /// Triggered when the back button of the dialog is pressed. + /// + event EventHandler Back; + + /// + /// Triggered when the forward button of the dialog is pressed. + /// + event EventHandler Forward; + + /// + /// Triggered when there is any user interaction before any other widget event. + /// + event EventHandler Interacted; + + /// + /// Gets the root panel of the dialog. Widgets and other panels can be added to build a UI. + /// + IPanel Panel { get; } + + /// + /// Gets the link to the SLAutomation process. + /// + IEngine Engine { get; } + + /// + /// Gets or sets a value indicating whether overlapping widgets are allowed or not. + /// Can be used in case you want to add multiple widgets to the same cell in the dialog. + /// You can use the Margin property on the widgets to place them apart. + /// + bool AllowOverlappingWidgets { get; set; } + + /// + /// Gets or sets the fixed height (in pixels) of the dialog. + /// + /// + /// The user will still be able to resize the window, + /// but scrollbars will appear immediately. + /// should be used instead as it has a more desired effect. + /// + /// When the value is smaller than 1. + int Height { get; set; } + + /// + /// Gets or sets the minimum height (in pixels) of the dialog. + /// + /// When the value is smaller than 1. + int MinHeight { get; set; } + + /// + /// Gets or sets the maximum height (in pixels) of the dialog. + /// + /// + /// The user will still be able to resize the window past this limit. + /// + /// When the value is smaller than 1. + int MaxHeight { get; set; } + + /// + /// Gets or sets the fixed width (in pixels) of the dialog. + /// + /// + /// The user will still be able to resize the window, + /// but scrollbars will appear immediately. + /// should be used instead as it has a more desired effect. + /// + /// When the value is smaller than 1. + int Width { get; set; } + + /// + /// Gets or sets the minimum width (in pixels) of the dialog. + /// + /// When the value is smaller than 1. + int MinWidth { get; set; } + + /// + /// Gets or sets the maximum width (in pixels) of the dialog. + /// + /// + /// The user will still be able to resize the window past this limit. + /// + /// When the value is smaller than 1. + int MaxWidth { get; set; } + + /// + /// Gets or sets the title at the top of the window. + /// + /// + /// Is set to null, the name of the script will be displayed instead.
+ /// Available from DataMiner 9.6.6 onwards. + ///
+ string Title { get; set; } + + /// + /// Gets or sets the value indicating whether a confirmation popup should be shown whenever a user decides to abort the script when this Dialog is shown. + /// Aborting an interactive script is done by closing the window in which the dialog is displayed. + /// + /// Setting the to or overrides this setting. + /// Available from DataMiner 10.4.12 onwards. + bool ShowScriptAbortPopup { get; set; } + + /// + /// Applies a fixed width (in pixels) to a column. + /// + /// The index of the column on the grid. + /// The width of the column. + /// When the column index does not exist. + /// When the column width is smaller than 0. + void SetColumnWidth(int column, int columnWidth); + + /// + /// The width of the column will be automatically adapted to the widest widget in that column. + /// + /// The index of the column on the grid. + /// When the column index does not exist. + void SetColumnWidthAuto(int column); + + /// + /// The column will have the largest possible width, depending on the width of the other columns. + /// + /// The index of the column on the grid. + /// When the column index does not exist. + void SetColumnWidthStretch(int column); + + /// + /// Applies a fixed height (in pixels) to a row. + /// + /// The index of the row on the grid. + /// The height of the column. + /// When the row index is smaller than 0. + /// When the row height is smaller than 0. + void SetRowHeight(int row, int rowHeight); + + /// + /// The height of the row will be automatically adapted to the highest widget in that row. + /// + /// The index of the row on the grid. + /// When the row index is smaller than 0. + void SetRowHeightAuto(int row); + + /// + /// The row will have the largest possible height, depending on the height of the other rows. + /// + /// The index of the row on the grid. + /// When the row index is smaller than 0. + void SetRowHeightStretch(int row); + + /// + /// Shows the dialog window and returns immediately. + /// + /// Users wont be able to interact with widgets. + /// When true, shows all widgets in a disabled state. + void ShowStatic(bool disabled); + + /// + /// Shows the dialog window and returns only after a user has interacted with a widget that has at least one event handler registered. + /// + void ShowInteractive(); + } + + /// + /// The type of the root panel used by the dialog. + public interface IDialog : IDialog where TPanel : IPanel, new() + { + /// + /// Gets the root panel of the dialog. Widgets and other panels can be added to build a UI. + /// + new TPanel Panel { get; } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Dialogs/MessageDialog.cs b/InteractiveAutomationToolkit/Dialogs/MessageDialog.cs index 945d9a9..e452a23 100644 --- a/InteractiveAutomationToolkit/Dialogs/MessageDialog.cs +++ b/InteractiveAutomationToolkit/Dialogs/MessageDialog.cs @@ -7,7 +7,7 @@ /// /// Dialog used to display a message. /// - public class MessageDialog : Dialog + public class MessageDialog : Dialog { private readonly Label messageLabel = new Label(); @@ -19,8 +19,8 @@ public MessageDialog(IEngine engine) : base(engine) { OkButton = new Button("OK") { Width = 130, Style = ButtonStyle.CallToAction }; - AddWidget(messageLabel, 0, 0); - AddWidget(OkButton, 1, 0); + Panel.Add(messageLabel, 0, 0); + Panel.Add(OkButton, 1, 0); } /// diff --git a/InteractiveAutomationToolkit/Dialogs/OkCancelDialog.cs b/InteractiveAutomationToolkit/Dialogs/OkCancelDialog.cs index 515a8a1..1a73738 100644 --- a/InteractiveAutomationToolkit/Dialogs/OkCancelDialog.cs +++ b/InteractiveAutomationToolkit/Dialogs/OkCancelDialog.cs @@ -6,7 +6,7 @@ /// /// Dialog used to ask user confirmation. /// - public class OkCancelDialog : Dialog + public class OkCancelDialog : Dialog { /// /// Initializes a new instance of the class. @@ -19,19 +19,19 @@ public OkCancelDialog(IEngine engine, string message, CallToAction callToAction OkButton.Style = callToAction == CallToAction.OK ? ButtonStyle.CallToAction : ButtonStyle.None; CancelButton.Style = callToAction == CallToAction.Cancel ? ButtonStyle.CallToAction : ButtonStyle.None; - AddWidget(new Label(message), 0, 0, 1, 2); + Panel.Add(new Label(message), 0, 0, 1, 2); - AddWidget(new WhiteSpace(), 1, 0); + Panel.Add(new WhiteSpace(), 1, 0); if (callToAction == CallToAction.OK) { - AddWidget(CancelButton, 2, 0); - AddWidget(OkButton, 2, 1, HorizontalAlignment.Right); + Panel.Add(CancelButton, 2, 0); + Panel.Add(OkButton, 2, 1); } else { - AddWidget(OkButton, 2, 0); - AddWidget(CancelButton, 2, 1, HorizontalAlignment.Right); + Panel.Add(OkButton, 2, 0); + Panel.Add(CancelButton, 2, 1); } SetColumnWidth(0, 140); @@ -41,12 +41,20 @@ public OkCancelDialog(IEngine engine, string message, CallToAction callToAction /// /// Gets the button with "OK" that is displayed below the message. /// - public Button OkButton { get; } = new Button("OK") { Width = 130 }; + public Button OkButton { get; } = new Button("OK") + { + Width = 130, + HorizontalAlignment = HorizontalAlignment.Right, + }; /// /// Gets the button with "Cancel" that is displayed below the message. /// - public Button CancelButton { get; } = new Button("Cancel") { Width = 130 }; + public Button CancelButton { get; } = new Button("Cancel") + { + Width = 130, + HorizontalAlignment = HorizontalAlignment.Right, + }; /// /// Shows the without passing through the @@ -70,7 +78,7 @@ public static bool Show(IEngine engine, string message, string title = "Are you dialog.OkButton.Pressed += (s, e) => okButtonPressed = true; dialog.CancelButton.Pressed += (s, e) => okButtonPressed = false; - dialog.Show(true); + dialog.ShowInteractive(); return okButtonPressed; } diff --git a/InteractiveAutomationToolkit/Dialogs/ProgressDialog.cs b/InteractiveAutomationToolkit/Dialogs/ProgressDialog.cs index d3b2e29..57cf076 100644 --- a/InteractiveAutomationToolkit/Dialogs/ProgressDialog.cs +++ b/InteractiveAutomationToolkit/Dialogs/ProgressDialog.cs @@ -9,7 +9,7 @@ /// When progress is displayed, this dialog has to be shown without requiring user interaction. /// When you are done displaying progress, call the Finish method and show the dialog with user interaction required. /// - public class ProgressDialog : Dialog + public class ProgressDialog : Dialog { private readonly StringBuilder progress = new StringBuilder(); private readonly Label progressLabel = new Label(); @@ -21,7 +21,12 @@ public class ProgressDialog : Dialog /// Link with DataMiner. public ProgressDialog(IEngine engine) : base(engine) { - OkButton = new Button("OK") { IsEnabled = true, Width = 130, Style = ButtonStyle.CallToAction }; + OkButton = new Button("OK") + { + IsEnabled = true, + Width = 130, + Style = ButtonStyle.CallToAction + }; } /// @@ -78,14 +83,14 @@ public void Finish() // TODO: ShowConfirmation { progressLabel.Text = progress.ToString(); - if (!Widgets.Contains(progressLabel)) + if (!Panel.GetWidgets().Contains(progressLabel)) { - AddWidget(progressLabel, 0, 0); + Panel.Add(progressLabel, 0, 0); } - if (!Widgets.Contains(OkButton)) + if (!Panel.GetWidgets().Contains(OkButton)) { - AddWidget(OkButton, 1, 0); + Panel.Add(OkButton, 1, 0); } } } diff --git a/InteractiveAutomationToolkit/Dialogs/YesNoDialog.cs b/InteractiveAutomationToolkit/Dialogs/YesNoDialog.cs index 5aa8149..0827315 100644 --- a/InteractiveAutomationToolkit/Dialogs/YesNoDialog.cs +++ b/InteractiveAutomationToolkit/Dialogs/YesNoDialog.cs @@ -5,7 +5,7 @@ /// /// Dialog used to ask the user a yes/no question. /// - public class YesNoDialog : Dialog + public class YesNoDialog : Dialog { /// /// Initializes a new instance of the class. @@ -18,19 +18,19 @@ public YesNoDialog(IEngine engine, string message, CallToAction callToAction = C YesButton.Style = callToAction == CallToAction.Yes ? ButtonStyle.CallToAction : ButtonStyle.None; NoButton.Style = callToAction == CallToAction.No ? ButtonStyle.CallToAction : ButtonStyle.None; - AddWidget(new Label(message), 0, 0, 1, 2); + Panel.Add(new Label(message), 0, 0, 1, 2); - AddWidget(new WhiteSpace(), 1, 0); + Panel.Add(new WhiteSpace(), 1, 0); if (callToAction == CallToAction.Yes) { - AddWidget(NoButton, 2, 0); - AddWidget(YesButton, 2, 1, HorizontalAlignment.Right); + Panel.Add(NoButton, 2, 0); + Panel.Add(YesButton, 2, 1); } else { - AddWidget(YesButton, 2, 0); - AddWidget(NoButton, 2, 1, HorizontalAlignment.Right); + Panel.Add(YesButton, 2, 0); + Panel.Add(NoButton, 2, 1); } SetColumnWidth(0, 140); @@ -40,12 +40,20 @@ public YesNoDialog(IEngine engine, string message, CallToAction callToAction = C /// /// Gets the button with "Yes" that is displayed below the message. /// - public Button YesButton { get; } = new Button("Yes") { Width = 130 }; + public Button YesButton { get; } = new Button("Yes") + { + Width = 130, + HorizontalAlignment = HorizontalAlignment.Right, + }; /// /// Gets the button with "No" that is displayed below the message. /// - public Button NoButton { get; } = new Button("No") { Width = 130 }; + public Button NoButton { get; } = new Button("No") + { + Width = 130, + HorizontalAlignment = HorizontalAlignment.Right, + }; /// /// Shows the without passing through the @@ -69,7 +77,7 @@ public static bool Show(IEngine engine, string message, string title = "Are you dialog.YesButton.Pressed += (s, e) => yesButtonPressed = true; dialog.NoButton.Pressed += (s, e) => yesButtonPressed = false; - dialog.Show(true); + dialog.ShowInteractive(); return yesButtonPressed; } diff --git a/InteractiveAutomationToolkit/Exceptions/OverlappingWidgetsException.cs b/InteractiveAutomationToolkit/Exceptions/OverlappingWidgetsException.cs index 869c4e9..2d252bd 100644 --- a/InteractiveAutomationToolkit/Exceptions/OverlappingWidgetsException.cs +++ b/InteractiveAutomationToolkit/Exceptions/OverlappingWidgetsException.cs @@ -1,6 +1,7 @@ namespace Skyline.DataMiner.Utils.InteractiveAutomationScript { using System; + using System.Text; /// /// This exception is used to indicate that two widgets have overlapping positions on the same dialog. @@ -42,5 +43,64 @@ protected OverlappingWidgetsException( System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + + /// + /// Helps formatting an informative message about the widgets and how they overlap. + /// + internal class Builder + { + private readonly StringBuilder stringBuilder = new StringBuilder(); + + /// + /// Gets the number of widgets that overlap. + /// + public int Count { get; private set; } + + /// + /// Adds a set of overlapping widgets with their locations. + /// + /// The first widget. + /// The location of the first widget. + /// The second widget. + /// The location of the second widget. + /// The builder. + public Builder Add( + IWidget widget, + WidgetLocation location, + IWidget otherWidget, + WidgetLocation otherLocation) + { + Count++; + + if (stringBuilder.Length != 0) + { + stringBuilder.AppendLine(); + } + + stringBuilder.AppendFormat( + "{0} (Row {1}, Column {2}, RowSpan {3} ColumnSpan {4}) overlaps with {5} (Row {6}, Column {7}, RowSpan {8} ColumnSpan {9}).", + widget.GetType().Name, + location.Row, + location.Column, + location.RowSpan, + location.ColumnSpan, + otherWidget.GetType().Name, + otherLocation.Row, + otherLocation.Column, + otherLocation.RowSpan, + otherLocation.ColumnSpan); + + return this; + } + + /// + /// Gets an exception object with a message detailing how the widgets are overlapping. + /// + /// An exception object with a message detailing how the widgets are overlapping. + public OverlappingWidgetsException Build() + { + return new OverlappingWidgetsException(stringBuilder.ToString()); + } + } } } diff --git a/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj b/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj index 78f9c2b..6c7b687 100644 --- a/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj +++ b/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj @@ -20,15 +20,18 @@ README.md 10.5.4.1 + + + diff --git a/InteractiveAutomationToolkit/InteractiveController.cs b/InteractiveAutomationToolkit/InteractiveController.cs index 25b1162..c56dfa9 100644 --- a/InteractiveAutomationToolkit/InteractiveController.cs +++ b/InteractiveAutomationToolkit/InteractiveController.cs @@ -14,7 +14,7 @@ public class InteractiveController { private bool isManualModeRequested; private Action manualAction; - private Dialog nextDialog; + private IDialog nextDialog; private bool isRunning; /// @@ -36,7 +36,7 @@ public InteractiveController(IEngine engine) /// /// Gets the dialog that is shown to the user. /// - public Dialog CurrentDialog { get; private set; } + public IDialog CurrentDialog { get; private set; } /// /// Gets the link to the SLManagedAutomation process. @@ -85,7 +85,7 @@ public void Stop() /// /// The next dialog to be shown. /// When dialog is null. - public void ShowDialog(Dialog dialog) + public void ShowDialog(IDialog dialog) { if (dialog == null) { @@ -124,7 +124,7 @@ public void Update() CurrentDialog = nextDialog; SetScriptAbortPopupBehavior(CurrentDialog); - CurrentDialog.Show(false); + CurrentDialog.ShowStatic(false); } /// @@ -144,14 +144,9 @@ public void Hide() /// Use if you want to manually control when the dialog is updated. /// /// Dialog to be shown first. - private void Run(Dialog startDialog) + private void Run(IDialog startDialog) { - if (startDialog == null) - { - throw new ArgumentNullException("startDialog"); - } - - nextDialog = startDialog; + nextDialog = startDialog ?? throw new ArgumentNullException("startDialog"); if (isRunning) { @@ -192,13 +187,13 @@ private void DoRun() { SetScriptAbortPopupBehavior(CurrentDialog); - if (CurrentDialog.RequiresResponse) + if (CurrentDialog.Panel.GetWidgets(true).OfType().Any(x => x.IsVisible && x.RequiresResponse)) { - CurrentDialog.Show(); + CurrentDialog.ShowInteractive(); } else { - CurrentDialog.Show(false); + CurrentDialog.ShowStatic(false); System.Threading.Thread.Sleep(10000); // Wait for 10 seconds before checking for new dialogs } } @@ -213,7 +208,7 @@ private void RunManualAction() IsManualMode = false; } - private void SetScriptAbortPopupBehavior(Dialog dialog) + private void SetScriptAbortPopupBehavior(IDialog dialog) { switch (ScriptAbortPopupBehavior) { diff --git a/InteractiveAutomationToolkit/Layout/Direction.cs b/InteractiveAutomationToolkit/Layout/Direction.cs new file mode 100644 index 0000000..1c6d831 --- /dev/null +++ b/InteractiveAutomationToolkit/Layout/Direction.cs @@ -0,0 +1,11 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + /// + /// Indicates the orientation of a component that can exist in a horizontal or vertical state. + /// + public enum Direction + { + Horizontal, + Vertical, + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Layout/ILayout.cs b/InteractiveAutomationToolkit/Layout/ILayout.cs index 9f8c078..005fc23 100644 --- a/InteractiveAutomationToolkit/Layout/ILayout.cs +++ b/InteractiveAutomationToolkit/Layout/ILayout.cs @@ -1,8 +1,11 @@ namespace Skyline.DataMiner.Utils.InteractiveAutomationScript { + using System; + /// /// Used to define the position of an item in a grid layout. /// + [Obsolete("Replaced by WidgetLocation and PanelLocation.", false)] public interface ILayout { /// diff --git a/InteractiveAutomationToolkit/Layout/IWidgetLayout.cs b/InteractiveAutomationToolkit/Layout/IWidgetLayout.cs index ba35b8a..f0b3d4c 100644 --- a/InteractiveAutomationToolkit/Layout/IWidgetLayout.cs +++ b/InteractiveAutomationToolkit/Layout/IWidgetLayout.cs @@ -5,6 +5,7 @@ /// /// Used to define the position of a widget in a grid layout. /// + [Obsolete("Replaced by WidgetLocation.", false)] public interface IWidgetLayout : ILayout { /// diff --git a/InteractiveAutomationToolkit/Layout/PanelLocation.cs b/InteractiveAutomationToolkit/Layout/PanelLocation.cs new file mode 100644 index 0000000..e277be3 --- /dev/null +++ b/InteractiveAutomationToolkit/Layout/PanelLocation.cs @@ -0,0 +1,112 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + + /// + /// Used to define the location of a panel in another panel or dialog. + /// + public readonly struct PanelLocation : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Row index of the cell that the top-left cell of the panel will be mapped to. + /// Column index of the cell that the top-left cell of the panel will be mapped to. + public PanelLocation(int row, int column) + { + if (row < 0) + { + throw new ArgumentOutOfRangeException(nameof(row)); + } + + if (column < 0) + { + throw new ArgumentOutOfRangeException(nameof(column)); + } + + Row = row; + Column = column; + } + + /// + /// Gets the column location of the panel on the dialog grid. + /// + /// The top-left location is (0, 0) by default. + public int Column { get; } + + /// + /// Gets the row location of the panel on the dialog grid. + /// + /// The top-left location is (0, 0) by default. + public int Row { get; } + + /// + /// Determines whether two specified instances of are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// true if and represent the same margin; otherwise, + /// false. + /// + public static bool operator ==(PanelLocation left, PanelLocation right) + { + return left.Equals(right); + } + + /// + /// Determines whether two specified instances of are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// true if and do not represent the same margin; + /// otherwise, false. + /// + public static bool operator !=(PanelLocation left, PanelLocation right) + { + return !left.Equals(right); + } + + /// + /// Returns a value indicating whether the value of this instance is equal to the value of the specified + /// instance. + /// + /// The object to compare to this instance. + /// true if the value parameter equals the value of this instance; otherwise, false. + public bool Equals(PanelLocation other) + { + return Column == other.Column && Row == other.Row; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is PanelLocation location && Equals(location); + } + + /// + public override int GetHashCode() + { + unchecked + { + return Column * 397 ^ Row; + } + } + + /// + /// Combines both locations. + /// + /// Location that is used as offset. + /// A new location that is the combination of both locations. + internal PanelLocation AddOffset(PanelLocation offset) + { + return new PanelLocation(Row + offset.Row, Column + offset.Column); + } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Layout/SectionLayout.cs b/InteractiveAutomationToolkit/Layout/SectionLayout.cs index b7b28aa..cdc47b5 100644 --- a/InteractiveAutomationToolkit/Layout/SectionLayout.cs +++ b/InteractiveAutomationToolkit/Layout/SectionLayout.cs @@ -5,6 +5,7 @@ /// /// Used to define the position of a section in another section or dialog. /// + [Obsolete("Replaced by PanelLocation.", false)] public class SectionLayout : ILayout { private int column; diff --git a/InteractiveAutomationToolkit/Layout/WidgetLayout.cs b/InteractiveAutomationToolkit/Layout/WidgetLayout.cs index 4bf23fb..565bd9d 100644 --- a/InteractiveAutomationToolkit/Layout/WidgetLayout.cs +++ b/InteractiveAutomationToolkit/Layout/WidgetLayout.cs @@ -5,6 +5,7 @@ /// /// Used to define the position of a widget in a grid layout. /// + [Obsolete("Replaced by WidgetLocation.", false)] public class WidgetLayout : IWidgetLayout { private int column; diff --git a/InteractiveAutomationToolkit/Layout/WidgetLocation.cs b/InteractiveAutomationToolkit/Layout/WidgetLocation.cs new file mode 100644 index 0000000..4c2baaa --- /dev/null +++ b/InteractiveAutomationToolkit/Layout/WidgetLocation.cs @@ -0,0 +1,171 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + + /// + /// Used to define the location of a widget in a grid layout. + /// + public readonly struct WidgetLocation : IEquatable + { + /// + /// Initializes a new instance of the struct. + /// + /// Row index of top-left cell. + /// Column index of the top-left cell. + /// Number of vertical cells the widget spans across. + /// Number of horizontal cells the widget spans across. + /// is less than 0. + /// is less than 0. + /// is less than 1. + /// is less than 1. + public WidgetLocation(int fromRow, int fromColumn, int rowSpan, int columnSpan) + { + if (fromRow < 0) + { + throw new ArgumentOutOfRangeException(nameof(fromRow)); + } + + if (fromColumn < 0) + { + throw new ArgumentOutOfRangeException(nameof(fromColumn)); + } + + if (rowSpan <= 0) + { + throw new ArgumentOutOfRangeException(nameof(rowSpan)); + } + + if (columnSpan <= 0) + { + throw new ArgumentOutOfRangeException(nameof(columnSpan)); + } + + Row = fromRow; + Column = fromColumn; + RowSpan = rowSpan; + ColumnSpan = columnSpan; + } + + /// + /// Initializes a new instance of the struct. + /// + /// Row index of the cell where the widget is placed. + /// Column index of the cell where the widget is placed. + /// is less than 0. + /// is less than 0. + public WidgetLocation(int row, int column) + : this(row, column, 1, 1) + { + } + + /// + /// Gets the column location of the widget on the grid. + /// + public int Column { get; } + + /// + /// Gets how many columns the widget spans on the grid. + /// + public int ColumnSpan { get; } + + /// + /// Gets the row location of the widget on the grid. + /// + public int Row { get; } + + /// + /// Gets how many rows the widget spans on the grid. + /// + public int RowSpan { get; } + + /// + /// Determines whether two specified instances of are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// true if and represent the same margin; otherwise, + /// false. + /// + public static bool operator ==(WidgetLocation left, WidgetLocation right) + { + return left.Equals(right); + } + + /// + /// Determines whether two specified instances of are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// + /// true if and do not represent the same margin; + /// otherwise, false. + /// + public static bool operator !=(WidgetLocation left, WidgetLocation right) + { + return !left.Equals(right); + } + + /// + /// Determines if the current location overlaps with the specified other location. + /// + /// The other location to compare the current location to. + /// Whether the location overlaps with . + public bool Overlaps(WidgetLocation other) + { + // https://stackoverflow.com/a/20925869 + bool rowsOverlap = Row + RowSpan > other.Row && other.Row + other.RowSpan > Row; + bool columnsOverlap = Column + ColumnSpan > other.Column && other.Column + other.ColumnSpan > Column; + + return rowsOverlap && columnsOverlap; + } + + /// + /// Returns a value indicating whether the value of this instance is equal to the value of the specified + /// instance. + /// + /// The object to compare to this instance. + /// true if the value parameter equals the value of this instance; otherwise, false. + public bool Equals(WidgetLocation other) + { + return Column == other.Column && + ColumnSpan == other.ColumnSpan && + Row == other.Row && + RowSpan == other.RowSpan; + } + + /// + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is WidgetLocation location && Equals(location); + } + + /// + public override int GetHashCode() + { + unchecked + { + int hashCode = Column; + hashCode = hashCode * 397 ^ ColumnSpan; + hashCode = hashCode * 397 ^ Row; + hashCode = hashCode * 397 ^ RowSpan; + return hashCode; + } + } + + /// + /// Combines both locations. + /// + /// Location that is used as offset. + /// A new location that is the combination of both locations. + internal WidgetLocation AddOffset(PanelLocation offset) + { + return new WidgetLocation(Row + offset.Row, Column + offset.Column, RowSpan, ColumnSpan); + } + } +} \ No newline at end of file diff --git a/InteractiveAutomationToolkit/Layout/WidgetLocationPair.cs b/InteractiveAutomationToolkit/Layout/WidgetLocationPair.cs new file mode 100644 index 0000000..798c289 --- /dev/null +++ b/InteractiveAutomationToolkit/Layout/WidgetLocationPair.cs @@ -0,0 +1,63 @@ +namespace Skyline.DataMiner.Utils.InteractiveAutomationScript +{ + using System; + + /// + /// A WidgetLocationPair holds a and a . + /// + public readonly struct WidgetLocationPair : IEquatable + { + /// + /// Initializes a new instance of the structure with the specified widget + /// and location. + /// + /// The widget in the widget/location pair. + /// The location in the widget/location pair. + public WidgetLocationPair(IWidget widget, WidgetLocation location) + { + Widget = widget; + Location = location; + } + + /// + /// Gets the widget in the widget/location pair. + /// + public IWidget Widget { get; } + + /// + /// Gets the location in the widget/location pair. + /// + public WidgetLocation Location { get; } + + public static bool operator ==(WidgetLocationPair left, WidgetLocationPair right) + { + return left.Equals(right); + } + + public static bool operator !=(WidgetLocationPair left, WidgetLocationPair right) + { + return !left.Equals(right); + } + + /// + public bool Equals(WidgetLocationPair other) + { + return Equals(Widget, other.Widget) && Location.Equals(other.Location); + } + + /// + public override bool Equals(object obj) + { + return obj is WidgetLocationPair other && Equals(other); + } + + /// + public override int GetHashCode() + { + unchecked + { + return (Widget != null ? Widget.GetHashCode() : 0) * 397 ^ Location.GetHashCode(); + } + } + } +} \ No newline at end of file From 9b873e522c5d5ac68f50b360e5ae0a4d13c67b8f Mon Sep 17 00:00:00 2001 From: Arne Maes Date: Thu, 7 Aug 2025 09:07:37 +0200 Subject: [PATCH 2/2] Add extra part to auto copy the nuget build to a specific directory if the dev specifies one. --- .gitignore | 3 ++- .../InteractiveAutomationToolkit.csproj | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index e0c4b20..0e0832a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ packages/* **/.svn _git2_* **/StyleCop.Cache -*.DotSettings.user \ No newline at end of file +*.DotSettings.user +*.user \ No newline at end of file diff --git a/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj b/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj index 6c7b687..fde8c31 100644 --- a/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj +++ b/InteractiveAutomationToolkit/InteractiveAutomationToolkit.csproj @@ -35,4 +35,11 @@ + + + + + + + \ No newline at end of file