diff --git a/src/AasxCsharpLibrary/AdminShellCollections.cs b/src/AasxCsharpLibrary/AdminShellCollections.cs index 282de6b44..4e83c016a 100644 --- a/src/AasxCsharpLibrary/AdminShellCollections.cs +++ b/src/AasxCsharpLibrary/AdminShellCollections.cs @@ -30,6 +30,7 @@ public V GetValueOrDefault(K key) public class MultiValueDictionary { private Dictionary> dict = new Dictionary>(); + public void Add(K key, V value) { if (dict.TryGetValue(key, out var list)) @@ -38,6 +39,12 @@ public void Add(K key, V value) dict.Add(key, new List { value }); } + public void Remove(K key) + { + if (dict.ContainsKey(key)) + dict.Remove(key); + } + public bool ContainsKey(K key) => dict.ContainsKey(key); public List this[K key] => dict[key]; diff --git a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs index 023ad9012..3f7ad13ad 100644 --- a/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs +++ b/src/AasxCsharpLibrary/Extensions/ExtendEnvironment.cs @@ -962,7 +962,7 @@ public static IAssetAdministrationShell FindAasWithAssetInformation(this AasCore foreach (var aas in environment.AssetAdministrationShells) { - if (aas.AssetInformation.GlobalAssetId.Equals(globalAssetId)) + if (aas.AssetInformation?.GlobalAssetId?.Equals(globalAssetId) == true) { return aas; } diff --git a/src/AasxCsharpLibrary/Extensions/ExtendIHasSemantics.cs b/src/AasxCsharpLibrary/Extensions/ExtendIHasSemantics.cs new file mode 100644 index 000000000..265d98ddd --- /dev/null +++ b/src/AasxCsharpLibrary/Extensions/ExtendIHasSemantics.cs @@ -0,0 +1,31 @@ +/* +Copyright (c) 2018-2023 Festo SE & Co. KG +Author: Michael Hoffmeister + +This source code is licensed under the Apache License 2.0 (see LICENSE.txt). + +This source code may use other Open Source software components (see LICENSE.txt). +*/ +using System.Runtime.Intrinsics.X86; + +namespace Extensions +{ + public static class ExtendIHasSemantics + { + public static string GetConceptDescriptionId(this IHasSemantics ihs) + { + if (ihs?.SemanticId != null + && ihs.SemanticId.IsValid() == true + && ihs.SemanticId.Count() == 1 + && (ihs.SemanticId.Keys[0].Type == KeyTypes.ConceptDescription + || ihs.SemanticId.Keys[0].Type == KeyTypes.Submodel + || ihs.SemanticId.Keys[0].Type == KeyTypes.GlobalReference) + && ihs.SemanticId.Keys[0].Value != null + && ihs.SemanticId.Keys[0].Value.Trim().Length > 0) + { + return ihs.SemanticId.Keys[0].Value; + } + return null; + } + } +} diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 0a35d7dc5..d88457e31 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -857,6 +857,7 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) // display elements has a cache DisplayElements.ActivateElementStateCache(); + VisualElementEnvironmentItem.SetCdSortOrderByString(Options.Curr.CdSortOrder); // show Logo? if (Options.Curr.LogoFile != null) @@ -2531,20 +2532,20 @@ private async Task MainTimer_Tick(object sender, EventArgs e) PackageCentral.MainItem.Container.SignificantElements); _mainTimer_LastCheckForAnimationElements = DateTime.Now; } + } - // do re-index? - deltaSecs = (DateTime.Now - _mainTimer_LastCheckForReIndexElements).TotalSeconds; - if (deltaSecs >= 1.0 && _mainTimer_PendingReIndexElements) - { - // dis-engage - _mainTimer_PendingReIndexElements = false; + // do re-index? + var deltaSecs2 = (DateTime.Now - _mainTimer_LastCheckForReIndexElements).TotalSeconds; + if (deltaSecs2 >= 1.0 && _mainTimer_PendingReIndexElements) + { + // dis-engage + _mainTimer_PendingReIndexElements = false; - // be modest for the time being - PackageCentral.MainItem?.Container?.ReIndexIdentifiables(); + // be modest for the time being + PackageCentral.MainItem?.Container?.ReIndexIdentifiables(); - // Info - Log.Singleton.Info("Re-indexing Identifiables for faster access."); - } + // Info + Log.Singleton.Info("Re-indexing Identifiables for faster access."); } MainTimer_PeriodicalTaskForSelectedEntity(); diff --git a/src/AasxPackageExplorer/extension-presets.json b/src/AasxPackageExplorer/extension-presets.json index c3eab8379..2cc93d344 100644 --- a/src/AasxPackageExplorer/extension-presets.json +++ b/src/AasxPackageExplorer/extension-presets.json @@ -1,4 +1,21 @@ [ + { + "name": "Main app | Submodel template attribute set", + "extension": { + "semanticId": { + "type": "ExternalReference", + "keys": [ + { + "type": "GlobalReference", + "value": "https://admin-shell.io/SubmodelTemplates/smt-attribute-set/v1/0" + } + ] + }, + "name": "smt-attrtibute-set", + "value": "{ type: \"Sin\", ofs: 230.0, scale: 10.0, freq: 0.05, timer: 500 }", + "valueId": null + } + }, { "name": "Main app | Animation of values", "extension": { diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index 5ab72be9f..0f32082fb 100644 --- a/src/AasxPackageExplorer/options-debug.MIHO.json +++ b/src/AasxPackageExplorer/options-debug.MIHO.json @@ -26,7 +26,7 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_B.aasx", // "AuxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_A.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\00_FestoDemoBox-Module-2-Kopie2.aasx", - "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\samm_spiel_empty-smt.aasx", + "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\SMT_and_SAMM_Showcase_v01.aasx", "WindowLeft": 200, "WindowTop": -1, "WindowWidth": 900, @@ -61,6 +61,7 @@ "ShowIdAsIri": false, "VerboseConnect": true, "WorkDir": ".\\work", + "CdSortOrder" : "Structured", "ObserveEvents": true, "CompressEvents": true, "CheckSmtElements": false, diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index fd9892cbc..ed0de5fed 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -301,7 +301,7 @@ public void AddKeyValueExRef( AnyUiLambdaActionBase takeOverLambdaAction = null, bool limitToOneRowForNoEdit = false, int comboBoxMinWidth = -1, - bool noFirstColumnWidth = false, + int firstColumnWidth = -1, // -1 = Standard int maxLines = -1, bool keyVertCenter = false) { @@ -312,7 +312,7 @@ public void AddKeyValueExRef( (value == null) ? 0 : value.GetHashCode(), containingObject: containingObject, limitToOneRowForNoEdit: limitToOneRowForNoEdit, comboBoxMinWidth: comboBoxMinWidth, - noFirstColumnWidth: noFirstColumnWidth, + firstColumnWidth: firstColumnWidth, maxLines: maxLines, keyVertCenter: keyVertCenter); } @@ -351,7 +351,7 @@ public void AddKeyValue( object containingObject = null, bool limitToOneRowForNoEdit = false, int comboBoxMinWidth = -1, - bool noFirstColumnWidth = false, + int firstColumnWidth = -1, // -1 = Standard int maxLines = -1, bool keyVertCenter = false) { @@ -389,7 +389,9 @@ public void AddKeyValue( g.Margin = new AnyUiThickness(0, 1, 0, 1); var gc1 = new AnyUiColumnDefinition(); gc1.Width = AnyUiGridLength.Auto; - if (!noFirstColumnWidth) + if (firstColumnWidth >= 0) + gc1.MinWidth = firstColumnWidth; + if (firstColumnWidth == -1) gc1.MinWidth = this.GetWidth(FirstColumnWidth.Standard); g.ColumnDefinitions.Add(gc1); var gc2 = new AnyUiColumnDefinition(); @@ -615,8 +617,12 @@ public void AddKeyMultiValue(AnyUiStackPanel view, string key, string[][] value, view.Children.Add(g); } - public void AddCheckBox(AnyUiStackPanel panel, string key, bool initialValue, string additionalInfo = "", - Action valueChanged = null) + public void AddSmallCheckBox( + AnyUiStackPanel panel, string key, + bool value, + Func setValue = null, + string additionalInfo = "", + string[] boolTexts = null) { // make grid var g = this.AddSmallGrid(1, 2, new[] { "" + this.GetWidth(FirstColumnWidth.Standard) + ":", "*" }, @@ -626,20 +632,25 @@ public void AddCheckBox(AnyUiStackPanel panel, string key, bool initialValue, st this.AddSmallLabelTo(g, 0, 0, padding: new AnyUiThickness(5, 0, 0, 0), content: key); // Column 1 = Check box or info - if (repo == null || valueChanged == null) + if (repo == null || setValue == null) { - this.AddSmallLabelTo(g, 0, 1, padding: new AnyUiThickness(2, 0, 0, 0), - content: initialValue ? "True" : "False"); + // label + var strVal = (value) ? "True" : "False"; + if (boolTexts != null && boolTexts.Length >= 2) + strVal = (value) ? boolTexts[1] : boolTexts[0]; + + this.AddSmallLabelTo(g, 0, 1, padding: new AnyUiThickness(2, 0, 0, 0), + content: strVal); } else { AnyUiUIElement.RegisterControl(this.AddSmallCheckBoxTo(g, 0, 1, margin: new AnyUiThickness(2, 2, 2, 2), content: additionalInfo, verticalContentAlignment: AnyUiVerticalAlignment.Center, - isChecked: initialValue), + isChecked: value), (o) => { - if (o is bool) - valueChanged((bool)o); + if (o is bool && setValue != null) + return setValue((bool)o); return new AnyUiLambdaActionNone(); }); } @@ -648,7 +659,7 @@ public void AddCheckBox(AnyUiStackPanel panel, string key, bool initialValue, st panel.Children.Add(g); } - public void AddActionPanel( + public void AddActionPanel( AnyUiPanel view, string key, string[] actionStr = null, ModifyRepo repo = null, Func action = null, string[] actionTags = null, diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 21db04f8d..137078a5e 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1707,7 +1707,8 @@ public void DisplayOrEditAasEntitySubmodelOrRef( key: "SubmodelElement:", submodel.SubmodelElements, setValueLambda: (sml) => submodel.SubmodelElements = sml, - superMenu: superMenu); + superMenu: superMenu, + basedOnSemanticId: submodel.SemanticId); this.AddHintBubble(stack, hintMode, new[] { new HintCheck( @@ -1844,6 +1845,8 @@ public void DisplayOrEditAasEntitySubmodelOrRef( "Upgrades particular qualifiers from V2.0 to V3.0 for selected element.") .AddAction("SMT-qualifiers-convert", "Convert SMT qualifiers", "Converts particular SMT qualifiers to SMT extension for selected element.") + .AddAction("SMT-set-organize", "Set SMT organize", + "Take over Submodel's element relationships to associated concepts.") .AddAction("remove-qualifiers", "Remove qualifiers", "Removes all qualifiers for selected element.") .AddAction("remove-extensions", "Remove extensions", @@ -1935,6 +1938,63 @@ public void DisplayOrEditAasEntitySubmodelOrRef( } if (buttonNdx == 2) + { + // ask 1 + if (ticket?.ScriptMode != true + && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( + "This operation analyzes the element relatioships in the Submodel " + + "and will take over these as organize references into SMT attribute " + + "records of associated ConceptDescriptions. Do you want to proceed?", + "Take over SM element relationships to CDs", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + return new AnyUiLambdaActionNone(); + + // ask 2 + var eachElemDetails = true; + if (ticket?.ScriptMode != true) + eachElemDetails = AnyUiMessageBoxResult.Yes == this.context.MessageBoxFlyoutShow( + "Create detailed SMT attributes for each relevant ConceptDescription, " + + "include SubmodelElement type list?", + "Take over SM element relationships to CDs", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning); + +#if __not_useful + // ask 2 + var resetOrganize = true; + if (ticket?.ScriptMode != true) + resetOrganize = AnyUiMessageBoxResult.Yes == this.context.MessageBoxFlyoutShow( + "Reset existing organize references in CDs?", + "Take over SM element relationships to CDs", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning); +#endif + + // do + int anyChanges = 0; + Action lambdaConvert = (o) => { + if (SmtAttributeRecord.TakeoverSmOrganizeToCds(env, o, + eachElemDetails: eachElemDetails)) + anyChanges++; + }; + + lambdaConvert(submodel); + submodel.RecurseOnSubmodelElements(null, (o, parents, sme) => + { + // do + lambdaConvert(sme); + // recurse + return true; + }); + + // report + Log.Singleton.Info($"Take over SM element relationships to CDs: {anyChanges} changes done."); + + // emit event for Submodel and children + this.AddDiaryEntry(submodel, new DiaryEntryStructChange(), allChildrenAffected: true); + + return new AnyUiLambdaActionRedrawAllElements(nextFocus: smref, isExpanded: true); + } + + if (buttonNdx == 3) { if (ticket?.ScriptMode != true && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( @@ -1962,7 +2022,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( return new AnyUiLambdaActionRedrawAllElements(nextFocus: smref, isExpanded: true); } - if (buttonNdx == 3) + if (buttonNdx == 4) { if (ticket?.ScriptMode != true && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( @@ -2388,7 +2448,13 @@ public void DisplayOrEditAasEntityConceptDescription( lambdaRf(true); lambdaIdf(); lambdaIsCaseOf(); - lambdaEDS(false); + + DisplayOrEditEntityListOfExtension( + stack: stack, extensions: cd.Extensions, + setOutput: (v) => { cd.Extensions = v; }, + relatedReferable: cd); + + lambdaEDS(false); lambdaSammExt(); lambdaExtRecs(); } @@ -3007,7 +3073,8 @@ public void DisplayOrEditAasEntitySubmodelElement( if (sme is Aas.Entity) (sme as Aas.Entity).Statements = sml; }, - superMenu: superMenu); + superMenu: superMenu, + basedOnSemanticId: sme.SemanticId); // Copy @@ -3240,7 +3307,8 @@ public void DisplayOrEditAasEntitySubmodelElement( key: "annotation:", are.Annotations, setValueLambda: (sml) => are.Annotations = sml, - superMenu: superMenu); + superMenu: superMenu, + basedOnSemanticId: are.SemanticId); this.AddHintBubble( substack, hintMode, @@ -4061,9 +4129,10 @@ public void DisplayOrEditAasEntitySubmodelElement( else this.AddKeyValue(stack, "Values", "Please add elements via editing of sub-ordinate entities"); - this.AddCheckBox( - stack, "orderRelevant:", sml.OrderRelevant ?? false, " (true if order in list is relevant)", - (b) => { sml.OrderRelevant = b; }); + this.AddSmallCheckBox( + stack, "orderRelevant:", sml.OrderRelevant ?? false, + additionalInfo: " (true if order in list is relevant)", + setValue: (b) => { sml.OrderRelevant = b; return new AnyUiLambdaActionNone(); }); // stats var stats = sml.EvalConstraintStat(); diff --git a/src/AasxPackageLogic/DispEditHelperExtensions.cs b/src/AasxPackageLogic/DispEditHelperExtensions.cs index b97ca8185..adc7306f0 100644 --- a/src/AasxPackageLogic/DispEditHelperExtensions.cs +++ b/src/AasxPackageLogic/DispEditHelperExtensions.cs @@ -9,8 +9,8 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasCore.Samm2_2_0; using AasxIntegrationBase; +using AasxPackageLogic.PackageCentral; using AdminShellNS; -using Aml.Engine.CAEX; using AnyUi; using Extensions; using Microsoft.VisualBasic.ApplicationServices; @@ -19,13 +19,14 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Collections; using System.Collections.Generic; using System.Data; -using System.Data.SqlTypes; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Serialization; +using System.Text; using System.Text.RegularExpressions; +using System.Windows; using System.Xaml; using static AasxPackageLogic.AasSmtQualifiers; using Aas = AasCore.Aas3_0; @@ -74,15 +75,28 @@ public static void GeneralExtensionHelperUpdateJson(Aas.IExtension se, Type recT } public static bool GeneralExtensionHelperAddJsonExtension( - Aas.IHasExtensions ihe, Type recType, ExtensionRecordBase recInst) + Aas.IHasExtensions ihe, Type recType, ExtensionRecordBase recInst, + bool replaceExistingRecordType = false) { // acces if (ihe == null || recType == null || recInst == null || !(recInst is IExtensionSelfDescription ssd)) return false; - // create extension - var newExt = new Aas.Extension( + // create or use extension + Aas.IExtension foundExt = null; + if (ihe.Extensions != null && recInst is IExtensionSelfDescription esd) + foreach (var ex in ihe.Extensions) + if (ex?.SemanticId?.Keys != null && ex.SemanticId.Count() == 1 + && ex.SemanticId.Keys[0].Value == esd.GetSelfUri()) + { + foundExt = ex; + break; + } + + var newExt = (replaceExistingRecordType && foundExt != null) + ? foundExt + : new Aas.Extension( name: ssd.GetSelfName(), semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, (new[] { @@ -95,9 +109,12 @@ public static bool GeneralExtensionHelperAddJsonExtension( // add JSON GeneralExtensionHelperUpdateJson(newExt, recType, recInst); - // add to extension - ihe.Extensions = ihe.Extensions ?? new List(); - ihe.Extensions.Add(newExt); + // add to extension? + if (foundExt == null) + { + ihe.Extensions = ihe.Extensions ?? new List(); + ihe.Extensions.Add(newExt); + } // ok return true; @@ -182,6 +199,8 @@ public static IEnumerable CheckReferableForExtensionRecords foreach (var se in rf.Extensions) { var rec = CheckReferableForSingleExtensionRecord(se); + if (rec == null) + continue; if (typesAllowed != null) { @@ -239,9 +258,269 @@ protected void ExtensionHelperAddScalarField maxLines: 1); } + protected void ExtensionHelperAddEnumField( + AnyUiGrid grid, + int row, int col, + string caption, + Type typeForEnum, + object enumValue, + Func setEnumValue) + { + // access + if (grid == null || typeForEnum == null) + return; + + // generate a list for combo box + var eMems = EnumHelper.EnumHelperGetMemberInfo(typeForEnum).ToList(); + + // find selected index + int? selectedIndex = null; + if (enumValue != null) + for (int emi = 0; emi < eMems.Count; emi++) + { + if (((int)eMems[emi].MemberInstance) == ((int)enumValue)) + selectedIndex = emi; + } + + // and combobox inside + AnyUiComboBox cb = null; + cb = AnyUiUIElement.RegisterControl( + AddSmallComboBoxTo( + grid, row + 0, col + 0, + minWidth: 120, + margin: NormalOrCapa( + new AnyUiThickness(4, 1, 2, 3), + AnyUiContextCapability.Blazor, new AnyUiThickness(4, 2, 2, 0)), + padding: NormalOrCapa( + new AnyUiThickness(2, 1, 2, 1), + AnyUiContextCapability.Blazor, new AnyUiThickness(0, 4, 0, 4)), + selectedIndex: selectedIndex, + items: eMems.Select((mi) => mi.MemberDisplay).ToArray()), + setValue: (o) => + { + if (cb.SelectedIndex.HasValue + && cb.SelectedIndex.Value >= 0 + && cb.SelectedIndex.Value < eMems.Count) + { + return setEnumValue?.Invoke(eMems[cb.SelectedIndex.Value].MemberInstance); + } + return new AnyUiLambdaActionNone(); + }); + } + + public AnyUiLambdaActionBase ExtensionHelperIdfReferenceAction( + Aas.Environment env, + Aas.IReferable relatedReferable, + int actionIndex, + ExtIdfReference sr, + Action setValue, + Func createInstance) + { + if (actionIndex == 0) + { + var k2 = SmartSelectAasEntityKeys( + packages, + PackageCentral.PackageCentral.Selector.MainAuxFileRepo, + "ConceptDescription"); + if (k2 != null && k2.Count >= 1) + { + setValue?.Invoke(createInstance?.Invoke("" + k2[0].Value)); + return new AnyUiLambdaActionRedrawEntity(); + } + } + + if (actionIndex == 2 && sr?.Value?.HasContent() == true) + { + return new AnyUiLambdaActionNavigateTo( + new Aas.Reference( + Aas.ReferenceTypes.ModelReference, + new Aas.IKey[] { + new Aas.Key(KeyTypes.ConceptDescription, sr.Value) + }.ToList())); + } + + return new AnyUiLambdaActionNone(); + } + + public void ExtensionHelperAddIdfReference( + Aas.Environment env, + AnyUiStackPanel stack, + string caption, + ExtensionRecordBase recInst, + Aas.IReferable relatedReferable, + ExtIdfReference sr, + Action setValue, + Func createInstance, + int firstColumnWidth = -1, // -1 = Standard + string[] presetList = null, + bool showButtons = true) + { + var grid = AddSmallGrid(1, 2, colWidths: new[] { "*", "#" }); + stack.Add(grid); + var g1stack = AddSmallStackPanelTo(grid, 0, 0, margin: new AnyUiThickness(0)); + + AddKeyValueExRef( + g1stack, "" + caption, recInst, + value: "" + sr?.Value, null, repo, + setValue: v => + { + setValue?.Invoke(createInstance?.Invoke((string)v)); + return new AnyUiLambdaActionNone(); + }, + keyVertCenter: true, + firstColumnWidth: firstColumnWidth, + auxButtonTitles: !showButtons ? null : new[] { "Existing", "New", "Jump" }, + auxButtonToolTips: !showButtons ? null : new[] { + "Select existing ConceptDescription.", + "Create a new ConceptDescription for this known extension use.", + "Jump to ConceptDescription with given Id." + }, + auxButtonLambda: (i) => + { + return ExtensionHelperIdfReferenceAction( + env, + relatedReferable, + i, + sr: sr, + setValue: setValue, + createInstance: createInstance); + }); + } + + public void ExtensionHelperAddListOfIdfReference( + Aas.Environment env, + AnyUiStackPanel stack, + QuickLookupIdentifiable lookupIdf, + string caption, + ExtensionRecordBase recInst, + Aas.IReferable relatedReferable, + List value, + Action> setValue, + Func createInstance) + { + this.AddVerticalSpace(stack); + + if (this.SafeguardAccess(stack, repo, value, "" + caption + ":", + "Create data element!", + v => { + setValue?.Invoke(new List(new ExtIdfReference[] { createInstance?.Invoke("") })); + return new AnyUiLambdaActionRedrawEntity(); + })) + { + // Head + var sg = this.AddSubGrid(stack, "" + caption + ":", + rows: 1 + 2 * value.Count, cols: 2, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + colWidths: new[] { "*", "#" }); + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 0, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(1, 0, 1, 0), + content: "\u2795"), + (v) => + { + value.Add(createInstance?.Invoke("")); + setValue?.Invoke(value); + return new AnyUiLambdaActionRedrawEntity(); + }); + + // individual references + for (int lsri = 0; lsri < value.Count; lsri++) + { + // remember lambda safe + var theLsri = lsri; + + // Stack in the 1st column of 1st row + var sp1 = AddSmallStackPanelTo(sg, 1 + 2 * lsri, 0); + ExtensionHelperAddIdfReference( + env, + sp1, + $"[{1 + lsri}]", + recInst, relatedReferable, + value[lsri], + firstColumnWidth: 40, + showButtons: false, + setValue: (v) => { + value[theLsri] = v; + setValue?.Invoke(value); + }, + createInstance: createInstance); + + // Info on 1st column of 2nd row + var idf = lookupIdf?.Invoke(value[lsri]?.Value); + if (idf != null) + AddSmallBasicLabelTo(sg, 1 + 2 * lsri + 1, 0, + padding: new AnyUiThickness(44, 0, 0, 6), + content: "" + idf.IdShort, + fontSize: 0.8, + foreground: AnyUiBrushes.DarkGray); + + // button [hamburger] + AddSmallContextMenuItemTo( + sg, 1 + 2 * lsri, 1, + "\u22ee", + repo, new[] { + "\u2702", "Delete", + "\u25b2", "Move Up", + "\u25bc", "Move Down", + "\U0001F517", "Select from existing CDs", + "\U0001f516", "Create new CD for SAMM", + "\U0001f872", "Jump to" + }, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + menuItemLambda: (o) => + { + var action = false; + + if (o is int ti) + switch (ti) + { + case 0: + value.RemoveAt(theLsri); + action = true; + break; + case 1: + MoveElementInListUpwards(value, value[theLsri]); + action = true; + break; + case 2: + MoveElementInListDownwards(value, value[theLsri]); + action = true; + break; + case 3: + case 4: + case 5: + return ExtensionHelperIdfReferenceAction( + env, relatedReferable, + sr: value[theLsri], + actionIndex: ti - 3, + setValue: (srv) => + { + value[theLsri] = srv; + setValue?.Invoke(value); + }, + createInstance: createInstance); + } + + if (action) + { + setValue?.Invoke(value); + return new AnyUiLambdaActionRedrawEntity(); + } + return new AnyUiLambdaActionNone(); + }); + } + } + + } + public void ExtensionHelperAddEditFieldsByReflection( Aas.Environment env, AnyUiStackPanel stack, + QuickLookupIdentifiable lookupIdf, ExtensionRecordBase recInst, Aas.IReferable relatedReferable, Action setValue) @@ -392,6 +671,55 @@ public void ExtensionHelperAddEditFieldsByReflection( } } + // List of IdfReference? + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + // value + var lidr = (List)pii.GetValue(recInst); + + // hint? + hintLambda(lidr == null || lidr.Count < 1); + + // edit + ExtensionHelperAddListOfIdfReference( + env, stack, + lookupIdf, + caption: "" + pii.Name, + recInst, + relatedReferable, + value: lidr, + setValue: (v) => + { + pii.SetValue(recInst, v); + setValue?.Invoke(recInst); + }, + createInstance: (eir) => new ExtIdfReference(eir)); + } + + // single IdfReference? + if (pii.PropertyType.IsAssignableTo(typeof(ExtIdfReference))) + { + this.AddVerticalSpace(stack); + + // value + var idr = (ExtIdfReference)pii.GetValue(recInst); + + // hint? + hintLambda(idr == null); + + // edit + ExtensionHelperAddIdfReference( + env, + stack, + "" + pii.Name, recInst, relatedReferable, + idr, + setValue: (v) => { + pii.SetValue(recInst, v); + setValue?.Invoke(recInst); + }, + createInstance: (eir) => new ExtIdfReference(eir)); + } + // single string? if (pii.PropertyType.IsAssignableTo(typeof(string))) { @@ -448,10 +776,26 @@ public void ExtensionHelperAddEditFieldsByReflection( } } + // single boolean + if (pii.PropertyType.IsAssignableTo(typeof(bool))) + { + // value and hint? + var boolVal = (bool)pii.GetValue(recInst); + + this.AddSmallCheckBox( + stack, "" + pii.Name, + value: boolVal, + additionalInfo: "", + setValue: (b) => { + pii.SetValue(recInst, b); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionNone(); + }); + } + // scalar value type // Note: for Double, "G17" shall be used according to Microsoft; this is changed // to "G16" in order to round properly before least-significant-bit-precision errors. - if (underlyingType != null) { if (pii.PropertyType.IsAssignableTo(typeof(uint?))) @@ -513,7 +857,7 @@ public void ExtensionHelperAddEditFieldsByReflection( if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var res)) return res; else return null; }); - // nullable enum? + // (nullable) enum? var typeForEnum = propType; if (underlyingType != null && underlyingType.IsEnum) typeForEnum = underlyingType; @@ -523,53 +867,116 @@ public void ExtensionHelperAddEditFieldsByReflection( AddVerticalSpace(stack); // current enum member - var currEM = pii.GetValue(recInst); - - // generate a list for combo box - var eMems = EnumHelper.EnumHelperGetMemberInfo(typeForEnum).ToList(); - - // find selected index - int? selectedIndex = null; - if (currEM != null) - for (int emi = 0; emi < eMems.Count; emi++) - { - if (((int)eMems[emi].MemberInstance) == ((int)currEM)) - selectedIndex = emi; - } + var currEnumValue = pii.GetValue(recInst); - // add a container + // make a grid for this (have one edit function) var sg = this.AddSubGrid(stack, "" + pii.Name + ":", - rows: 1, cols: 2, - minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), - paddingCaption: new AnyUiThickness(5, 0, 0, 0), - marginGrid: new AnyUiThickness(4, 0, 0, 0), - colWidths: new[] { "*", "#" }); + rows: 1, cols: 2, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + colWidths: new[] { "*", "#" }); - // and combobox inside - AnyUiComboBox cb = null; - cb = AnyUiUIElement.RegisterControl( - AddSmallComboBoxTo( - sg, 0, 0, - minWidth: 120, - margin: NormalOrCapa( - new AnyUiThickness(0, 1, 2, 3), - AnyUiContextCapability.Blazor, new AnyUiThickness(4, 2, 2, 0)), - padding: NormalOrCapa( - new AnyUiThickness(2, 1, 2, 1), - AnyUiContextCapability.Blazor, new AnyUiThickness(0, 4, 0, 4)), - selectedIndex: selectedIndex, - items: eMems.Select((mi) => mi.MemberDisplay).ToArray()), - setValue: (o) => + // edit + ExtensionHelperAddEnumField( + sg, 0, 0, + "" + pii.Name, + typeForEnum: typeForEnum, + enumValue: currEnumValue, + setEnumValue: (o) => { - if (cb.SelectedIndex.HasValue - && cb.SelectedIndex.Value >= 0 - && cb.SelectedIndex.Value < eMems.Count) - { - pii.SetValue(recInst, eMems[cb.SelectedIndex.Value].MemberInstance); - setValue?.Invoke(recInst); - } + pii.SetValue(recInst, o); + setValue?.Invoke(recInst); return new AnyUiLambdaActionNone(); }); + + } + + // List of enum? + + // see:https://stackoverflow.com/questions/12617280/how-to-check-if-an-object-is-a-list-enum-type-objects + //var testListEnum = pii.PropertyType + // .GetInterface("System.Collections.Generic.IList")? + // .GetGenericArguments()?[0].IsEnum; + var testListEnum = pii.PropertyType.IsGenericType + && pii.PropertyType.GetGenericTypeDefinition() != null + && pii.PropertyType.GetGenericTypeDefinition() == typeof(System.Collections.Generic.List<>) + && pii.PropertyType.GenericTypeArguments != null + && pii.PropertyType.GenericTypeArguments.Length == 1 + && pii.PropertyType.GenericTypeArguments[0].IsEnum; + if (testListEnum == true) + { + this.AddVerticalSpace(stack); + + var singleEnumType = pii.PropertyType.GenericTypeArguments[0]; + + // var enumList = (List)pii.GetValue(recInst); + var enumList = pii.GetValue(recInst) as IList; + + // hint? + hintLambda(enumList == null || enumList.Count < 1); + + if (this.SafeguardAccess(stack, repo, enumList, "" + pii.Name + ":", + "Create data element!", + v => + { + var x = Activator.CreateInstance(pii.PropertyType); + pii.SetValue(recInst, x); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + })) + { + // grid + var sg = this.AddSubGrid(stack, "" + pii.Name + ":", + rows: 1 + enumList.Count, cols: 2, + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard), + paddingCaption: new AnyUiThickness(5, 0, 0, 0), + colWidths: new[] { "*", "#" }); + + // add button + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 0, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "Add blank"), + (v) => + { + enumList.Add((int) 0); + pii.SetValue(recInst, enumList); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + + for (int enumListItem = 0; enumListItem < enumList.Count; enumListItem++) + { + var theLsi = enumListItem; + + ExtensionHelperAddEnumField( + sg, 1 + enumListItem, 0, + "" + pii.Name, + typeForEnum: singleEnumType, + enumValue: enumList[enumListItem], + setEnumValue: (o) => + { + enumList[theLsi] = o; + pii.SetValue(recInst, enumList); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 1 + enumListItem, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "-"), + (v) => + { + enumList.RemoveAt(theLsi); + pii.SetValue(recInst, enumList); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + } } } } @@ -702,6 +1109,7 @@ public void DisplayOrEditEntityExtensionRecords( // edit ExtensionHelperAddEditFieldsByReflection( env, stack, + packages.QuickLookupFirstIdent, recInst: recInst, relatedReferable: relatedReferable, setValue: (si) => @@ -1065,6 +1473,56 @@ public EnumMemberDisplayAttribute(string text) } } + /// + /// Abstract class for all references, which are based on a string value. + /// + public class IdStringReferenceBase + { + public string Value { get; set; } + + public IdStringReferenceBase(string val = "") + { + Value = val; + } + } + + /// + /// This class creates a reference to an Identifiable, which "feels" like a string. + /// + public class ExtIdfReference : IdStringReferenceBase + { + public ExtIdfReference(string val = "") + { + Value = val; + } + } + + /// + /// Shall be implemented in order to give hints about the + /// (hierarchical) structuring of elements + /// + public interface IAbstractStructureModel where T : IdStringReferenceBase + { + /// + /// True, if a top element of a hierarchy + /// + bool IsTopElement(); + + /// + /// Iterate over all the SAMM elements referenced from this instance + /// without further recursion (see AasCore). + /// + IEnumerable DescendOnce(); + } + + /// + /// Shall be implemented in order to give hints about the + /// (hierarchical) structuring of elements + /// + public interface IExtensionStructureModel : IAbstractStructureModel + { + } + /// /// Abstract base class for information for data records of extensions. /// @@ -1086,7 +1544,7 @@ public class SmtAttributeCheckItem /// Holds the possible attributes for an SMT specification per element /// as a whole. /// - public class SmtAttributeRecord : ExtensionRecordBase, IExtensionSelfDescription + public class SmtAttributeRecord : ExtensionRecordBase, IExtensionSelfDescription, IExtensionStructureModel { // self description public string GetSelfName() => "smt-attrtibute-set"; @@ -1100,6 +1558,17 @@ public class SmtAttributeRecord : ExtensionRecordBase, IExtensionSelfDescription Background = 0xff00cd90 }; + // hierarchical structure + + bool IAbstractStructureModel.IsTopElement() => false; + + IEnumerable IAbstractStructureModel.DescendOnce() + { + if (Organizes != null) + foreach (var org in Organizes) + yield return org; + } + // attributes [ExtensionHintAttribute("Specifies, how many SubmodelElement instances of this " + @@ -1107,7 +1576,7 @@ public class SmtAttributeRecord : ExtensionRecordBase, IExtensionSelfDescription public AasSmtQualifiers.SmtCardinality Cardinality { get; set; } = AasSmtQualifiers.SmtCardinality.One; [ExtensionHintAttribute("Specifies an id of an equivalence class. " + - "Only ids in the range[A-Za-z0-9] are allowed. If multiple SMT elements feature the same equivalency " + + "Only ids in the range [A-Za-z0-9] are allowed. If multiple SMT elements feature the same equivalency " + "class, only one of these are allowed in the actual collection (hierarchy level of the Submodel). ")] public string EitherOr { get; set; } = ""; @@ -1151,6 +1620,118 @@ public class SmtAttributeRecord : ExtensionRecordBase, IExtensionSelfDescription "shall not change the value.")] public AasSmtQualifiers.AccessMode AccessMode { get; set; } = AasSmtQualifiers.AccessMode.ReadWrite; + [ExtensionHintAttribute("Specifies that this concept relates to Submodel.")] + public bool IsSubmodel { get; set; } = false; + + [ExtensionHintAttribute("\u00ab Experimental \u00bb Specifies a list of types of SubmodelElements " + + "which can be used to implement this concept.")] + public List SubmodelElements { get; set; } = null; + + [ExtensionHintAttribute("\u00ab Experimental \u00bb Specifies a list of concepts, which are " + + "organized, hierarchically structured, within this concept. This does not impose a type-like " + + "semantic. A concept might be organized by multiple concepts.")] + public List Organizes { get; set; } = null; + + // + // Init + // + + protected static string[] CardinalitiesShort = null; + + static SmtAttributeRecord() + { + // cardinalities + CardinalitiesShort = EnumHelper.EnumHelperGetMemberInfo(typeof(AasSmtQualifiers.SmtCardinality)) + .Select((em) => + { + var st = em.MemberDisplay; + var p = st.IndexOf('['); + if (p > 0) + st = st.Substring(p); + return st; + }).ToArray(); + } + + public string CardinalityShort() + { + if (CardinalitiesShort == null) + return ""; + var i = ((int)this.Cardinality) % CardinalitiesShort.Length; + return CardinalitiesShort[i]; + } + + // + // Join + // + + /// + /// Take over attributes from another SmtAttributeRecord. If present/ cannot joined, the other + /// attributes will be dominant. + /// + /// + public void JoinAttributes(SmtAttributeRecord other) + { + // access + if (other == null) + return; + + // define some small lambdas + Func JoinStrings = (a, other, delim) => + { + if (other?.HasContent() == true && a?.Contains(other) != true) + { + if (a == null) + a = ""; + if (a.HasContent()) + a = a + delim; + a = a + other; + } + return a; + }; + + Func OverStrings = (a, other) => + { + if (other?.HasContent() == true) + a = other; + return a; + }; + + // apply + Cardinality = other.Cardinality; + EitherOr = JoinStrings(EitherOr, other.EitherOr, "|"); + InitialValue = OverStrings(InitialValue, other.InitialValue); + DefaultValue = OverStrings(DefaultValue, other.DefaultValue); + ExampleValue = JoinStrings(ExampleValue, other.ExampleValue, ","); + AllowedRange = JoinStrings(AllowedRange, other.AllowedRange, "|"); + AllowedValue = OverStrings(AllowedValue, other.AllowedValue); + AllowedIdShort = OverStrings(AllowedIdShort, other.AllowedIdShort); + RequiredLang = JoinStrings(RequiredLang, other.RequiredLang, "|"); + AccessMode = other.AccessMode; + IsSubmodel = other.IsSubmodel; + + if (other.SubmodelElements != null && other.SubmodelElements.Count > 0) + foreach (var osme in other.SubmodelElements) + { + SubmodelElements = SubmodelElements ?? new List(); + if (SubmodelElements.Contains(osme)) + continue; + SubmodelElements.Add(osme); + } + + if (other.Organizes != null && other.Organizes.Count > 0) + foreach (var oorg in other.Organizes) + { + Organizes = Organizes ?? new List(); + var found = false; + foreach (var org in Organizes) + if (org?.Value == oorg?.Value) + found = true; + if (found) + continue; + Organizes.Add(oorg.Copy()); + } + } + // // Check // @@ -1382,6 +1963,191 @@ public static List PerformAttributeCheck(List(ownCd)?.FirstOrDefault(); + if (smtRec == null) + smtRec = new SmtAttributeRecord(); + var changes = false; + + // check if the SME has an attribute record + var smeSmtRec = DispEditHelperExtensions + .CheckReferableForExtensionRecords(rf)?.FirstOrDefault(); + if (smeSmtRec != null) + { + // the SME attributes are dominant + smtRec.JoinAttributes(smeSmtRec); + changes = true; + } + + // reset + if (eachElemDetails) + { + // touch every record, set Submodel / SME flags + smtRec.IsSubmodel = false; + changes = true; + + if (rf is Aas.Submodel) + { + smtRec.IsSubmodel = true; + } + + if (rf is Aas.ISubmodelElement sme) + { + var sd = sme.GetSelfDescription(); + try + { + var se = Stringification.AasSubmodelElementsFromString(sd.AasElementName); + if (se.HasValue) + { + smtRec.SubmodelElements = smtRec.SubmodelElements ?? new List(); + smtRec.SubmodelElements.Add(se.Value); + } + } catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + } + } + } + + // build a dictionary of descendant's semantic ids .. + var dict = new MultiValueDictionary(); + foreach (var x in rf.DescendOnce()) + if (x is Aas.ISubmodelElement sme) + { + var id = sme.GetConceptDescriptionId(); + if (id != null) + dict.Add(id, sme); + } + + // now go over these dict's keys! + foreach (var dictKey in dict.Keys) + { + // make really really really clear that the reference is not already contained!! + var already = false; + if (smtRec.Organizes != null) + foreach (var oo in smtRec.Organizes) + if (oo?.Value?.Trim() == dictKey?.Trim()) + { + already = true; + break; + } + if (already) + continue; + + // without further ado, just put it in! + smtRec.Organizes = smtRec.Organizes ?? new List(); + smtRec.Organizes.Add(new ExtIdfReference(dictKey)); + changes = true; + } + + // if changes, write back + if (changes) + { + if (ownCd.Extensions != null && ownCd.Extensions.Count > 1) + ; + DispEditHelperExtensions.GeneralExtensionHelperAddJsonExtension( + ownCd, smtRec.GetType(), smtRec, + replaceExistingRecordType: true); + } + + // okay, give back changes + return changes; + } + + // + // Populate based on attributes + // + + public Aas.IReferable PopulateReferable(Aas.IReferable rf, Aas.IConceptDescription cd) + { + // access + if (rf == null || cd == null) + return rf; + + // IReferable attributes + rf.Description = cd?.Description?.Copy(); + rf.DisplayName = cd?.DisplayName?.Copy(); + rf.IdShort = "" + cd?.IdShort; + + // admin info? + if (rf is Aas.IIdentifiable idf && cd.Administration != null) + idf.Administration = cd.Administration.Copy(); + + // semanticId! + if (rf is Aas.Submodel sm) + sm.SemanticId = new Aas.Reference(ReferenceTypes.ModelReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.Submodel, cd.Id) }).ToList()); + else + if (rf is Aas.IHasSemantics ihs) + ihs.SemanticId = new Aas.Reference(ReferenceTypes.ExternalReference, + (new Aas.IKey[] { new Aas.Key(KeyTypes.GlobalReference, cd.Id) }).ToList()); + + // values + var valToUse = this.InitialValue; + if (valToUse?.HasContent() != true) + valToUse = "" + this.ExampleValue; + + if (rf is Aas.IProperty prop) + { + prop.Value = "" + valToUse; + } + else + if (rf is Aas.IRange rng) + { + rng.Max = "" + valToUse; + rng.Min = "" + valToUse; + } + else + if (rf is Aas.IMultiLanguageProperty mlp) + { + if (this.RequiredLang?.HasContent() == true) + foreach (var lang in this.RequiredLang.Split("|", + StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + mlp.Value = mlp.Value ?? new List(); + mlp.Value.Add(new Aas.LangStringTextType(lang, "" + valToUse)); + } + } + else + if (rf is Aas.IFile file) + { + file.Value = "" + valToUse; + } + else + if (rf is Aas.IBlob blob) + { + blob.Value = Encoding.Default.GetBytes("" + valToUse); + } + + // okay + return rf; + } } public class ExtensionRecords diff --git a/src/AasxPackageLogic/DispEditHelperMiniModules.cs b/src/AasxPackageLogic/DispEditHelperMiniModules.cs index e90369b01..fea3d3d32 100644 --- a/src/AasxPackageLogic/DispEditHelperMiniModules.cs +++ b/src/AasxPackageLogic/DispEditHelperMiniModules.cs @@ -19,6 +19,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Intrinsics.X86; using Aas = AasCore.Aas3_0; using Samm = AasCore.Samm2_2_0; @@ -1550,22 +1551,146 @@ public void ValueListHelper( } } - /// - /// Provides a menu to add a new SubmodelElement to a list of these. - /// - public void DispSmeListAddNewHelper( + public class DispSmeListAddNewSmtItemRecord + { + public Aas.IConceptDescription Cd; + public SmtAttributeRecord SmtRec; + public Samm.ModelElement SammMe; + public AasSubmodelElements? Sme; + } + + protected List DispSmeListAddNewCheckForSmtItems( + Aas.IReference basedOnSemanticId) + { + // access + var res = new List(); + if (basedOnSemanticId?.IsValid() != true + || basedOnSemanticId.Count() != 1) + return res; + + // check all ConceptDescriptions for the semanticId + var cdId = basedOnSemanticId.Keys[0].Value; + var candidates = new List>(); + foreach (var rftup in packages.QuickLookupAllIdent(cdId)) + if (rftup?.Item2 is Aas.ConceptDescription cd) + { + // SMT extension + foreach (var smtRec in DispEditHelperExtensions + .CheckReferableForExtensionRecords(cd)) + // find all organizes + if (smtRec.Organizes != null) + foreach (var orgId in smtRec.Organizes) + foreach (var rftup2 in packages.QuickLookupAllIdent(orgId?.Value)) + if (rftup2.Item2 is Aas.ConceptDescription cd2) + foreach (var smtRec2 in DispEditHelperExtensions + .CheckReferableForExtensionRecords(cd2)) + { + // now, smtRec2 is a candidate for a follow up! + candidates.Add(new Tuple(cd2, smtRec2)); + } + + // SAMM extension + foreach (var me in DispEditHelperSammModules.CheckReferableForSammElements(cd)) + { + foreach (var item in SammTransformation.FindChildElementsForConcept(packages, cd, me)) + candidates.Add(item); + } + } + + // check candidates further + foreach (var cand in candidates) + { + // access + var cd = cand.Item1; + var smtRec = cand.Item2; + if (cd == null || smtRec == null) + continue; + + // Submodel + if (smtRec.IsSubmodel) + { + // basically makes no sense + res.Add(new AnyUiDialogueDataGridRow() + { + // Text = $"{cd.IdShort} (Submodel) {cd.Id}", + Cells = (new[] { "-", smtRec.CardinalityShort(), "SM", cd.IdShort, cd.Id }).ToList(), + Tag = new DispSmeListAddNewSmtItemRecord() + { + Cd = cd, + SmtRec = smtRec, + Sme = null + } + }); + } + else + { + if (smtRec.SubmodelElements != null) + foreach (var smet in smtRec.SubmodelElements) + res.Add(new AnyUiDialogueDataGridRow() + { + // Text = $"{cd.IdShort} ({smet.ToString()}) {cd.Id}", + Cells = (new[] { "-", smtRec.CardinalityShort(), + ExtendISubmodelElement.ToString(smet), cd.IdShort, cd.Id }).ToList(), + Tag = new DispSmeListAddNewSmtItemRecord() + { + Cd = cd, + SmtRec = smtRec, + Sme = smet + } + }); + } + } + + // ok + return res; + } + + protected void DispSmeListAddNewDetailOnItems( + List smeList, + List items) where T : class, ISubmodelElement + { + // access + if (smeList == null || items == null) + return; + + // for all items + foreach (var item in items) + { + // access + if (!(item.Tag is DispSmeListAddNewSmtItemRecord itrec) + || itrec.Cd?.Id == null) + continue; + + // search for sme's having the specific semantic id + var cnt = smeList.Where((sme) => (sme?.SemanticId? + .Matches(KeyTypes.GlobalReference, itrec.Cd.Id, MatchMode.Relaxed) == true)).Count(); + if (cnt > 0) + // make sense to rework + item.Cells[0] = "" + cnt; + } + } + + /// + /// Provides a menu to add a new SubmodelElement to a list of these. + /// + public void DispSmeListAddNewHelper( Aas.Environment env, AnyUiStackPanel stack, ModifyRepo repo, string key, List smeList, Action> setValueLambda = null, - AasxMenu superMenu = null) where T : ISubmodelElement + AasxMenu superMenu = null, + Aas.IReference basedOnSemanticId = null) where T : class, ISubmodelElement { // access if (stack == null) return; - // hint - this.AddHintBubble(stack, hintMode, new[] { + // gather potential SMT element items + var smtElemItem = DispSmeListAddNewCheckForSmtItems(basedOnSemanticId); + + // hint + this.AddHintBubble(stack, hintMode, new[] { new HintCheck( () => { return smeList == null || smeList.Count < 1; }, "This element currently has no SubmodelElements, yet. " + @@ -1596,6 +1721,12 @@ public void DispSmeListAddNewHelper( args: new AasxMenuListOfArgDefs() .Add("Kind", "Name (not abbreviated) of kind of SubmodelElement.")); + if (smtElemItem.Count > 0) + { + menu.AddAction("add-smt-organized", "Add SMT organized ..", + "Adds a element based on SMT organized elements given by semanticId."); + } + this.AddActionPanel( stack, key, repo: repo, superMenu: superMenu, @@ -1643,7 +1774,10 @@ public void DispSmeListAddNewHelper( smeList = new List(); setValueLambda?.Invoke(smeList); } + + smeList = smeList ?? new List(); smeList.Add(smw); + setValueLambda(smeList); // make some more adjustments if (sme2 is IMultiLanguageProperty mlp) @@ -1665,6 +1799,67 @@ public void DispSmeListAddNewHelper( return new AnyUiLambdaActionRedrawAllElements(nextFocus: sme2, isExpanded: true); } } + + if (buttonNdx == 4) + { + // new SMT + + // rework list + DispSmeListAddNewDetailOnItems(smeList, smtElemItem); + + // show list + var uc = new AnyUiDialogueDataSelectFromDataGrid( + "Select element(s) to be created guided by SMT attributes ..", + maxWidth: 1400); + + uc.ColumnDefs = AnyUiListOfGridLength.Parse(new[] { "1*", "1*", "1*", "5*", "8*" }); + uc.ColumnHeaders = new[] { "Present", "Card.", "Type", "IdShort", "Id" }; + uc.Rows = smtElemItem; + + this.context.StartFlyoverModal(uc); + var itemsAdded = 0; + ISubmodelElement lastSme = null; + if (uc.ResultItems != null) + foreach (var ucr in uc.ResultItems) + if (ucr?.Tag is DispSmeListAddNewSmtItemRecord item) + { + // create from SMT and do NOT allow Submodels here + if (item.SmtRec != null && item.Sme.HasValue && item.Cd != null) + { + // create a new SME + var sme = AdminShellUtil.CreateSubmodelElementFromEnum(item.Sme.Value); + if (sme == null) + { + Log.Singleton.Error("Creating type provided by SMT attributes."); + return new AnyUiLambdaActionNone(); + } + + // populate by SMT attributes + item.SmtRec.PopulateReferable(sme, item.Cd); + + // add & confirm + var smw = sme as T; + if (smw != null) + { + smeList.Add(smw); + this.AddDiaryEntry(sme, new DiaryEntryStructChange( + StructuralChangeReason.Create)); + + // statistics + lastSme = sme; + itemsAdded++; + } + } + } + + // finalize + if (itemsAdded > 0) + Log.Singleton.Info($"{itemsAdded} elements guided by SMT were added."); + + if (lastSme != null) + return new AnyUiLambdaActionRedrawAllElements(nextFocus: lastSme, isExpanded: true); + } + return new AnyUiLambdaActionNone(); }); } diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 5dfabf6b9..21b51e79f 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -2490,7 +2490,7 @@ public List DisplayOrEditEntityCheckValueEvalItems( && packages != null) { // try find - foreach (var x in packages.LookupAllIdent(rfsem.SemanticId.Keys[0].Value)) + foreach (var x in packages.QuickLookupAllIdent(rfsem.SemanticId.Keys[0].Value)) if (x.Item2 is Aas.IConceptDescription rfsemCd) { var rec2 = CheckReferableForExtensionRecords(rfsemCd).FirstOrDefault(); diff --git a/src/AasxPackageLogic/DispEditHelperMultiElement.cs b/src/AasxPackageLogic/DispEditHelperMultiElement.cs index 5163c1b2e..267a18fb4 100644 --- a/src/AasxPackageLogic/DispEditHelperMultiElement.cs +++ b/src/AasxPackageLogic/DispEditHelperMultiElement.cs @@ -15,6 +15,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System; using System.Collections.Generic; using System.Linq; +using static AnyUi.AnyUiDialogueDataTextEditor; using Aas = AasCore.Aas3_0; namespace AasxPackageLogic @@ -334,6 +335,62 @@ public void ChangeElementAttributes(Aas.IClass el, AnyUiDialogueDataChangeElemen } } + public void DispEditMultiElemsRemoveExtensions(IEnumerable bosObjs) + { + // idShort or semId? + var useSemId = AnyUiMessageBoxResult.Yes == + this.context.MessageBoxFlyoutShow( + "Do you want to select extension based on name (No) or based on" + + "semanticId (Yes)?", "Remove extensions", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Question); + + // collect data + var dict = new MultiValueDictionary>(); + foreach (var bo in bosObjs) + if (bo?.Extensions != null) + foreach (var ext in bo.Extensions) + { + var key = (useSemId) ? "" + ext.SemanticId?.ToStringExtended() : "" + ext.Name; + if (!key.HasContent()) + continue; + dict.Add( + key: key, + value: new Tuple(bo, ext)); + } + if (dict.Keys.Count() < 1) + { + Log.Singleton.Error("Remove extension from multiple elements: Could not find valid " + + "selection items. Aborting!"); + return; + } + + // make list + var uc = new AnyUiDialogueDataSelectFromList(); + uc.ListOfItems = dict.Keys.Select((dictKey) + => new AnyUiDialogueListItem() + { + Text = "" + dictKey + $" ({dict[dictKey].Count()})", + Tag = dict[dictKey].ToList() + }).ToList(); + + this.context.StartFlyoverModal(uc); + if (uc.Result && uc.ResultItem?.Tag is List> tuples) + { + int numRemove = 0; + + foreach (var tuple in tuples) + if (tuple?.Item1?.Extensions != null && tuple.Item2 != null + && tuple.Item1.Extensions.Contains(tuple.Item2)) + { + tuple.Item1.Extensions.Remove(tuple.Item2); + numRemove++; + } + + Log.Singleton.Info($"Remove extension from multiple elements: {numRemove} extensions removed."); + } + + } + /// /// This is a special helper for multiple selected entities in a list of entoties of type T. /// It is separate from EntityListUpDownDeleteHelper in order to minimize cross effecrs @@ -613,7 +670,9 @@ public void DisplayOrEditAasEntityMultipleElements( repo: repo, superMenu: superMenu, ticketMenu: new AasxMenu() .AddAction("aas-elem-cut", "Change attribute ..", - "Changes common attributes of multiple selected elements."), + "Changes common attributes of multiple selected elements.") + .AddAction("remove-extension", "Remove extensions ..", + "Removes a specific selected extension from elements."), ticketAction: (buttonNdx, ticket) => { if (buttonNdx == 0) @@ -630,7 +689,14 @@ public void DisplayOrEditAasEntityMultipleElements( return new AnyUiLambdaActionRedrawAllElements(nextFocus: nf); } } - return new AnyUiLambdaActionNone(); + + if (buttonNdx == 1) + { + DispEditMultiElemsRemoveExtensions(bos); + return new AnyUiLambdaActionRedrawAllElements(nextFocus: null); + } + + return new AnyUiLambdaActionNone(); }); } } diff --git a/src/AasxPackageLogic/DispEditHelperSammModules.cs b/src/AasxPackageLogic/DispEditHelperSammModules.cs index f622c7474..c6617237b 100644 --- a/src/AasxPackageLogic/DispEditHelperSammModules.cs +++ b/src/AasxPackageLogic/DispEditHelperSammModules.cs @@ -259,7 +259,7 @@ public void SammExtensionHelperAddSammReference( T sr, Action setValue, Func createInstance, - bool noFirstColumnWidth = false, + int firstColumnWidth = -1, // -1 = Standard string[] presetList = null, bool showButtons = true, bool editOptionalFlag = false, @@ -278,7 +278,7 @@ public void SammExtensionHelperAddSammReference( return new AnyUiLambdaActionNone(); }, keyVertCenter: true, - noFirstColumnWidth: noFirstColumnWidth, + firstColumnWidth: firstColumnWidth, auxButtonTitles: !showButtons ? null : new[] { "Preset", "Existing", "New", "Jump" }, auxButtonToolTips: !showButtons ? null : new[] { "Select from given presets.", @@ -369,7 +369,7 @@ public void SammExtensionHelperAddListOfSammReference( sp1, $"[{1 + lsri}]", (Samm.ModelElement)sammInst, relatedReferable, value[lsri], - noFirstColumnWidth: true, + firstColumnWidth: 40, showButtons: false, editOptionalFlag: editOptionalFlag, addableElements: addableElements, @@ -2563,5 +2563,74 @@ public void CreateSubmodelInstanceFromAspectCD( firstAas.AddSubmodelReference( submodel.GetModelReference()); } + + public static IEnumerable> + FindChildElementsForConcept( + PackageCentral.PackageCentral packages, + Aas.IConceptDescription cd, + ModelElement me) + { + // access + if (packages == null || cd == null || me == null) + yield break; + + // possible childs? + var childs = new List(); + + // Aspect + if (me is Samm.Aspect asp) + { + if (asp.Properties != null) + childs.AddRange(asp.Properties); + if (asp.Operations != null) + childs.AddRange(asp.Operations); + if (asp.Events != null) + childs.AddRange(asp.Events); + } + + // Property -> Charasteristics -> Enitity -> Property + if (me is Samm.Property prop) + { + var propCharCd = packages.QuickLookupFirstIdent(prop.Characteristic?.Value); + var propCharMe = DispEditHelperSammModules.CheckReferableForSammElements(propCharCd)?.FirstOrDefault(); + if (propCharMe is Samm.Characteristic propChar) + { + var propEntCd = packages.QuickLookupFirstIdent(propChar.DataType?.Value); + var propEntMe = DispEditHelperSammModules.CheckReferableForSammElements(propEntCd)?.FirstOrDefault(); + if (propEntMe is Samm.Entity ent && ent.Properties != null) + foreach (var p in ent.Properties) + childs.Add(p); + } + } + + // try lookup childs + foreach (var child in childs) + { + // access + var childCd = packages.QuickLookupFirstIdent(child?.Value); + var childMe = DispEditHelperSammModules.CheckReferableForSammElements(childCd)?.FirstOrDefault(); + + if (childMe is Samm.Property childProp) + { + // for the time being, fake a SmtRecord + var smtRec = new SmtAttributeRecord(); + + // poor mens cardinality + smtRec.Cardinality = AasSmtQualifiers.SmtCardinality.One; + if (child is OptionalSammReference osr && osr.Optional) + smtRec.Cardinality = AasSmtQualifiers.SmtCardinality.ZeroToOne; + + // poor mens initial / default / example value + smtRec.ExampleValue = childProp.ExampleValue; + + // poor mens SME type + smtRec.SubmodelElements = new List() { AasSubmodelElements.Property }; + + // put into intem + var item = new Tuple(childCd, smtRec); + yield return item; + } + } + } } } diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index a2191904b..a4fafc1b4 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -509,7 +509,12 @@ public AnyUiColor GetColor(ColorNames c) Cmd = "-plugin-prefer")] public string PluginPrefer = null; - [OptionDescription(Description = + [OptionDescription(Description = + "Sorting order of ConceptDescriptions (ListIndex, IdShort, Id, Submodel, SME, Structured)", + Cmd = "-cd-sort-order")] + public string CdSortOrder = null; + + [OptionDescription(Description = "For such operations as query repository, do load a new AASX file without " + "prompting the user.", Cmd = "-load-without-prompt")] diff --git a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs index ae5885006..8a04f55fb 100644 --- a/src/AasxPackageLogic/PackageCentral/PackageCentral.cs +++ b/src/AasxPackageLogic/PackageCentral/PackageCentral.cs @@ -9,10 +9,13 @@ This source code may use other Open Source software components (see LICENSE.txt) using AasxIntegrationBase.AdminShellEvents; using AdminShellNS; +using Extensions; using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Aas = AasCore.Aas3_0; +using AasxIntegrationBase; namespace AasxPackageLogic.PackageCentral { @@ -26,6 +29,8 @@ public PackageCentralException(string message, Exception innerException = null) : base(message, innerException) { } } + public delegate Aas.IReferable QuickLookupIdentifiable(string idKey); + /// /// This class is an item maintained by the PackageCentral. /// Note: this class works on application level; it means to be resilient, reports errors to the Log @@ -326,14 +331,54 @@ public IEnumerable GetAllPackageEnv(Func> LookupAllIdent(string idKey) + /// + /// This provides a "quick" lookup of Identifiables, e.g. based on hashes/ dictionaries. + /// May be not 100% reliable, but quick. + /// + public IEnumerable> QuickLookupAllIdent( + string idKey, + bool deepLookup = false) { + if (idKey?.HasContent() != true) + yield break; + foreach (var cnt in GetAllContainer()) + { + var res = new Dictionary(); + if (cnt.IdentifiableLookup != null) foreach (var idf in cnt.IdentifiableLookup.LookupAllIdent(idKey)) - yield return new Tuple(cnt, idf); + if (!res.ContainsKey(idf)) + res.Add(idf, idf); + + if (deepLookup && cnt.Env?.AasEnv != null) + foreach (var rfi in cnt.Env?.AasEnv.FindAllReferable(onlyIdentifiables: true)) + if (rfi is Aas.IIdentifiable idf && idf.Id?.Trim() == idKey.Trim()) + res.Add(idf, idf); + + foreach (var idf in res.Keys) + yield return new Tuple(cnt, idf); + } } + /// + /// This provides a "quick" lookup of Identifiables, e.g. based on hashes/ dictionaries. + /// May be not 100% reliable, but quick. + /// + public Aas.IReferable QuickLookupFirstIdent(string idKey) + { + return QuickLookupAllIdent(idKey).FirstOrDefault()?.Item2; + } + + /// + /// This provides a "quick" lookup of Identifiables, e.g. based on hashes/ dictionaries. + /// May be not 100% reliable, but quick. + /// + public T QuickLookupFirstIdent(string idKey) where T : class, Aas.IReferable + { + return QuickLookupAllIdent(idKey).FirstOrDefault()?.Item2 as T; + } + // // Event management // diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 64e30e767..936d08584 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -19,6 +19,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Runtime.Serialization; using Aas = AasCore.Aas3_0; using Samm = AasCore.Samm2_2_0; @@ -563,7 +564,20 @@ public enum ItemType "Environment", "AdministrationShells", "ConceptDescriptions", "Package", "OrphanSubmodels", "AllSubmodels", "SupplementalFiles", "Value.Aas.Reference", "Empty", "Dummy" }; - public enum ConceptDescSortOrder { None = 0, IdShort, Id, BySubmodel, BySme, Structured } + public enum ConceptDescSortOrder { + [EnumMember(Value = "ListIndex")] + None = 0, + [EnumMember(Value = "IdShort")] + IdShort, + [EnumMember(Value = "Id")] + Id, + [EnumMember(Value = "Submodel")] + BySubmodel, + [EnumMember(Value = "SME")] + BySme, + [EnumMember(Value = "Structured")] + Structured + } public string thePackageSourceFn; public AdminShellPackageEnv thePackage = null; @@ -651,7 +665,15 @@ public ConceptDescSortOrder CdSortOrder VisualElementEnvironmentItem._cdSortOrder = value; } } - } + + public static void SetCdSortOrderByString(string order) + { + if (order?.HasContent() != true) + return; + VisualElementEnvironmentItem._cdSortOrder = + EnumHelper.GetEnumMemberFromValueString(order); + } + } public class VisualElementAdminShell : VisualElementGeneric { @@ -1177,12 +1199,13 @@ public override void RefreshFromMainData() } } - public class VisualElementConceptDescription : VisualElementGeneric { public Aas.Environment theEnv = null; public Aas.IConceptDescription theCD = null; + public bool HasSpecialColors = false; + public VisualElementConceptDescription( VisualElementGeneric parent, TreeViewLineCache cache, Aas.Environment env, Aas.IConceptDescription cd) @@ -1204,6 +1227,27 @@ public VisualElementConceptDescription( RestoreFromCache(); } + public void ApplyShade(int recursionIndex) + { + // normal "CD" colors + if (!HasSpecialColors) + switch (recursionIndex % 4) + { + case 0: + this.TagBg = new AnyUiColor(0xff707070u); + break; + case 1: + this.TagBg = new AnyUiColor(0xff505050u); + break; + case 2: + this.TagBg = new AnyUiColor(0xff303030u); + break; + case 3: + this.TagBg = new AnyUiColor(0xff101010u); + break; + } + } + public override string GetFilterElementInfo() { return "ConceptDescription"; @@ -1252,6 +1296,8 @@ public override void RefreshFromMainData() this.Background = new AnyUiColor(Samm.Constants.RenderBackground); this.TagBg = new AnyUiColor(ri.Background); this.TagFg = new AnyUiColor(ri.Foreground); + + this.HasSpecialColors = true; } } @@ -1278,6 +1324,19 @@ public override void RefreshFromMainData() } } + // member access + + public IEnumerable FindAllMemberWithId(string id) + { + foreach (var mem in Members) + if (mem is VisualElementConceptDescription memcd + && memcd?.theCD?.Id?.HasContent() == true + && memcd.theCD.Id.Trim() == id.Trim()) + { + yield return memcd; + } + } + // sorting public class ComparerIdShort : IComparer @@ -1544,7 +1603,10 @@ private VisualElementEnvironmentItem private MultiValueDictionary _cdToSm = new MultiValueDictionary(); - public ListOfVisualElement() + private MultiValueDictionary _cdInStructure = + new MultiValueDictionary(); + + public ListOfVisualElement() { // interested plug-ins _pluginsToCheck.Clear(); @@ -1896,7 +1958,35 @@ private static void ObservableCollectionSort(ObservableCollection collecti } } - private void GenerateInnerElementsForConceptDescriptions( + private IEnumerable ComputeTopsOfExtensionForest( + List allIdf) + { + // access + if (allIdf == null) + yield break; + + // first, put all Identifiables into a dictionary + var tops = new MultiValueDictionary(); + foreach (var idf in allIdf) + tops.Add(idf.Id, idf); + + // now, go through all Identifiables and remove the direct descendants + foreach (var idf in allIdf) + foreach (var idfrec in DispEditHelperExtensions.CheckReferableForExtensionRecords(idf)) + if (idfrec is IExtensionStructureModel asm) + { + // use this information to REMOVE all Identifiables, which are descendants + foreach (var dsc in asm.DescendOnce()) + tops.Remove(dsc.Value); + } + + // the remaining keys point to top Identifiables + foreach (var key in tops.Keys) + foreach (var i2 in tops[key]) + yield return i2; + } + + private void GenerateInnerElementsForConceptDescriptions( TreeViewLineCache cache, Aas.Environment env, VisualElementEnvironmentItem tiCDs, VisualElementGeneric root, @@ -1910,17 +2000,54 @@ private void GenerateInnerElementsForConceptDescriptions( // try to approach structures first // - if (tiCDs.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.Structured) + var tiUnstructuredRoot = tiCDs; + + if (tiCDs.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.Structured) { - // recursive lambda!! - Action lambdaAddRecurse = null; - lambdaAddRecurse = (tiParent, cd) => + // + // Forest of hierarchies + // + + var tiStructuredRoot = new VisualElementEnvironmentItem( + parent: tiCDs, cache: cache, + package: tiCDs.thePackage, env: tiCDs.theEnv, + itemType: VisualElementEnvironmentItem.ItemType.Env); + tiStructuredRoot.Caption = "Structured ConceptDescriptions"; + tiStructuredRoot.IsExpanded = false; + tiCDs.Members.Add(tiStructuredRoot); + + // recursive lambda!! + Action lambdaAddRecurse = null; + lambdaAddRecurse = (tiParent, cd, recDepth) => { // add var tiCD = GenerateVisualElementsForSingleCD(cache, env, cd, tiParent); - _cdReferred.Add(cd, tiCD); + tiCD.ApplyShade(recDepth); + + // when straight called, might be not part of a structure + _cdInStructure.Add(cd, tiCD); + + // look for Extension descendants + foreach (var ee in DispEditHelperExtensions.CheckReferableForExtensionRecords(cd)) + if (ee is IExtensionStructureModel esm) + foreach (var ier in esm.DescendOnce()) + { + // try to find extension elements + if (ier?.Value?.HasContent() != true || !_idToReferable.ContainsKey(ier.Value)) + continue; + + // already in? + if (tiCD.FindAllMemberWithId(ier.Value).FirstOrDefault() != null) + continue; - // look for descendants + // add + foreach (var y in _idToReferable[ier.Value]) + if (y is Aas.IConceptDescription foundCD) + // descendents will be marked as in structure + lambdaAddRecurse(tiCD, foundCD, recDepth + 1); + } + + // look for SAMM descendants foreach (var me in DispEditHelperSammModules.CheckReferableForSammElements(cd)) if (me is Samm.ISammStructureModel ssm) foreach (var sr in ssm.DescendOnce()) @@ -1931,18 +2058,85 @@ private void GenerateInnerElementsForConceptDescriptions( foreach (var y in _idToReferable[sr.Value]) if (y is Aas.IConceptDescription foundCD) - lambdaAddRecurse(tiCD, foundCD); + // descendents will be marked as in structure + lambdaAddRecurse(tiCD, foundCD, recDepth + 1); } }; - // visit top nodes to start the lambda + // for the Extensions, identify the tops of the forest by computation + foreach (var idf in ComputeTopsOfExtensionForest( + env.ConceptDescriptions.Cast().ToList())) + { + foreach(var idfrec in DispEditHelperExtensions.CheckReferableForExtensionRecords(idf)) + if (idfrec is IExtensionStructureModel esm /* && esm.IsTopElement() */) + { + // add && recurse + // might not be in structure + lambdaAddRecurse(tiStructuredRoot, idf as Aas.IConceptDescription, 0); + } + } + + // visit dedicated top nodes to start the lambda foreach (var cd in env.ConceptDescriptions) - foreach (var me in DispEditHelperSammModules.CheckReferableForSammElements(cd)) + { + // SAMM + foreach (var me in DispEditHelperSammModules.CheckReferableForSammElements(cd)) if (me is Samm.ISammStructureModel ssm && ssm.IsTopElement()) { // add && recurse - lambdaAddRecurse(tiCDs, cd); - } + // mark as in structure + lambdaAddRecurse(tiStructuredRoot, cd, 0); + } + } + + // + // provide an branch per Submodel? + // + + var tiSubmodelsRoot = new VisualElementEnvironmentItem( + parent: tiCDs, cache: cache, + package: tiCDs.thePackage, env: tiCDs.theEnv, + itemType: VisualElementEnvironmentItem.ItemType.Env); + tiSubmodelsRoot.Caption = "Submodel ConceptDescriptions"; + tiSubmodelsRoot.IsExpanded = false; + tiCDs.Members.Add(tiSubmodelsRoot); + + if (env?.Submodels != null) + foreach (var sm in env.Submodels) + { + // branch per Submodel + var tiSM = new VisualElementEnvironmentItem( + parent: tiCDs, cache: cache, + package: tiCDs.thePackage, env: tiCDs.theEnv, + itemType: VisualElementEnvironmentItem.ItemType.Env); + tiSM.Caption = "Submodel: " + sm.IdShort; + if (sm.Administration != null) + tiSM.Info += $" V{sm.Administration.Version}.{sm.Administration.Revision}"; + tiSubmodelsRoot.Members.Add(tiSM); + + // now list CDs here + foreach (var cd in env.ConceptDescriptions) + { + if (!_cdToSm.ContainsKey(cd)) + continue; + if (null == _cdToSm[cd].Where((cdsm) => cdsm == sm).FirstOrDefault()) + continue; + + GenerateVisualElementsForSingleCD(cache, env, cd, tiSM); + } + } + + // + // provide extra branch for "unstructured" + // + + tiUnstructuredRoot = new VisualElementEnvironmentItem( + parent: tiCDs, cache: cache, + package: tiCDs.thePackage, env: tiCDs.theEnv, + itemType: VisualElementEnvironmentItem.ItemType.Env); + tiUnstructuredRoot.Caption = "Unstructured ConceptDescriptions"; + tiCDs.Members.Add(tiUnstructuredRoot); + tiUnstructuredRoot.IsExpanded = false; } // @@ -1961,10 +2155,11 @@ private void GenerateInnerElementsForConceptDescriptions( continue; if (tiCDs.CdSortOrder == VisualElementEnvironmentItem.ConceptDescSortOrder.Structured - && _cdReferred.ContainsKey(cd)) + && ( _cdInStructure.ContainsKey(cd) || _cdToSm.ContainsKey(cd))) continue; - GenerateVisualElementsForSingleCD(cache, env, cd, tiCDs); + // add to the "unstructured" branch of the tree + GenerateVisualElementsForSingleCD(cache, env, cd, tiUnstructuredRoot); } // @@ -2022,6 +2217,7 @@ public void AddVisualElementsFromShellEnv( _idToReferable.Clear(); _cdReferred.Clear(); _cdToSm.Clear(); + _cdInStructure.Clear(); foreach (var aas in env.AssetAdministrationShells) if (aas != null) diff --git a/src/AasxWpfControlLibrary/AnyUiWpf.cs b/src/AasxWpfControlLibrary/AnyUiWpf.cs index 0cd536706..f18fecc87 100644 --- a/src/AasxWpfControlLibrary/AnyUiWpf.cs +++ b/src/AasxWpfControlLibrary/AnyUiWpf.cs @@ -1619,7 +1619,14 @@ private UserControl DispatchFlyout(AnyUiDialogueDataBase dialogueData) res = uc; } - if (dialogueData is AnyUiDialogueDataSelectAasEntity ddsa) + if (dialogueData is AnyUiDialogueDataSelectFromDataGrid ddsdg) + { + var uc = new SelectFromDataGridFlyout(); + uc.DiaData = ddsdg; + res = uc; + } + + if (dialogueData is AnyUiDialogueDataSelectAasEntity ddsa) { var uc = new SelectAasEntityFlyout(Packages); uc.DiaData = ddsa; diff --git a/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml b/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml new file mode 100644 index 000000000..82271a082 --- /dev/null +++ b/src/AasxWpfControlLibrary/Flyouts/SelectFromDataGridFlyout.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} + +@code { + [Parameter] + public AnyUiHtmlEventSession EventSession { get; set; } + + [Parameter] + public AnyUiDialogueDataBase DialogueData { get; set; } + + [Parameter] + public AasxPredefinedConcepts.DefinitionsPool DataSourcePools { get; set; } + + protected List _domainNames = new List(); + protected string _domainCurrent = ""; + + protected DefinitionsPoolEntityBase _selectedEntity = null; + protected List _domainEntities = new List(); + + protected override void OnInitialized() + { + // pass thru + base.OnInitialized(); + + // access + if (!(DialogueData is AnyUiDialogueDataSelectReferableFromPool ddsrp)) + return; + + // domains + _domainNames.Clear(); + if (DataSourcePools != null) + { + var domains = DataSourcePools.GetDomains().ToList(); + domains.Sort(); + foreach (var d in domains) + _domainNames.Add(d); + } + + // no domain entities, yet + _domainEntities.Clear(); + + // first? + if (_domainNames.Count > 0) + { + _domainCurrent = _domainNames[0]; + ShowDomain(_domainCurrent); + } + } + + protected void ShowDomain(string domain) + { + // access + if (domain?.HasContent() != true) + return; + + // nothing selected, yet + _selectedEntity = null; + + // display + var ld = this.DataSourcePools?.GetEntitiesForDomain(domain)?.ToList(); + if (ld != null) + { + ld.Sort((x1, x2) => x1.DisplayName.CompareTo(x2.DisplayName)); + _domainEntities.Clear(); + foreach (var ent in ld) + _domainEntities.Add(ent); + StateHasChanged(); + } + } + + void OnSelectDomain(ChangeEventArgs e) + { + if (DialogueData is AnyUiDialogueDataSelectReferableFromPool ddsrp + && e.Value is string valstr + && int.TryParse(valstr, out int i) + && i >= 0 && i < _domainNames.Count) + { + ShowDomain(_domainNames[i]); + } + } + + protected void OnSelectRow(DefinitionsPoolEntityBase de) + { + _selectedEntity = de; + StateHasChanged(); + } + + void DblHandler(MouseEventArgs e) + { + LeaveResult(true); + } + + public void LeaveResult(bool result) + { + if (DialogueData is AnyUiDialogueDataSelectReferableFromPool ddsrp + && _domainEntities != null + && _selectedEntity != null) + { + ddsrp.ResultItem = _selectedEntity; + ddsrp.ResultIndex = _domainEntities.IndexOf(_selectedEntity); + EventSession?.EndModal(true); + } + else + { + // no + EventSession?.EndModal(false); + } + } +} \ No newline at end of file