From 8f7961721d12a0b6ecfb5891365292bda4ace363 Mon Sep 17 00:00:00 2001 From: Michael Hoffmeister Date: Sat, 11 Nov 2023 18:24:07 +0100 Subject: [PATCH] * SMT value validation in 1st degree in UI --- src/AasxCore.Samm2_2_0/SammClasses.cs | 4 +- .../AdminShellCollections.cs | 21 +- .../MainWindow.CommandBindings.cs | 3 +- src/AasxPackageExplorer/MainWindow.xaml.cs | 25 +- .../PackageExplorer-MainWindow_and_Menues.svg | 2 +- src/AasxPackageExplorer/debug.MIHO.script | 6 +- .../options-debug.MIHO.json | 3 +- src/AasxPackageLogic/DispEditHelperBasics.cs | 16 +- .../DispEditHelperEntities.cs | 194 ++- .../DispEditHelperExtensions.cs | 1311 +++++++++++++---- src/AasxPackageLogic/DispEditHelperModules.cs | 219 ++- .../DispEditHelperSammModules.cs | 31 +- src/AasxPackageLogic/ExplorerMenuFactory.cs | 3 +- src/AasxPackageLogic/Options.cs | 6 +- .../IndexOfSignificantAasElements.cs | 2 +- src/AasxPackageLogic/VisualAasxElements.cs | 20 +- src/AasxWpfControlLibrary/AnyUiWpf.cs | 194 +-- .../DispEditAasxEntity.xaml.cs | 17 +- src/AnyUi/AnyUiBase.cs | 26 +- src/AnyUi/AnyUiSmallWidgetToolkit.cs | 2 +- .../Data/BlazorSession.CommandBindings.cs | 5 +- src/BlazorExplorer/Data/BlazorSession.cs | 13 +- .../Pages/AnyUiRenderElem.razor | 5 +- .../Pages/AnyUiRenderGrid.razor | 1 + src/BlazorExplorer/options-debug.MIHO.json | 3 +- 25 files changed, 1684 insertions(+), 448 deletions(-) diff --git a/src/AasxCore.Samm2_2_0/SammClasses.cs b/src/AasxCore.Samm2_2_0/SammClasses.cs index 9cc81fe5d..645146ff1 100644 --- a/src/AasxCore.Samm2_2_0/SammClasses.cs +++ b/src/AasxCore.Samm2_2_0/SammClasses.cs @@ -1659,9 +1659,7 @@ public void Add( if (t != null && _renderInfo.ContainsKey(t)) return _renderInfo[t]; return null; - } - - + } static Constants() { diff --git a/src/AasxCsharpLibrary/AdminShellCollections.cs b/src/AasxCsharpLibrary/AdminShellCollections.cs index 414f16168..282de6b44 100644 --- a/src/AasxCsharpLibrary/AdminShellCollections.cs +++ b/src/AasxCsharpLibrary/AdminShellCollections.cs @@ -8,6 +8,7 @@ This source code may use other Open Source software components (see LICENSE.txt) */ using System.Collections.Generic; +using System.Linq; namespace AdminShellNS { @@ -41,7 +42,7 @@ public void Add(K key, V value) public List this[K key] => dict[key]; - public IEnumerable> Keys + public IEnumerable> Values { get { @@ -49,7 +50,23 @@ public IEnumerable> Keys } } - public void Clear() => dict.Clear(); + public IEnumerable Keys + { + get + { + return dict.Keys; + } + } + + public void Clear() => dict.Clear(); + + public IEnumerable All(K key) + { + if (!dict.ContainsKey(key)) + yield break; + foreach (var x in dict[key]) + yield return x; + } } public class DoubleSidedDict diff --git a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs index c6bd8aced..4b3b11fdb 100644 --- a/src/AasxPackageExplorer/MainWindow.CommandBindings.cs +++ b/src/AasxPackageExplorer/MainWindow.CommandBindings.cs @@ -207,7 +207,8 @@ private async Task CommandBinding_GeneralDispatch( if (cmd == "editmenu" || cmd == "editkey" || cmd == "hintsmenu" || cmd == "hintskey" - || cmd == "showirimenu" || cmd == "showirikey") + || cmd == "showirimenu" || cmd == "showirikey" + || cmd == "checksmtelements") { // start ticket.StartExec(); diff --git a/src/AasxPackageExplorer/MainWindow.xaml.cs b/src/AasxPackageExplorer/MainWindow.xaml.cs index 3baec5908..f11553834 100644 --- a/src/AasxPackageExplorer/MainWindow.xaml.cs +++ b/src/AasxPackageExplorer/MainWindow.xaml.cs @@ -586,7 +586,7 @@ public void UiShowRepositories(bool visible) public void PrepareDispEditEntity( AdminShellPackageEnv package, ListOfVisualElementBasic entities, - bool editMode, bool hintMode, bool showIriMode, + bool editMode, bool hintMode, bool showIriMode, bool checkSmt, DispEditHighlight.HighlightFieldInfo hightlightField = null) { // determine some flags @@ -597,7 +597,7 @@ public void PrepareDispEditEntity( DynamicMenu.Menu.Clear(); var renderHints = DispEditEntityPanel.DisplayOrEditVisualAasxElement( PackageCentral, DisplayContext, - entities, editMode, hintMode, showIriMode, tiCds?.CdSortOrder, + entities, editMode, hintMode, showIriMode, checkSmt, tiCds?.CdSortOrder, flyoutProvider: this, appEventProvider: this, hightlightField: hightlightField, @@ -807,7 +807,8 @@ public void RedrawElementView(DispEditHighlight.HighlightFieldInfo hightlightFie MainMenu?.IsChecked("EditMenu") == true, MainMenu?.IsChecked("HintsMenu") == true, MainMenu?.IsChecked("ShowIriMenu") == true, - hightlightField: hightlightField); + MainMenu?.IsChecked("CheckSmtElements") == true, + hightlightField: hightlightField); } @@ -1065,9 +1066,10 @@ private async void Window_Loaded(object sender, RoutedEventArgs e) MainMenu?.SetChecked("AnimateElements", Options.Curr.AnimateElements); MainMenu?.SetChecked("ObserveEvents", Options.Curr.ObserveEvents); MainMenu?.SetChecked("CompressEvents", Options.Curr.CompressEvents); + MainMenu?.SetChecked("CheckSmtElements", Options.Curr.CheckSmtElements); - // the UI application might receive events from items in the package central - PackageCentral.ChangeEventHandler = (data) => + // the UI application might receive events from items in the package central + PackageCentral.ChangeEventHandler = (data) => { if (data.Reason == PackCntChangeEventReason.Exception) Log.Singleton.Info("PackageCentral events: " + data.Info); @@ -1487,6 +1489,11 @@ private async Task MainTimer_HandleLambdaAction(AnyUiLambdaActionBase lab) currentFlyoutControl.LambdaActionAvailable(lamprr); } + if (lab is AnyUiLambdaActionEntityPanelReRender larrep) + { + UiHandleReRenderAnyUiInEntityPanel("", larrep.Mode, larrep.UseInnerGrid, + updateElemsOnly: larrep.UpdateElemsOnly); + } } private async Task MainTimer_HandleEntityPanel() @@ -1599,9 +1606,10 @@ private async Task LoadFromFileRepository(PackageCon } private void UiHandleReRenderAnyUiInEntityPanel( - string pluginName, AnyUiRenderMode mode, bool useInnerGrid = false) + string pluginName, AnyUiRenderMode mode, bool useInnerGrid = false, + Dictionary updateElemsOnly = null) { - // A plugin asks to re-render an exisiting panel. + // A plugin asks to re-render an existing panel. // Can get this information? var renderedInfo = DispEditEntityPanel.GetLastRenderedRoot(); @@ -1632,7 +1640,8 @@ private void UiHandleReRenderAnyUiInEntityPanel( DispEditEntityPanel.RedisplayRenderedRoot( renderedPanel, mode: mode, - useInnerGrid: useInnerGrid); + useInnerGrid: useInnerGrid, + updateElemsOnly: updateElemsOnly); } else { diff --git a/src/AasxPackageExplorer/UML/PackageExplorer-MainWindow_and_Menues.svg b/src/AasxPackageExplorer/UML/PackageExplorer-MainWindow_and_Menues.svg index 837e05daa..5ce06cb35 100644 --- a/src/AasxPackageExplorer/UML/PackageExplorer-MainWindow_and_Menues.svg +++ b/src/AasxPackageExplorer/UML/PackageExplorer-MainWindow_and_Menues.svg @@ -1 +1 @@ -AasxPackageLogicAasxPackageExplorerMainWindowLogicPackageCentralBase class for abstract(non-UI) main windowhandling.MainWindowToolsSome tools (Andreas,security) used bymultiple menu functionsMainWindowDispatchCommandBinding_GeneralDispatch()Abstract menu items.Inner core of menu items.Will be wrapped in WPF, HTML, Toolkit.IMainWindowUiLoadPackageWithNew()An main window needs implement this to allowbusiness logic to trigger important statechanges visible to the user, e.g. loading.MainWindow.xaml.csMainWindowDispatch _logicMain window to theWPF application.Contains all gluelogic between libraryelements.MainWindow.CommandBindings.csCommandBinding_GeneralDispatch()Menu functions whichinvolve a lot of WPF.usespartial \ No newline at end of file +AasxPackageLogicAasxPackageExplorerMainWindowLogicPackageCentralBase class for abstract(non-UI) main windowhandling.MainWindowToolsSome tools (Andreas,security) used bymultiple menu functionsMainWindowDispatchCommandBinding_GeneralDispatch()Abstract menu items.Inner core of menu items.Will be wrapped in WPF, HTML, Toolkit.IMainWindowUiLoadPackageWithNew()An main window needs implement this to allowbusiness logic to trigger important statechanges visible to the user, e.g. loading.MainWindow.xaml.csMainWindowDispatch _logicMain window to theWPF application.Contains all gluelogic between libraryelements.MainWindow.CommandBindings.csCommandBinding_GeneralDispatch()Menu functions whichinvolve a lot of WPF.usespartial \ No newline at end of file diff --git a/src/AasxPackageExplorer/debug.MIHO.script b/src/AasxPackageExplorer/debug.MIHO.script index 55cc138a0..1948d00a9 100644 --- a/src/AasxPackageExplorer/debug.MIHO.script +++ b/src/AasxPackageExplorer/debug.MIHO.script @@ -7,13 +7,13 @@ // Tool("exportsmtasciidoc", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\new.zip", "ExportHtml", "true"); // Tool("Exit"); // Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\BatteryPass-spiel.ttl"); -Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); +// Tool("sammaspectimport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\Batch-MM-2_0_0.ttl"); Tool("editkey"); -Select("ConceptDescription", "First"); +// Select("ConceptDescription", "First"); // Select("ConceptDescription", "Next"); // Select("ConceptDescription", "Next"); // Select("ConceptDescription", "Next"); // Select("ConceptDescription", "Next"); // Select("ConceptDescription", "Next"); // Tool("sammaspectexport", "File", "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\out.ttl"); -Tool("submodelinstancefromsammaspect"); \ No newline at end of file +// Tool("submodelinstancefromsammaspect"); \ No newline at end of file diff --git a/src/AasxPackageExplorer/options-debug.MIHO.json b/src/AasxPackageExplorer/options-debug.MIHO.json index b5d770634..5ab72be9f 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.aasx", + "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\samm_spiel_empty-smt.aasx", "WindowLeft": 200, "WindowTop": -1, "WindowWidth": 900, @@ -63,6 +63,7 @@ "WorkDir": ".\\work", "ObserveEvents": true, "CompressEvents": true, + "CheckSmtElements": false, "DefaultStayConnected": true, "DefaultUpdatePeriod": 1000, "StayConnectOptions": "REST-QUEUE", // SIM diff --git a/src/AasxPackageLogic/DispEditHelperBasics.cs b/src/AasxPackageLogic/DispEditHelperBasics.cs index 54b047484..fd9892cbc 100644 --- a/src/AasxPackageLogic/DispEditHelperBasics.cs +++ b/src/AasxPackageLogic/DispEditHelperBasics.cs @@ -769,7 +769,7 @@ public void AddAction( public void AddKeyListLangStr( AnyUiStackPanel view, string key, List langStr, ModifyRepo repo = null, Aas.IReferable relatedReferable = null, - Action emitCustomEvent = null) where T : IAbstractLangString + Func emitCustomEvent = null) where T : IAbstractLangString { // sometimes needless to show if (repo == null && (langStr == null || langStr.Count < 1)) @@ -783,7 +783,10 @@ public void AddKeyListLangStr( // default if (emitCustomEvent == null) - emitCustomEvent = (rf) => { this.AddDiaryEntry(rf, new DiaryEntryStructChange()); }; + emitCustomEvent = (rf) => { + this.AddDiaryEntry(rf, new DiaryEntryStructChange()); + return new AnyUiLambdaActionNone(); + }; // Grid var g = new AnyUiGrid(); @@ -837,7 +840,6 @@ public void AddKeyListLangStr( langStr.Add("", ""); emitCustomEvent?.Invoke(relatedReferable); - return new AnyUiLambdaActionRedrawEntity(); }); } @@ -884,7 +886,9 @@ public void AddKeyListLangStr( (o) => { langStr[currentI].Language = o as string; - emitCustomEvent?.Invoke(relatedReferable); + var evt = emitCustomEvent?.Invoke(relatedReferable); + if (evt != null && !(evt is AnyUiLambdaActionNone)) + return evt; return new AnyUiLambdaActionNone(); }); // check here, if to hightlight @@ -909,7 +913,9 @@ public void AddKeyListLangStr( (o) => { langStr[currentI].Text = o as string; - emitCustomEvent?.Invoke(relatedReferable); + var evt = emitCustomEvent?.Invoke(relatedReferable); + if (evt != null && !(evt is AnyUiLambdaActionNone)) + return evt; return new AnyUiLambdaActionNone(); }); // check here, if to hightlight diff --git a/src/AasxPackageLogic/DispEditHelperEntities.cs b/src/AasxPackageLogic/DispEditHelperEntities.cs index 67e758744..51e42ffe7 100644 --- a/src/AasxPackageLogic/DispEditHelperEntities.cs +++ b/src/AasxPackageLogic/DispEditHelperEntities.cs @@ -1450,7 +1450,8 @@ public void DisplayOrEditAasEntityAas( } // Referable - this.DisplayOrEditEntityReferable(stack, + this.DisplayOrEditEntityReferable( + env, stack, parentContainer: null, referable: aas, indexPosition: 0); // Identifiable @@ -1575,8 +1576,8 @@ public void DisplayOrEditAasEntitySubmodelOrRef( PackageCentral.PackageCentral packages, Aas.Environment env, Aas.IAssetAdministrationShell aas, Aas.IReference smref, Aas.ISubmodel submodel, bool editMode, - AnyUiStackPanel stack, bool hintMode = false, - AasxMenu superMenu = null) + AnyUiStackPanel stack, bool hintMode = false, bool checkSmt = false, + AasxMenu superMenu = null) { // This panel renders first the SubmodelReference and then the Submodel, below if (smref != null) @@ -1841,7 +1842,9 @@ public void DisplayOrEditAasEntitySubmodelOrRef( ticketMenu: new AasxMenu() .AddAction("upgrade-qualifiers", "Upgrade qualifiers", "Upgrades particular qualifiers from V2.0 to V3.0 for selected element.") - .AddAction("remove-qualifiers", "Remove qualifiers", + .AddAction("SMT-qualifiers-convert", "Convert SMT qualifiers", + "Converts particular SMT qualifiers to SMT extension for selected element.") + .AddAction("remove-qualifiers", "Remove qualifiers", "Removes all qualifiers for selected element.") .AddAction("remove-extensions", "Remove extensions", "Removes all extensions for selected element."), @@ -1895,7 +1898,43 @@ public void DisplayOrEditAasEntitySubmodelOrRef( return new AnyUiLambdaActionRedrawAllElements(nextFocus: smref, isExpanded: true); } - if (buttonNdx == 1) + if (buttonNdx == 1) + { + // ask + if (ticket?.ScriptMode != true + && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( + "This operation will move data in particular Qualifiers to Extensions of " + + "the Submodel and all of its SubmodelElements. Do you want to proceed?", + "Convert SMT qualifiers to SMT extension", + AnyUiMessageBoxButton.YesNo, AnyUiMessageBoxImage.Warning)) + return new AnyUiLambdaActionNone(); + + // do + int anyChanges = 0; + Action lambdaConvert = (o) => { + if (AasSmtQualifiers.ConvertSmtQualifiersToExtension(o)) + anyChanges++; + }; + + lambdaConvert(submodel); + submodel.RecurseOnSubmodelElements(null, (o, parents, sme) => + { + // do + lambdaConvert(sme); + // recurse + return true; + }); + + // report + Log.Singleton.Info($"Convert SMT qualifiers to SMT extension: {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 == 2) { if (ticket?.ScriptMode != true && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( @@ -1923,7 +1962,7 @@ public void DisplayOrEditAasEntitySubmodelOrRef( return new AnyUiLambdaActionRedrawAllElements(nextFocus: smref, isExpanded: true); } - if (buttonNdx == 2) + if (buttonNdx == 3) { if (ticket?.ScriptMode != true && AnyUiMessageBoxResult.Yes != this.context.MessageBoxFlyoutShow( @@ -1954,17 +1993,23 @@ public void DisplayOrEditAasEntitySubmodelOrRef( return new AnyUiLambdaActionNone(); }); - } + // Check for cardinality + if (checkSmt) + DisplayOrEditEntityCheckValue(env, stack, _checkValueHandle, submodel); - if (submodel != null) + } + + if (submodel != null) { // Submodel this.AddGroup(stack, "Submodel", this.levelColors.MainSection); - // IReferable - this.DisplayOrEditEntityReferable(stack, - parentContainer: null, referable: submodel, indexPosition: 0); + // IReferable (part 1) + this.DisplayOrEditEntityReferable( + env, stack, + parentContainer: null, referable: submodel, indexPosition: 0, + hideExtensions: true); // Identifiable this.DisplayOrEditEntityIdentifiable( @@ -2057,8 +2102,14 @@ public void DisplayOrEditAasEntitySubmodelOrRef( relatedReferable: submodel, superMenu: superMenu); - } - } + // IReferable (part 2) + this.DisplayOrEditEntityReferableContinue( + env, stack, + parentContainer: null, referable: submodel, indexPosition: 0, + hideExtensions: true); + + } + } // // @@ -2108,7 +2159,7 @@ public void DisplayOrEditAasEntityConceptDescription( Action lambdaRf = (hideExtensions) => { this.DisplayOrEditEntityReferable( - stack, parentContainer: parentContainer, referable: cd, + env, stack, parentContainer: parentContainer, referable: cd, indexPosition: 0, hideExtensions: hideExtensions, injectToIdShort: new DispEditHelperModules.DispEditInjectAction( @@ -2303,9 +2354,9 @@ public void DisplayOrEditAasEntityConceptDescription( // experimental: SMT elements - Action lambdaSmtExt = () => + Action lambdaExtRecs = () => { - DisplayOrEditEntitySmtExtensions( + DisplayOrEditEntityExtensionRecords( env, stack, cd.Extensions, (v) => { cd.Extensions = v; }, relatedReferable: cd, superMenu: superMenu); @@ -2313,15 +2364,14 @@ public void DisplayOrEditAasEntityConceptDescription( // check if to display special order for SAMM, SMT var specialOrderSAMM_SMT = - DispEditHelperSammModules.CheckReferableForSammExtensionType(cd) != null - || DispEditHelperExtensions.CheckReferableForSmtExtensionType(cd) != null; + DispEditHelperSammModules.CheckReferableForSammExtensionType(cd) != null; if (specialOrderSAMM_SMT) { lambdaIdf(); lambdaRf(true); lambdaSammExt(); - lambdaSmtExt(); + lambdaExtRecs(); this.AddGroup(stack, "Continue Referable:", levelColors.MainSection); lambdaIsCaseOf(); @@ -2340,7 +2390,7 @@ public void DisplayOrEditAasEntityConceptDescription( lambdaIsCaseOf(); lambdaEDS(false); lambdaSammExt(); - lambdaSmtExt(); + lambdaExtRecs(); } } @@ -2598,7 +2648,7 @@ public void DisplayOrEditAasEntitySubmodelElement( PackageCentral.PackageCentral packages, Aas.Environment env, Aas.IReferable parentContainer, Aas.ISubmodelElement wrapper, Aas.ISubmodelElement sme, int indexPosition, bool editMode, ModifyRepo repo, AnyUiStackPanel stack, - bool hintMode = false, bool nestedCds = false, + bool hintMode = false, bool checkSmt = false, bool nestedCds = false, AasxMenu superMenu = null) { // @@ -2943,6 +2993,8 @@ public void DisplayOrEditAasEntitySubmodelElement( if (sme is Aas.Entity) listOfSME = (sme as Aas.Entity).Statements; + // adding of SME + DispSmeListAddNewHelper(env, stack, repo, key: "SubmodelElement:", listOfSME, @@ -2957,6 +3009,8 @@ public void DisplayOrEditAasEntitySubmodelElement( }, superMenu: superMenu); + // Copy + this.AddHintBubble( stack, hintMode, new[] { @@ -3006,7 +3060,11 @@ public void DisplayOrEditAasEntitySubmodelElement( return new AnyUiLambdaActionNone(); }); - } + + // Check for cardinality + if (checkSmt) + DisplayOrEditEntityCheckValue(env, stack, _checkValueHandle, sme); + } Aas.IConceptDescription jumpToCD = null; if (sme?.SemanticId != null && sme.SemanticId.Keys.Count > 0) @@ -3239,9 +3297,11 @@ public void DisplayOrEditAasEntitySubmodelElement( $"Submodel Element ({"" + sme?.GetSelfDescription().AasElementName})", this.levelColors.MainSection); - // IReferable - this.DisplayOrEditEntityReferable(stack, + // IReferable (part 1) + this.DisplayOrEditEntityReferable( + env, stack, parentContainer: parentContainer, referable: sme, indexPosition: indexPosition, + hideExtensions: true, injectToIdShort: new DispEditHelperModules.DispEditInjectAction( auxTitles: new[] { "Sync" }, auxToolTips: new[] { "Copy (if target is empty) idShort " + @@ -3297,11 +3357,17 @@ public void DisplayOrEditAasEntitySubmodelElement( this.DisplayOrEditEntityHasDataSpecificationReferences(stack, sme.EmbeddedDataSpecifications, (ds) => { sme.EmbeddedDataSpecifications = ds; }, relatedReferable: sme, superMenu: superMenu); - // - // ConceptDescription <- via semantic ID ?! - // + // IReferable (part 2) + this.DisplayOrEditEntityReferableContinue( + env, stack, + parentContainer: null, referable: sme, indexPosition: 0, + hideExtensions: true); + + // + // ConceptDescription <- via semantic ID ?! + // - if (sme.SemanticId != null && sme.SemanticId.Keys.Count > 0 && !nestedCds) + if (sme.SemanticId != null && sme.SemanticId.Keys.Count > 0 && !nestedCds) { var cd = env.FindConceptDescriptionByReference(sme.SemanticId); if (cd == null) @@ -3373,12 +3439,28 @@ public void DisplayOrEditAasEntitySubmodelElement( }); - AddKeyValueExRef( + // now: Value + AddKeyValueExRef( stack, "value", p, p.Value, null, repo, v => { + // primary update p.Value = v as string; this.AddDiaryEntry(p, new DiaryEntryUpdateValue()); + + // kick off value check? + if (_checkValueHandle != null && checkSmt) + { + DisplayOrEditEntityCheckValue(env, stack, _checkValueHandle, sme, update: true); + return new AnyUiLambdaActionEntityPanelReRender( + mode: AnyUiRenderMode.StatusToUi, + updateElemsOnly: new Dictionary() { + { _checkValueHandle.Border, true }, + { _checkValueHandle.TextBlock, true } + }); + } + + // normal return new AnyUiLambdaActionNone(); }, auxButtonTitles: new[] { "\u2261" }, @@ -3410,7 +3492,10 @@ public void DisplayOrEditAasEntitySubmodelElement( return new AnyUiLambdaActionNone(); }); - this.AddHintBubble( + if (checkSmt) + DisplayOrEditEntityCheckValue(env, stack, _checkValueHandle, sme); + + this.AddHintBubble( stack, hintMode, new[] { new HintCheck( @@ -3462,7 +3547,6 @@ public void DisplayOrEditAasEntitySubmodelElement( this.AddGroup(stack, "MultiLanguageProperty", this.levelColors.MainSection); // Value - this.AddHintBubble( stack, hintMode, new[] { @@ -3479,15 +3563,39 @@ public void DisplayOrEditAasEntitySubmodelElement( stack, repo, mlp.Value, "value:", "Create data element!", v => { - mlp.Value = new List(); + mlp.Value = new List(); this.AddDiaryEntry(mlp, new DiaryEntryUpdateValue()); return new AnyUiLambdaActionRedrawEntity(); })) - - this.AddKeyListLangStr( - stack, "value", mlp.Value, repo, + { + // edit + this.AddKeyListLangStr( + stack, "value", mlp.Value, repo, relatedReferable: mlp, - emitCustomEvent: (rf) => { this.AddDiaryEntry(rf, new DiaryEntryUpdateValue()); }); + emitCustomEvent: (rf) => { + // primary + this.AddDiaryEntry(rf, new DiaryEntryUpdateValue()); + + // kick off value check? + if (_checkValueHandle != null && checkSmt) + { + DisplayOrEditEntityCheckValue(env, stack, _checkValueHandle, sme, update: true); + return new AnyUiLambdaActionEntityPanelReRender( + mode: AnyUiRenderMode.StatusToUi, + updateElemsOnly: new Dictionary() { + { _checkValueHandle.Border, true }, + { _checkValueHandle.TextBlock, true } + }); + } + + // normal + return new AnyUiLambdaActionNone(); + }); + + // provide check + if (checkSmt) + DisplayOrEditEntityCheckValue(env, stack, _checkValueHandle, sme); + } // ValueId @@ -4299,8 +4407,8 @@ public bool DisplayOrEditCommonEntity( PackageCentral.PackageCentral packages, AnyUiStackPanel stack, AasxMenu superMenu, - bool editMode, bool hintMode, - VisualElementEnvironmentItem.ConceptDescSortOrder? cdSortOrder, + bool editMode, bool hintMode, bool checkSmt, + VisualElementEnvironmentItem.ConceptDescSortOrder? cdSortOrder, VisualElementGeneric entity) { if (entity is VisualElementEnvironmentItem veei) @@ -4332,22 +4440,22 @@ public bool DisplayOrEditCommonEntity( // edit DisplayOrEditAasEntitySubmodelOrRef( packages, vesmref.theEnv, aas, vesmref.theSubmodelRef, vesmref.theSubmodel, editMode, stack, - hintMode: hintMode, - superMenu: superMenu); + hintMode: hintMode, checkSmt: checkSmt, + superMenu: superMenu); } else if (entity is VisualElementSubmodel vesm && vesm.theSubmodel != null) { DisplayOrEditAasEntitySubmodelOrRef( packages, vesm.theEnv, null, null, vesm.theSubmodel, editMode, stack, - hintMode: hintMode, - superMenu: superMenu); + hintMode: hintMode, checkSmt: checkSmt, + superMenu: superMenu); } else if (entity is VisualElementSubmodelElement vesme) { DisplayOrEditAasEntitySubmodelElement( packages, vesme.theEnv, vesme.theContainer, vesme.theWrapper, vesme.theWrapper, vesme.IndexPosition, editMode, - repo, stack, hintMode: hintMode, superMenu: superMenu, + repo, stack, hintMode: hintMode, checkSmt: checkSmt, superMenu: superMenu, nestedCds: cdSortOrder.HasValue && cdSortOrder.Value == VisualElementEnvironmentItem.ConceptDescSortOrder.BySme); } diff --git a/src/AasxPackageLogic/DispEditHelperExtensions.cs b/src/AasxPackageLogic/DispEditHelperExtensions.cs index 44d6328f5..b97ca8185 100644 --- a/src/AasxPackageLogic/DispEditHelperExtensions.cs +++ b/src/AasxPackageLogic/DispEditHelperExtensions.cs @@ -8,46 +8,29 @@ This source code may use other Open Source software components (see LICENSE.txt) */ using AasCore.Samm2_2_0; -using AasxAmlImExport; -using AasxCompatibilityModels; using AasxIntegrationBase; using AdminShellNS; +using Aml.Engine.CAEX; using AnyUi; using Extensions; +using Microsoft.VisualBasic.ApplicationServices; using Newtonsoft.Json; using System; +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.Windows.Media; +using System.Runtime.Serialization; +using System.Text.RegularExpressions; using System.Xaml; -using VDS.RDF.Parsing; -using VDS.RDF; -using static System.Windows.Forms.VisualStyles.VisualStyleElement.Window; +using static AasxPackageLogic.AasSmtQualifiers; using Aas = AasCore.Aas3_0; using Samm = AasCore.Samm2_2_0; -using System.Text.RegularExpressions; -using System.Runtime.Intrinsics.X86; -using Lucene.Net.Tartarus.Snowball.Ext; -using Lucene.Net.Util; -using System.Runtime.Serialization; -using J2N.Text; -using Lucene.Net.Codecs; -using VDS.RDF.Writing; -using AngleSharp.Text; -using System.Web.Services.Description; -using static AasxPackageLogic.DispEditHelperBasics; -using System.Collections; -using static Lucene.Net.Documents.Field; -using VDS.RDF.Query.Algebra; -using Microsoft.VisualBasic.ApplicationServices; -using static Lucene.Net.Queries.Function.ValueSources.MultiFunction; -using System.Windows.Controls; -using System.DirectoryServices; -using AngleSharp.Dom; -using Aml.Engine.CAEX; + namespace AasxPackageLogic { @@ -55,12 +38,12 @@ namespace AasxPackageLogic /// This class extends the AAS meta model editing function for those related to /// SAMM (Semantic Aspect Meta Model) elements. /// - public class DispEditHelperExtensions : DispEditHelperModules + public class DispEditHelperExtensions : DispEditHelperMiniModules { - public static void SammExtensionHelperUpdateJson(Aas.IExtension se, Type smtType, SmtModelElement smtInst) + public static void GeneralExtensionHelperUpdateJson(Aas.IExtension se, Type recType, ExtensionRecordBase recInst) { // trivial - if (se == null || smtType == null || smtInst == null) + if (se == null || recType == null || recInst == null) return; // do a full fledged, carefull serialization @@ -78,7 +61,7 @@ public static void SammExtensionHelperUpdateJson(Aas.IExtension se, Type smtType settings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); //settings.Converters.Add(new AdminShellConverters.AdaptiveAasIClassConverter( // AdminShellConverters.AdaptiveAasIClassConverter.ConversionMode.AasCore)); - json = JsonConvert.SerializeObject(smtInst, smtType, settings); + json = JsonConvert.SerializeObject(recInst, recType, settings); } catch (Exception ex) { @@ -90,10 +73,42 @@ public static void SammExtensionHelperUpdateJson(Aas.IExtension se, Type smtType se.ValueType = DataTypeDefXsd.String; } + public static bool GeneralExtensionHelperAddJsonExtension( + Aas.IHasExtensions ihe, Type recType, ExtensionRecordBase recInst) + { + // acces + if (ihe == null || recType == null || recInst == null + || !(recInst is IExtensionSelfDescription ssd)) + return false; + + // create extension + var newExt = new Aas.Extension( + name: ssd.GetSelfName(), + semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, + (new[] { + new Aas.Key(KeyTypes.GlobalReference, + "" + ssd.GetSelfUri()) + }) + .Cast().ToList()), + value: ""); + + // add JSON + GeneralExtensionHelperUpdateJson(newExt, recType, recInst); + + // add to extension + ihe.Extensions = ihe.Extensions ?? new List(); + ihe.Extensions.Add(newExt); + + // ok + return true; + } + /// /// Shall provide rather quick access to information .. /// - public static SmtModelElement CheckReferableForSmtExtensionType(Aas.IReferable rf) + public static ExtensionRecordBase CheckReferableForExtensionRecordType( + Aas.IReferable rf, + Type[] typesAllowed = null) { // access if (rf?.Extensions == null) @@ -103,7 +118,7 @@ public static SmtModelElement CheckReferableForSmtExtensionType(Aas.IReferable r foreach (var se in rf.Extensions) if (se.SemanticId?.IsValid() == true && se.SemanticId.Keys.Count == 1) { - var t = SmtModelElements.GetTypeInstFromUri(se.SemanticId.Keys[0].Value); + var t = ExtensionRecords.GetTypeInstFromUri(se.SemanticId.Keys[0].Value); if (t != null) return t; } @@ -112,7 +127,52 @@ public static SmtModelElement CheckReferableForSmtExtensionType(Aas.IReferable r return null; } - public static IEnumerable CheckReferableForSammElements(Aas.IReferable rf) + public static ExtensionRecordBase CheckReferableForSingleExtensionRecord( + Aas.IExtension se, + bool allowCreateOnNull = false) + { + // access + if (se == null || se.SemanticId?.IsValid() != true + || se.SemanticId.Keys.Count < 1) + return null; + + // get type + var recTypeInst = ExtensionRecords.GetTypeInstFromUri(se.SemanticId.Keys[0].Value); + if (recTypeInst == null) + return null; + + // get instance data + ExtensionRecordBase recInst = null; + + // try to de-serializan extension value + try + { + if (se.Value != null) + recInst = JsonConvert.DeserializeObject(se.Value, recTypeInst.GetType()) as ExtensionRecordBase; + } + catch (Exception ex) + { + LogInternally.That.SilentlyIgnoredError(ex); + recInst = null; + } + + // if not, may create new + if (recInst == null && allowCreateOnNull) + { + recInst = Activator.CreateInstance(recTypeInst.GetType(), new object[] { }) as ExtensionRecordBase; + } + + // no? + if (recInst == null) + return null; + + // give back + return recInst; + } + + public static IEnumerable CheckReferableForExtensionRecords( + Aas.IReferable rf, + Type[] typesAllowed = null) { // access if (rf?.Extensions == null) @@ -120,49 +180,404 @@ public static IEnumerable CheckReferableForSammElements(Aas.IRe // find any? foreach (var se in rf.Extensions) - if (se.SemanticId?.IsValid() == true && se.SemanticId.Keys.Count == 1) + { + var rec = CheckReferableForSingleExtensionRecord(se); + + if (typesAllowed != null) { - // get type - var smtTypeInst = SmtModelElements.GetTypeInstFromUri(se.SemanticId.Keys[0].Value); - if (smtTypeInst == null) + var found = false; + foreach (var ta in typesAllowed) + if (ta == rec.GetType()) + found = true; + if (!found) continue; + } - // get instance data - SmtModelElement sammInst = null; + if (rec != null) + yield return rec; + } + } + + public static IEnumerable CheckReferableForExtensionRecords( + Aas.IReferable rf) where T : class + { + foreach (var x in CheckReferableForExtensionRecords(rf, new[] { typeof(T) })) + yield return x as T; + } + + protected void ExtensionHelperAddScalarField + (AnyUiStackPanel stack, + PropertyInfo pii, + ExtensionRecordBase recInst, + Action setRecInst, + bool isNullable, + Func toStringRepr, + Func fromStrRepr) + { + // access + if (stack == null || pii == null || recInst == null) + return; - // try to de-serializa extension value - try + // 1 line + AddKeyValueExRef( + stack, "" + pii.Name, recInst, + value: "" + toStringRepr?.Invoke(pii.GetValue(recInst)), + null, repo, + setValue: (v) => + { + if (isNullable && (v == null || ((string)v).Trim().Length < 1)) + pii.SetValue(recInst, null); + else { - if (se.Value != null) - sammInst = JsonConvert.DeserializeObject(se.Value, smtTypeInst.GetType()) as SmtModelElement; + var result = fromStrRepr?.Invoke((string)v); + if (result != null) + pii.SetValue(recInst, result); } - catch (Exception ex) + setRecInst?.Invoke(recInst); + return new AnyUiLambdaActionNone(); + }, + maxLines: 1); + } + + public void ExtensionHelperAddEditFieldsByReflection( + Aas.Environment env, + AnyUiStackPanel stack, + ExtensionRecordBase recInst, + Aas.IReferable relatedReferable, + Action setValue) + { + // access + if (env == null || stack == null || recInst == null) + return; + + // visually ease + this.AddVerticalSpace(stack); + + // okay, try to build up a edit field by reflection + var propInfo = recInst.GetType().GetProperties(); + for (int pi = 0; pi < propInfo.Length; pi++) + { + // access + var pii = propInfo[pi]; + + // some type investigation + var propType = pii.PropertyType; + var underlyingType = Nullable.GetUnderlyingType(propType); + + // make hint lambda + Action hintLambda = (hint) => + { + var hintAttr = pii.GetCustomAttribute(); + if (hintAttr == null) + return; + this.AddHintBubble(stack, hintMode, new[] { + new HintCheck( + () => hint, + text: hintAttr.HintText, + severityLevel: HintCheck.Severity.Notice) + }); + }; + + // List of LangString + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + // space + this.AddVerticalSpace(stack); + + // get data + var lls = (List)pii.GetValue(recInst); + + // hint? + hintLambda(lls == null || lls.Count < 1); + + // handle null + Action> lambdaSetValue = (v) => + { + var back = v?.Select((ls) => new Aas.LangStringTextType(ls.Language, ls.Text)).ToList(); + pii.SetValue(recInst, back); + setValue?.Invoke(recInst); + }; + + if (this.SafeguardAccess(stack, repo, lls, "" + pii.Name + ":", + "Create data element!", + v => + { + lambdaSetValue(new List()); + return new AnyUiLambdaActionRedrawEntity(); + })) { - LogInternally.That.SilentlyIgnoredError(ex); - sammInst = null; + // get values + var forth = lls?.Select( + (ls) => (new Aas.LangStringTextType(ls.Language, ls.Text)) + as Aas.ILangStringTextType).ToList(); + + // edit fields + AddKeyListLangStr( + stack, "" + pii.Name, forth, repo, relatedReferable, + emitCustomEvent: (rf) => + { + lambdaSetValue(forth); + return new AnyUiLambdaActionNone(); + }); } + } - if (sammInst == null) - continue; + // List of string? + if (pii.PropertyType.IsAssignableTo(typeof(List))) + { + this.AddVerticalSpace(stack); + + var ls = (List)pii.GetValue(recInst); + + // hint? + hintLambda(ls == null || ls.Count < 1); + + if (this.SafeguardAccess(stack, repo, ls, "" + pii.Name + ":", + "Create data element!", + v => + { + pii.SetValue(recInst, (new List())); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + })) + { + + var sg = this.AddSubGrid(stack, "" + pii.Name + ":", + rows: 1 + ls.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(5, 0, 5, 0), + content: "Add blank"), + (v) => + { + ls.Add(""); + pii.SetValue(recInst, ls); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + + for (int lsi = 0; lsi < ls.Count; lsi++) + { + var theLsi = lsi; + var tb = AnyUiUIElement.RegisterControl( + AddSmallTextBoxTo(sg, 1 + lsi, 0, + text: ls[lsi], + margin: new AnyUiThickness(2, 2, 2, 2)), + (v) => + { + ls[theLsi] = (string)v; + pii.SetValue(recInst, ls); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + }); - // give back - yield return sammInst; + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(sg, 1 + lsi, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "-"), + (v) => + { + ls.RemoveAt(theLsi); + pii.SetValue(recInst, ls); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + }); + } + } } - } - /// - /// Shall provide rather quick access to information .. - /// - /// Null, if not a SAMM model element - public static string CheckReferableForSammExtensionTypeName(Type sammType) - { - return Samm.SammIdSets.GetAnyNameFromSammType(sammType); + // single string? + if (pii.PropertyType.IsAssignableTo(typeof(string))) + { + var isMultiLineAttr = pii.GetCustomAttribute(); + + // value and hint? + var strVal = (string)pii.GetValue(recInst); + hintLambda(strVal == null || strVal.Length < 1); + + Func setValueLambda = (v) => + { + pii.SetValue(recInst, v); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionNone(); + }; + + if (isMultiLineAttr == null) + { + // 1 line + AddKeyValueExRef( + stack, "" + pii.Name, recInst, strVal, null, repo, + setValue: setValueLambda); + } + else + { + // makes sense to have a bit vertical space + AddVerticalSpace(stack); + + // multi line + AddKeyValueExRef( + stack, "" + pii.Name, recInst, (string)pii.GetValue(recInst), null, repo, + setValue: setValueLambda, + limitToOneRowForNoEdit: true, + maxLines: isMultiLineAttr.MaxLines.Value, + auxButtonTitles: new[] { "\u2261" }, + auxButtonToolTips: new[] { "Edit in multiline editor" }, + auxButtonLambda: (buttonNdx) => + { + if (buttonNdx == 0) + { + var uc = new AnyUiDialogueDataTextEditor( + caption: $"Edit " + pii.Name, + mimeType: System.Net.Mime.MediaTypeNames.Text.Plain, + text: (string)pii.GetValue(recInst)); + if (this.context.StartFlyoverModal(uc)) + { + pii.SetValue(recInst, uc.Text); + setValue?.Invoke(recInst); + return new AnyUiLambdaActionRedrawEntity(); + } + } + 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?))) + ExtensionHelperAddScalarField( + stack, pii, recInst, setValue, + isNullable: true, + toStringRepr: (o) => + { + var val = (uint?)o; + return (val.HasValue) ? val.Value.ToString() : null; + }, + fromStrRepr: (s) => { if (uint.TryParse(s, out var res)) return res; else return null; }); + + if (pii.PropertyType.IsAssignableTo(typeof(int?))) + ExtensionHelperAddScalarField( + stack, pii, recInst, setValue, + isNullable: true, + toStringRepr: (o) => + { + var val = (int?)o; + return (val.HasValue) ? val.Value.ToString() : null; + }, + fromStrRepr: (s) => { if (int.TryParse(s, out var res)) return res; else return null; }); + + if (pii.PropertyType.IsAssignableTo(typeof(double?))) + ExtensionHelperAddScalarField( + stack, pii, recInst, setValue, + isNullable: true, + toStringRepr: (o) => + { + var val = (double?)o; + return (val.HasValue) ? val.Value.ToString("G16", CultureInfo.InvariantCulture) : null; + }, + fromStrRepr: (s) => { + if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var res)) + return res; else return null; }); + } + + if (pii.PropertyType.IsAssignableTo(typeof(uint))) + ExtensionHelperAddScalarField( + stack, pii, recInst, setValue, + isNullable: false, + toStringRepr: (o) => ((uint)o).ToString(), + fromStrRepr: (s) => { if (uint.TryParse(s, out var res)) return res; else return null; }); + + if (pii.PropertyType.IsAssignableTo(typeof(int))) + ExtensionHelperAddScalarField( + stack, pii, recInst, setValue, + isNullable: false, + toStringRepr: (o) => ((int)o).ToString(), + fromStrRepr: (s) => { if (int.TryParse(s, out var res)) return res; else return null; }); + + if (pii.PropertyType.IsAssignableTo(typeof(double))) + ExtensionHelperAddScalarField( + stack, pii, recInst, setValue, + isNullable: false, + toStringRepr: (o) => ((double)o).ToString("G16", CultureInfo.InvariantCulture), + fromStrRepr: (s) => { + if (double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var res)) + return res; else return null; }); + + // nullable enum? + var typeForEnum = propType; + if (underlyingType != null && underlyingType.IsEnum) + typeForEnum = underlyingType; + if (typeForEnum.IsEnum) + { + // a little space + 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; + } + + // add a container + 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[] { "*", "#" }); + + // 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) => + { + if (cb.SelectedIndex.HasValue + && cb.SelectedIndex.Value >= 0 + && cb.SelectedIndex.Value < eMems.Count) + { + pii.SetValue(recInst, eMems[cb.SelectedIndex.Value].MemberInstance); + setValue?.Invoke(recInst); + } + return new AnyUiLambdaActionNone(); + }); + } + } } - public void DisplayOrEditEntitySmtExtensions( + public void DisplayOrEditEntityExtensionRecords( Aas.Environment env, AnyUiStackPanel stack, - List smtExtension, + List extension, Action> setOutput, string[] addPresetNames = null, List[] addPresetKeyLists = null, Aas.IReferable relatedReferable = null, @@ -173,25 +588,21 @@ public void DisplayOrEditEntitySmtExtensions( return; // members - this.AddGroup(stack, "SMT extensions \u00ab experimental \u00bb :", levelColors.MainSection); + this.AddGroup(stack, "Known extensions \u00ab experimental \u00bb :", levelColors.MainSection); this.AddHintBubble( stack, hintMode, new[] { new HintCheck( - () => { return smtExtension == null || - smtExtension.Count < 1; }, + () => { return extension == null || + extension.Count < 1; }, "For modelling Submodel template specifications (SMT), a set of particular attributes " + "to the elements of SMTs are specified. These attributes can be added as specific " + "Qualifiers or via adding an extension as a whole.", breakIfTrue: true, severityLevel: HintCheck.Severity.Notice), - new HintCheck( - () => { return smtExtension.Where(p => Samm.Util.HasSammSemanticId(p)).Count() > 1; }, - "Only one SMT extension is allowed per element.", - breakIfTrue: true), }); if (this.SafeguardAccess( - stack, this.repo, smtExtension, "SMT extensions:", "Create data element!", + stack, this.repo, extension, "Known extensions:", "Create data element!", v => { setOutput?.Invoke(new List()); @@ -203,11 +614,11 @@ public void DisplayOrEditEntitySmtExtensions( { // let the user control the number of references this.AddActionPanel( - stack, "Spec. records:", repo: repo, + stack, "Known extension:", repo: repo, superMenu: superMenu, ticketMenu: new AasxMenu() - .AddAction("add-smt-attributes", "Add attribute set", - "Add the attribute set as a whole.") + .AddAction("add-smt-attributes", "SMT attributes", + "Add attributes for Submodel template specifications.") .AddAction("delete-last", "Delete last extension", "Deletes last extension."), ticketAction: (buttonNdx, ticket) => @@ -215,10 +626,10 @@ public void DisplayOrEditEntitySmtExtensions( if (buttonNdx == 0) { // new - var newSet = new SmtAttributeSet(); + var newSet = new SmtAttributeRecord(); // now add - smtExtension.Add( + extension.Add( new Aas.Extension( name: newSet.GetSelfName(), semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, @@ -233,8 +644,8 @@ public void DisplayOrEditEntitySmtExtensions( // remove if (buttonNdx == 1) { - if (smtExtension.Count > 0) - smtExtension.RemoveAt(smtExtension.Count - 1); + if (extension.Count > 0) + extension.RemoveAt(extension.Count - 1); else setOutput?.Invoke(null); } @@ -245,120 +656,79 @@ public void DisplayOrEditEntitySmtExtensions( } // now use the normal mechanism to deal with editMode or not .. - if (smtExtension != null && smtExtension.Count > 0) + for (int exti = 0; exti < extension.Count; exti++) { - var numSammExt = 0; + // get + var se = extension[exti]; - for (int i = 0; i < smtExtension.Count; i++) - { - // get type - var se = smtExtension[i]; - var idSetType = Samm.SammIdSets.GetAnyIdSetTypeFromUrn(Samm.Util.GetSammUrn(se)); - if (idSetType?.Item1 == null || idSetType.Item2 == null) - continue; - var sammType = idSetType.Item2; - var idSet = idSetType.Item1; - - // remeber as detected .. (for later dialogs described above!) - if (detectedIdSet == null) - detectedIdSet = idSet; - - // more then one? - this.AddHintBubble( - stack, hintMode, - new[] { - new HintCheck( - () => numSammExt > 0, - "Only one SAMM extension per ConceptDescription allowed!", - breakIfTrue: true)}); - - // indicate - numSammExt++; - - AnyUiFrameworkElement iconElem = null; - var ri = Samm.Constants.GetRenderInfo(sammType); - if (ri != null) + // get instance data + ExtensionRecordBase recInst = CheckReferableForSingleExtensionRecord( + se, allowCreateOnNull: true); + if (!(recInst is IExtensionSelfDescription esd)) + continue; + + // icon element? + AnyUiFrameworkElement iconElem = null; + var ri = (recInst as IExtensionSelfDescription)?.GetRenderInfo(); + if (ri != null) + iconElem = new AnyUiBorder() { - iconElem = new AnyUiBorder() + Background = new AnyUiBrush(ri.Background), + BorderBrush = new AnyUiBrush(ri.Foreground), + BorderThickness = new AnyUiThickness(2.0f), + CornerRadius = 2.0, + MinHeight = 50, + MinWidth = 50, + Child = new AnyUiTextBlock() { - Background = new AnyUiBrush(ri.Background), - BorderBrush = new AnyUiBrush(ri.Foreground), - BorderThickness = new AnyUiThickness(2.0f), - MinHeight = 50, - MinWidth = 50, - Child = new AnyUiTextBlock() - { - Text = "" + ri.Abbreviation, - HorizontalAlignment = AnyUiHorizontalAlignment.Center, - VerticalAlignment = AnyUiVerticalAlignment.Center, - Foreground = new AnyUiBrush(ri.Foreground), - Background = AnyUi.AnyUiBrushes.Transparent, - FontSize = 2.0, - FontWeight = AnyUiFontWeight.Bold - }, + Text = "" + ri.Abbreviation, HorizontalAlignment = AnyUiHorizontalAlignment.Center, VerticalAlignment = AnyUiVerticalAlignment.Center, - Margin = new AnyUiThickness(5, 0, 10, 0), - SkipForTarget = AnyUiTargetPlatform.Browser - }; - } + Foreground = new AnyUiBrush(ri.Foreground), + Background = AnyUi.AnyUiBrushes.Transparent, + FontSize = 2.0, + FontWeight = AnyUiFontWeight.Bold + }, + HorizontalAlignment = AnyUiHorizontalAlignment.Center, + VerticalAlignment = AnyUiVerticalAlignment.Center, + Margin = new AnyUiThickness(5, 0, 10, 0), + SkipForTarget = AnyUiTargetPlatform.Browser + }; - this.AddGroup(stack, $"SAMM extension [{i + 1}]: {sammType.Name}", - levelColors.SubSection.Bg, levelColors.SubSection.Fg, - iconElement: iconElem); + // make visual group + this.AddGroup(stack, $"Known extension [{exti + 1}]: {esd.GetSelfName()}", + levelColors.SubSection.Bg, levelColors.SubSection.Fg, iconElement: iconElem); - // get instance data - Samm.ModelElement sammInst = null; - if (false) + // edit + ExtensionHelperAddEditFieldsByReflection( + env, stack, + recInst: recInst, + relatedReferable: relatedReferable, + setValue: (si) => { - // Note: right now, create fresh instance - sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; - if (sammInst == null) - { - stack.Add(new AnyUiLabel() { Content = "(unable to create instance data)" }); - continue; - } - } - else - { - // try to de-serializa extension value - try - { - if (se.Value != null) - sammInst = JsonConvert.DeserializeObject(se.Value, sammType) as Samm.ModelElement; - } - catch (Exception ex) - { - LogInternally.That.SilentlyIgnoredError(ex); - sammInst = null; - } - - if (sammInst == null) - { - sammInst = Activator.CreateInstance(sammType, new object[] { }) as Samm.ModelElement; - } - } - - SammExtensionHelperAddCompleteModelElement( - env, idSet, stack, - sammInst: sammInst, - relatedReferable: relatedReferable, - setValue: (si) => - { - SammExtensionHelperUpdateJson(se, si.GetType(), si); - }); - - } + GeneralExtensionHelperUpdateJson(se, si.GetType(), si); + }); } } } } + /// + /// Holds information, how model element types should be rendered on the screen. + /// + public class ExtensionRecordRenderInfo + { + public string DisplayName = ""; + public string Abbreviation = ""; + public uint Foreground = 0x00000000; + public uint Background = 0x00000000; + } + /// /// Shall be implemented by all non-abstract model elements /// - public interface ISmtSelfDescription + public interface IExtensionSelfDescription { /// /// get short name, which can als be used to distinguish elements. @@ -369,18 +739,54 @@ public interface ISmtSelfDescription /// Get URN of this element class. /// string GetSelfUri(); + + /// + /// Return information to render elements visually on screen. + /// + /// + ExtensionRecordRenderInfo GetRenderInfo(); } /// - /// This class provides generators for Qualifiers, Extension etc. - /// in order to express preset-based information e.g. Cardinality + /// This class provides handles specific qualifiers, extensions + /// for Submodel templates /// - public static class AasPresetHelper + public static class AasSmtQualifiers { /// /// Semantically different, but factually equal to FormMultiplicity /// - public enum SmtCardinality { ZeroToOne = 0, One, ZeroToMany, OneToMany }; + public enum SmtCardinality + { + [EnumMember(Value = "ZeroToOne")] + [EnumMemberDisplay("ZeroToOne [0..1]")] + ZeroToOne = 0, + + [EnumMember(Value = "One")] + [EnumMemberDisplay("One [1]")] + One, + + [EnumMember(Value = "ZeroToMany")] + [EnumMemberDisplay("ZeroToMany [0..*]")] + ZeroToMany, + + [EnumMember(Value = "OneToMany")] + [EnumMemberDisplay("OneToMany [1..*]")] + OneToMany + }; + + /// + /// Specifies the user access mode for SubmodelElement instance.When a Submodel is + /// received from another party, if set to Read/Only, then the user shall not change the value. + /// + public enum AccessMode + { + [EnumMember(Value = "ReadWrite")] + ReadWrite, + + [EnumMember(Value = "ReadOnly")] + ReadOnly + }; public static Aas.IQualifier CreateQualifierSmtCardinality(SmtCardinality card) { @@ -465,128 +871,541 @@ public static Aas.IQualifier CreateQualifierSmtRequiredLang(string reqLang) }).ToList()), value: "" + reqLang); } + + public static Aas.IQualifier CreateQualifierSmtAccessMode(AccessMode mode) + { + return new Aas.Qualifier( + type: "SMT/AccessMode", + valueType: DataTypeDefXsd.String, + kind: QualifierKind.TemplateQualifier, + semanticId: new Aas.Reference(ReferenceTypes.ExternalReference, + (new Aas.IKey[] { + new Aas.Key(KeyTypes.GlobalReference, + "https://admin-shell.io/SubmodelTemplates/AccessMode/1/0") + }).ToList()), + value: "" + mode); + } + + public static Aas.IQualifier[] AllSmtQualifiers = + { + CreateQualifierSmtCardinality(SmtCardinality.One), + CreateQualifierSmtAllowedValue(""), + CreateQualifierSmtExampleValue(""), + CreateQualifierSmtDefaultValue(""), + CreateQualifierSmtEitherOr(""), + CreateQualifierSmtRequiredLang(""), + CreateQualifierSmtAccessMode(AccessMode.ReadWrite) + }; + + /// + /// Find either type or semanticId and returns the link + /// to a STATIC IQualifier (not to be changed!). + /// + public static Aas.IQualifier FindQualifierTypeInst( + string type, Aas.IReference semanticId, bool relaxed = true) + { + // at best: tries to find semanticId + Aas.IQualifier res = null; + foreach (var qti in AllSmtQualifiers) + if (semanticId?.IsValid() == true && semanticId.Matches(qti.SemanticId, MatchMode.Relaxed)) + res = qti; + + // do a more sloppy name comparison? + if (relaxed && res == null && type?.HasContent() == true) + { + // some adoptions ahead + type = type.Trim().ToLower(); + if (type == "cardinality") + type = "smt/cardinality"; + if (type == "multiplicity") + type = "smt/cardinality"; + + // now try to find + foreach (var qti in AllSmtQualifiers) + if (qti.Type?.HasContent() == true + && qti.Type.Trim().ToLower() == type) + res = qti; + } + + // okay? + return res; + } + + public static SmtAttributeRecord FindSmtQualifiers(Aas.IReferable rf, bool removeQualifers = false) + { + // acesses + if (!(rf is Aas.IQualifiable iqf)) + return null; + + // already done? + if (iqf.Qualifiers == null || iqf.Qualifiers.Count() < 1) + return null; + + // result + SmtAttributeRecord rec = null; + + // try convert + for (int qi = 0; qi < iqf.Qualifiers.Count(); qi++) + { + // find + var qf = iqf.Qualifiers[qi]; + if (qf == null) + continue; + var qti = FindQualifierTypeInst(qf.Type, qf.SemanticId); + if (qti == null) + continue; + + // to convert + rec = rec ?? new SmtAttributeRecord(); + + if (qti.Type == "SMT/Cardinality") + rec.Cardinality = (SmtCardinality) + EnumHelper.GetEnumMemberFromValueString(qf.Value); + + if (qti.Type == "SMT/EitherOr") + rec.EitherOr = qf.Value; + + if (qti.Type == "SMT/InitialValue") + rec.InitialValue = qf.Value; + + if (qti.Type == "SMT/DefaultValue") + rec.DefaultValue = qf.Value; + + if (qti.Type == "SMT/ExampleValue") + rec.ExampleValue = qf.Value; + + if (qti.Type == "SMT/AllowedRange") + rec.AllowedRange = qf.Value; + + if (qti.Type == "SMT/AllowedIdShort") + rec.AllowedIdShort = qf.Value; + + if (qti.Type == "SMT/AllowedValue") + rec.AllowedValue = qf.Value; + + if (qti.Type == "SMT/RequiredLang") + rec.RequiredLang = qf.Value; + + if (qti.Type == "SMT/AccessMode") + rec.AccessMode = (AccessMode) + EnumHelper.GetEnumMemberFromValueString(qf.Value); + + // remove in qualfiers? + if (removeQualifers) + iqf.Qualifiers.RemoveAt(qi--); + } + + // results + return rec; + } + + public static bool ConvertSmtQualifiersToExtension(Aas.IReferable rf) + { + // acesses + if (!(rf is Aas.IQualifiable iqf) || !(rf is Aas.IHasExtensions ihe)) + return false; + + // already done? + if (iqf.Qualifiers == null || iqf.Qualifiers.Count() < 1) + return false; + + // convert + SmtAttributeRecord rec = FindSmtQualifiers(rf, removeQualifers: true); + + // attach + return DispEditHelperExtensions.GeneralExtensionHelperAddJsonExtension(rf, rec.GetType(), rec); + } } /// - /// Abstract base class for information for Submodel template specifications. + /// This attribute gives a list of given presets to an field or property. + /// in order to avoid cycles /// - public class SmtModelElement + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property, AllowMultiple = false)] + public class ExtensionHintAttributeAttribute : System.Attribute + { + public string HintText = ""; + + public ExtensionHintAttributeAttribute(string hintText) + { + if (hintText != null) + HintText = hintText; + } + } + + /// + /// This attribute marks a string field/ property as multiline. + /// in order to avoid cycles + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property)] + public class ExtensionMultiLineAttribute : System.Attribute + { + public int? MaxLines = null; + + public ExtensionMultiLineAttribute(int maxLines = -1) + { + if (maxLines > 0) + MaxLines = maxLines; + } + } + + /// + /// This attribute marks a string field/ property as multiline. + /// in order to avoid cycles + /// + [System.AttributeUsage(System.AttributeTargets.Field | System.AttributeTargets.Property)] + public class EnumMemberDisplayAttribute : System.Attribute + { + public string Text = ""; + + public EnumMemberDisplayAttribute(string text) + { + if (text != null) + Text = text; + } + } + + /// + /// Abstract base class for information for data records of extensions. + /// + public class ExtensionRecordBase { } + /// + /// Small class to report on the outcomes of SMT cheching + /// + public class SmtAttributeCheckItem + { + public bool Fail = false; + public string ShortText = ""; + public string LongText = ""; + } + /// /// Holds the possible attributes for an SMT specification per element /// as a whole. /// - public class SmtAttributeSet : SmtModelElement, ISmtSelfDescription + public class SmtAttributeRecord : ExtensionRecordBase, IExtensionSelfDescription { // self description public string GetSelfName() => "smt-attrtibute-set"; public string GetSelfUri() => "https://admin-shell.io/SubmodelTemplates/smt-attribute-set/v1/0"; + public ExtensionRecordRenderInfo GetRenderInfo() => new ExtensionRecordRenderInfo() + { + // see https://colors.muz.li/palette/0028CD/004190/2915cd/00cd90/009064 + DisplayName = "SMT attributes", + Abbreviation = "SMT", + Foreground = 0xff009064, + Background = 0xff00cd90 + }; // attributes - /// - /// This Qualifier allows to specify, how many SubmodelElement - /// instances of this SMT element are allowed in the actual - /// collection (hierarchy level of the Submodel). - /// - public AasPresetHelper.SmtCardinality Cardinality { get; set; } = AasPresetHelper.SmtCardinality.One; + [ExtensionHintAttribute("Specifies, how many SubmodelElement instances of this " + + "SMT element are allowed in the actual collection (hierarchy level of the Submodel).")] + public AasSmtQualifiers.SmtCardinality Cardinality { get; set; } = AasSmtQualifiers.SmtCardinality.One; - /// - /// The Qualifier.value defines an id of an equivalence class. - /// Only ids in the range[A-Za-z0-9] are allowed. - /// If multiple SMT elements feature the same equivalence class, - /// only one of these are allowed in the actual collection - /// (hierarchy level of the Submodel). - /// + [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 " + + "class, only one of these are allowed in the actual collection (hierarchy level of the Submodel). ")] public string EitherOr { get; set; } = ""; - /// - /// Specifies the initial value of the SubmodelElement instance, - /// when it is created for the first time. - /// + [ExtensionHintAttribute("Specifies the initial value of the SubmodelElement instance, when it is created " + + "for the first time.")] public string InitialValue { get; set; } = ""; - /// - /// Specifies the default value of the SubmodelElement instance. - /// Often, this might designate a neutral, zero or empty value - /// depending on the valueType of a SMT element. - /// + [ExtensionHintAttribute("Specifies the default value of the SubmodelElement instance. " + + "Often, this might designate a neutral, zero or empty value " + + "depending on the valueType of a SMT element.")] public string DefaultValue { get; set; } = ""; - /// - /// Specifies an example value of the SubmodelElement instance, - /// in order to allow the user to better understand the intention - /// and possible values of a SubmodelElement instance. - /// + [ExtensionHintAttribute("Specifies an example value of the SubmodelElement instance, in " + + "order to allow the user to better understand the intention nd possible values of a " + + "SubmodelElement instance.")] public string ExampleValue { get; set; } = ""; - /// - /// Specifies a set of allowed continous numerical ranges. - /// Multiple ranges can be given by delimiting them by '|'. - /// A single range is defined by interval start and end, - /// either including or excluding the given number. - /// Interval start and end are delimited by ','; - /// '.' is the decimal point. - /// '*' allows to enter the default value - /// + [ExtensionHintAttribute("Multiple ranges can be given by delimiting them by '|'. " + + "A single range is defined by interval start and end, either including or " + + "excluding the given number. Interval start and end are delimited by '(', ')' resp. '[', ']'. " + + "The decimal point is '.'. '*' allows to enter the default value.")] public string AllowedRange { get; set; } = ""; - /// - /// Specifies a regular expression validating the idShort of the - /// created SubmodelElement instance. The format shall - /// conform to POSIX extended regular expressions. - /// + [ExtensionHintAttribute("Specifies a regular expression validating the idShort of the created " + + "SubmodelElement instance. The format shall conform to POSIX extended regular expressions.")] public string AllowedIdShort { get; set; } = ""; + [ExtensionHintAttribute("Specifies a regular expression validating the value of the created " + + "SubmodelElement instance in its string representation. The format shall conform to POSIX " + + "extended regular expressions.")] + public string AllowedValue { get; set; } = ""; + + [ExtensionHintAttribute("If the SMT element is a multi language property (MLP), " + + "specifies the required languages, which shall be given. Multiple languages can " + + "be given by multiple Qualifiers. Multiple languages can be given by delimiting them by '|' . " + + "Languages are specified either by ISO 639-1 or ISO 639-2 codes.")] + public string RequiredLang { get; set; } = ""; + + [ExtensionHintAttribute("Specifies the user access mode for SubmodelElement instance. " + + "When a Submodel is received from another party, if set to Read/Only, then the user " + + "shall not change the value.")] + public AasSmtQualifiers.AccessMode AccessMode { get; set; } = AasSmtQualifiers.AccessMode.ReadWrite; + + // + // Check + // + /// - /// Specifies a regular expression validating the value of the created - /// SubmodelElement instance in its string representation. The format - /// shall conform to POSIX extended regular expressions. + /// Check a single string value /// - public string AllowedValue { get; set; } = ""; + public List PerformAttributeCheck(string idShort, string value, + List inList = null) + { + // access + var res = inList ?? new List(); + + // be safe + try + { + // idShort? + if (idShort != null) + { + if (AllowedIdShort?.HasContent() == true) + { + var match = Regex.Match(idShort, AllowedIdShort); + if (!match.Success) + res.Add(new SmtAttributeCheckItem() + { + Fail = true, + ShortText = "IdShort", + LongText = $"Fail when checking IdShort {idShort} vs. AllowedIdShort {AllowedIdShort}." + }); + } + } + + // for the value, do not allow to disable a required matched by + // simply having null + value = "" + value; + var value0 = value.HasContent() ? value : "0"; + + // AllowedRange + if (AllowedRange?.HasContent() == true + && double.TryParse(value0, NumberStyles.Number, CultureInfo.InvariantCulture, out double valueDbl)) + { + var ar = AllowedRange.Replace("*", DefaultValue); + + var inRange = false; + foreach (var rngPart in ar.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + // interval + var match = Regex.Match(rngPart, @"(\(|\[)([0-9.-]+)\s*,\s*([0-9.-]+)(\)|\])"); + if (match.Success + && double.TryParse(match.Groups[2].ToString(), out double ivMin) + && double.TryParse(match.Groups[3].ToString(), out double ivMax)) + { + var greaterMin = (match.Groups[1].ToString() == "(") + ? (valueDbl > ivMin) + : (valueDbl >= ivMin); + + var lesserMax = (match.Groups[4].ToString() == ")") + ? (valueDbl < ivMax) + : (valueDbl <= ivMax); + + if (greaterMin && lesserMax) + inRange = true; + } + else + { + // single value? + if (double.TryParse(rngPart, out double soloVal) + && soloVal == valueDbl) + inRange = true; + } + } + + // okay, now check + if (!inRange) + res.Add(new SmtAttributeCheckItem() + { + Fail = true, + ShortText = "Range", + LongText = $"Fail when checking Value {value} vs. AllowedRange {AllowedRange}." + }); + } + + // AllowedValue + if (AllowedValue?.HasContent() == true) + { + var match = Regex.Match(value, AllowedValue); + if (!match.Success) + res.Add(new SmtAttributeCheckItem() + { + Fail = true, + ShortText = "AllowedValue", + LongText = $"Fail when checking Value {value} vs. AllowedValue {AllowedValue}." + }); + } + + } catch (Exception ex) + { + LogInternally.That.CompletelyIgnoredError(ex); + res.Add(new SmtAttributeCheckItem() + { + Fail = true, + ShortText = "SMT-Attributes", + LongText = $"Fail in given SMT-attributes when checking: " + ex.Message + }); + } + + // okay + return res; + } /// - /// If the SMT element is a multi language property (MLP), - /// specifies the required languages, which shall be given. - /// Multiple languages can be given by multiple Qualifiers. - /// Multiple languages can be given by delimiting them by '|' . - /// Languages are specified either by ISO 639-1 or ISO 639-2 codes. + /// Check a multi language element /// - public string RequiredLang { get; set; } = ""; + public List PerformAttributeCheck(Aas.IMultiLanguageProperty mlp, + List inList = null) + { + // access + if (mlp?.Value == null || mlp.Value.Count < 1) + return inList; + var res = inList ?? new List(); + + // over single languages to test the Text + for (int vi = 0; vi < mlp.Value.Count; vi++) + { + // element + var mvi = mlp.Value[vi]; + if (mvi == null) + continue; + + // test for value + res = PerformAttributeCheck( + idShort: (vi == 0) ? mlp.IdShort : null, + value: mvi.Text, + inList: res); + } + + // over required langauge, to test if actual languages are there! + if (RequiredLang?.HasContent() == true) + { + // over all required languages + var lngMissing = new List(); + foreach (var rlng in RequiredLang.Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)) + { + var found = false; + foreach (var mvi in mlp.Value) + if (mvi.Language?.Trim() == rlng) + found = true; + + if (!found) + lngMissing.Add(rlng); + } + + // okay, now check + if (lngMissing.Count > 0) + res.Add(new SmtAttributeCheckItem() + { + Fail = true, + ShortText = "RequiredLang", + LongText = $"Fail when checking RequiredLang {RequiredLang}. " + + $"Missing languages are: {string.Join("|", lngMissing)}." + }); + } + + // okay + return res; + } /// - /// Specifies the user access mode for SubmodelElement instance. - /// When a Submodel is received from another party, if set to - /// Read/Only, then the user shall not change the value. + /// Check a collection of elements (for cardinality). + /// Note: this function needs a lambda for looking up SMT attribute records + /// of subordinate elements either by Referable or SemanticId reference. /// - public string AccessMode { get; set; } = ""; + public static List PerformAttributeCheck(List elems, + Func lambdaLookupSmtRec, + List inList = null) + { + // access + if (elems == null || elems.Count < 1) + return inList; + var res = inList ?? new List(); + + // make two dictionaries on these elements + // (to count elemens per semantic id and have SmtAttributes available) + var elemPerSemId = new MultiValueDictionary(); + var attrRecPerSemId = new MultiValueDictionary(); + foreach (var elem in elems) + if (elem.SemanticId?.IsValid() == true) + { + // 1st + var ssi = elem.SemanticId.ToStringExtended(); + elemPerSemId.Add(ssi, elem); + + // 2nd + var smtr = lambdaLookupSmtRec?.Invoke(elem); + if (smtr != null) + attrRecPerSemId.Add(ssi, smtr); + } + + // now can use the key of one dictionary to check it values by + // looking up the contraints within the seconde + foreach (var ssiKey in elemPerSemId.Keys) + { + // access + var els = elemPerSemId.All(ssiKey).ToList(); + var rec = attrRecPerSemId.All(ssiKey).FirstOrDefault(); + if (rec == null) + continue; + + // check + var complain = ""; + if (rec.Cardinality == SmtCardinality.One && els.Count != 1) + complain = "[1]"; + if (rec.Cardinality == SmtCardinality.OneToMany && els.Count < 1) + complain = "[1..*]"; + if (rec.Cardinality == SmtCardinality.ZeroToOne && els.Count > 1) + complain = "[0..1]"; + + // give out + if (complain.HasContent()) + res.Add(new SmtAttributeCheckItem() + { + Fail = true, + ShortText = "Cardinality", + LongText = $"Fail when checking Cardinality on dependent elements for semanticId {ssiKey}: " + + $"Required {complain} but found {0 + els.Count}." + }); + } + + // okay + return res; + } } - public class SmtModelElements + public class ExtensionRecords { - public static Dictionary AllElements = - new Dictionary(); + public static Dictionary AllRecords = + new Dictionary(); - static SmtModelElements() + static ExtensionRecords() { - Action add = (sme) => + Action add = (sme) => { - if (sme is ISmtSelfDescription sd) - AllElements.Add(sd.GetSelfUri(), sme); + if (sme is IExtensionSelfDescription sd) + AllRecords.Add(sd.GetSelfUri(), sme); }; - add(new SmtAttributeSet()); + add(new SmtAttributeRecord()); } - public static SmtModelElement GetTypeInstFromUri(string uri) + public static ExtensionRecordBase GetTypeInstFromUri(string uri) { - foreach (var x in AllElements.Values) - if (x is ISmtSelfDescription ssd && ssd.GetSelfUri() == uri) - return x; + foreach (var rec in AllRecords.Values) + if (rec is IExtensionSelfDescription ssd && ssd.GetSelfUri() == uri) + return rec; return null; } } - } diff --git a/src/AasxPackageLogic/DispEditHelperModules.cs b/src/AasxPackageLogic/DispEditHelperModules.cs index 34d7b42d0..368c23b3f 100644 --- a/src/AasxPackageLogic/DispEditHelperModules.cs +++ b/src/AasxPackageLogic/DispEditHelperModules.cs @@ -35,6 +35,7 @@ This source code may use other Open Source software components (see LICENSE.txt) using System.Runtime.Serialization; using J2N.Text; using Lucene.Net.Codecs; +using System.Text; namespace AasxPackageLogic { @@ -42,7 +43,7 @@ namespace AasxPackageLogic /// This class extends the basic helper functionalities of DispEditHelper by providing modules for display/ /// editing disting modules of the GUI, such as the different (re-usable) Interfaces of the AAS entities /// - public class DispEditHelperModules : DispEditHelperMiniModules + public class DispEditHelperModules : DispEditHelperExtensions { // // Members @@ -110,7 +111,8 @@ public static string[] GetToolTips(string[] fixToolTips, DispEditInjectAction ac // IReferable // - public void DisplayOrEditEntityReferable(AnyUiStackPanel stack, + public void DisplayOrEditEntityReferable( + Aas.Environment env, AnyUiStackPanel stack, Aas.IReferable parentContainer, Aas.IReferable referable, int indexPosition, @@ -296,14 +298,48 @@ public void DisplayOrEditEntityReferable(AnyUiStackPanel stack, if (!hideExtensions) { - // Extensions (at the end to make them not so much impressive!) - DisplayOrEditEntityListOfExtension( + // before extension, some helpful records + DisplayOrEditEntityExtensionRecords( + env, stack, referable.Extensions, + (v) => { referable.Extensions = v; }, + relatedReferable: referable); + + // Extensions (at the end to make them not so much impressive!) + DisplayOrEditEntityListOfExtension( stack: stack, extensions: referable.Extensions, setOutput: (v) => { referable.Extensions = v; }, relatedReferable: referable); } } + public void DisplayOrEditEntityReferableContinue( + Aas.Environment env, AnyUiStackPanel stack, + Aas.IReferable parentContainer, + Aas.IReferable referable, + int indexPosition, + DispEditInjectAction injectToIdShort = null, + bool hideExtensions = false) + { + // access + if (stack == null || referable == null) + return; + + // members + this.AddGroup(stack, "Referable (continue):", levelColors.SubSection); + + // before extension, some helpful records + DisplayOrEditEntityExtensionRecords( + env, stack, referable.Extensions, + (v) => { referable.Extensions = v; }, + relatedReferable: referable); + + // Extensions (at the end to make them not so much impressive!) + DisplayOrEditEntityListOfExtension( + stack: stack, extensions: referable.Extensions, + setOutput: (v) => { referable.Extensions = v; }, + relatedReferable: referable); + } + // // Extensions // @@ -2414,5 +2450,180 @@ public void DisplayOrEditEntityFileResource(AnyUiStackPanel stack, } + // + // Values checking + // + + /// + /// Infomration carrying from the functionbelow to the main application + /// + public class DisplayOrEditEntityCheckValueHandle + { + public Aas.IReferable Referable = null; + + public List CheckItems = new List(); + + public AnyUiBorder Border = null; + public AnyUiTextBlock TextBlock = null; + } + + public List DisplayOrEditEntityCheckValueEvalItems( + Aas.IReferable rf) + { + // access + var checkItems = new List(); + if (rf == null) + return checkItems; + + // what to check + var rec = CheckReferableForExtensionRecords(rf).FirstOrDefault(); + if (rec == null) + { + // can analyze qualifiers? + rec = AasSmtQualifiers.FindSmtQualifiers(rf, removeQualifers: false); + } + + // some checks can be done on the static record function, as record entities might + // be only on subordinate elements + + Func lambdaLookupSmtRec = (rf2) => + { + return CheckReferableForExtensionRecords(rf2).FirstOrDefault(); + }; + + if (rf is Aas.ISubmodel sm) + checkItems = SmtAttributeRecord.PerformAttributeCheck(sm.SubmodelElements, inList: checkItems, + lambdaLookupSmtRec: lambdaLookupSmtRec); + + if (rf is Aas.ISubmodelElementCollection smc) + checkItems = SmtAttributeRecord.PerformAttributeCheck(smc.Value, inList: checkItems, + lambdaLookupSmtRec: lambdaLookupSmtRec); + + // perform the check on factual record of this element + if (rec != null) + { + if (rf is Aas.IProperty prop) + checkItems = rec.PerformAttributeCheck(rf.IdShort, prop.Value, checkItems); + + if (rf is Aas.IMultiLanguageProperty mlp) + checkItems = rec.PerformAttributeCheck(mlp, checkItems); + } + + // okay + return checkItems; + + } + + /// + /// this handle is used to link edit value field and status fields together + /// + protected DisplayOrEditEntityCheckValueHandle _checkValueHandle = new DisplayOrEditEntityCheckValueHandle(); + + public void DisplayOrEditEntityCheckValue( + Aas.Environment env, AnyUiStackPanel stack, + DisplayOrEditEntityCheckValueHandle handle, + Aas.IReferable rf, + bool update = false) + { + // access + if (stack == null || rf == null || handle == null) + return; + + // evaluate + bool? alarmState = null; + var evalText = "Idle (no SMT spec)"; + var indicatorBg = AnyUiBrushes.White; + var indicatorFg = AnyUiBrushes.Black; + + // test + handle.CheckItems = DisplayOrEditEntityCheckValueEvalItems(rf); + if (handle.CheckItems != null) + { + // evaluate alarm + alarmState = handle.CheckItems.Where((aci) => aci.Fail).Count() > 0; + + if (alarmState == true) + { + var distinctMsg = handle.CheckItems.GroupBy((ci) => ci.ShortText).Select((gr) => gr.First().ShortText); + evalText = "Fail: " + string.Join(" ", distinctMsg); + indicatorBg = new AnyUiBrush(0xffFF4F0E); + indicatorFg = new AnyUiBrush(0xFF541805); + } + else + { + evalText = "PASS!"; + indicatorBg = new AnyUiBrush(0xff00cd90); + indicatorFg = new AnyUiBrush(0xff009064); + } + } + + // update + if (update && handle.Referable == rf) + { + if (handle.Border != null) + { + handle.Border.Background = indicatorBg; + handle.Border.BorderBrush = indicatorFg; + handle.Border.Touch(); + } + + if (handle.TextBlock != null) + { + handle.TextBlock.Text = evalText; + handle.Border.Touch(); + } + + // stop here + return; + } + + // NO update, rebuild + handle.Referable = rf; + + // add grid + var g = AddSubGrid(stack, "SMT value check:", + rows: 1, cols: 2, new[] { "*", "#" }, + paddingCaption: new AnyUiThickness(6, 0, 0, 0), + minWidthFirstCol: GetWidth(FirstColumnWidth.Standard)); + + g.DebugTag = "TEST2"; + + // indicator + handle.Border = AddSmallBorderTo(g, 0, 0, + margin: new AnyUiThickness(5, 2, 2, 2), + background: indicatorBg, + borderBrush: indicatorFg, + borderThickness: new AnyUiThickness(1)); + handle.TextBlock = new AnyUiTextBlock() + { + Text = "" + evalText, + HorizontalAlignment = AnyUiHorizontalAlignment.Center, + VerticalAlignment = AnyUiVerticalAlignment.Center, + Foreground = AnyUiBrushes.White, + Background = AnyUi.AnyUiBrushes.Transparent, + FontSize = 1.0, + FontWeight = AnyUiFontWeight.Bold + }; + handle.Border.Child = handle.TextBlock; + + AnyUiUIElement.RegisterControl( + AddSmallButtonTo(g, 0, 1, + margin: new AnyUiThickness(2, 2, 2, 2), + padding: new AnyUiThickness(5, 0, 5, 0), + content: "\u2261"), + setValue: (v) => + { + // re-evaluate + Log.Singleton.Info("Starting check."); + var ci2 = DisplayOrEditEntityCheckValueEvalItems(rf); + if (ci2 != null) + foreach (var aci in handle.CheckItems) + Log.Singleton.Info("" + aci.LongText); + Log.Singleton.Info(StoredPrint.Color.Blue, + "SMT value check: " + ((alarmState == true) ? "FAIL!" : "PASS / IDLE!") + " See log for details."); + return new AnyUiLambdaActionNone(); + }); + } + } } diff --git a/src/AasxPackageLogic/DispEditHelperSammModules.cs b/src/AasxPackageLogic/DispEditHelperSammModules.cs index 65f8a0c7c..50e572ee8 100644 --- a/src/AasxPackageLogic/DispEditHelperSammModules.cs +++ b/src/AasxPackageLogic/DispEditHelperSammModules.cs @@ -48,7 +48,7 @@ namespace AasxPackageLogic /// This class extends the AAS meta model editing function for those related to /// SAMM (Semantic Aspect Meta Model) elements. /// - public class DispEditHelperSammModules : DispEditHelperExtensions + public class DispEditHelperSammModules : DispEditHelperModules { public SammIdSet SammExtensionHelperSelectSammVersion(IEnumerable idsets) { @@ -563,6 +563,7 @@ public void SammExtensionHelperAddCompleteModelElement( emitCustomEvent: (rf) => { lambdaSetValue(forth); + return new AnyUiLambdaActionNone(); }); } } @@ -1209,6 +1210,7 @@ public static class EnumHelper public class EnumHelperMemberInfo { public string MemberValue = ""; + public string MemberDisplay = ""; public object MemberInstance; } @@ -1218,19 +1220,30 @@ public static IEnumerable EnumHelperGetMemberInfo(Type und { var enumInst = Activator.CreateInstance(underlyingType); - var enumMemberStrValue = enumMemberInfo.GetCustomAttribute(); - if (enumMemberStrValue?.Value != null) + var memVal = enumMemberInfo.GetCustomAttribute()?.Value; + var memDisp = enumMemberInfo.GetCustomAttribute()?.Text; + + if (memVal?.HasContent() == true) { var ev = enumMemberInfo.GetValue(enumInst); yield return new EnumHelperMemberInfo() { - MemberValue = enumMemberStrValue?.Value, + MemberValue = memVal, + MemberDisplay = (memDisp?.HasContent() == true) ? memDisp : memVal, MemberInstance = ev }; } } } + + public static T GetEnumMemberFromValueString(string valStr, T valElse = default(T) ) where T : struct + { + foreach (var em in EnumHelperGetMemberInfo(typeof(T))) + if (em.MemberValue.Equals(valStr?.Trim(), StringComparison.InvariantCultureIgnoreCase)) + return (T) em.MemberInstance; + return (T) valElse; + } } /// @@ -2502,13 +2515,13 @@ public void CreateSubmodelElementsInto( if (osrProp.Optional) // Cardinality qualiferToAdd.Add( - AasPresetHelper.CreateQualifierSmtCardinality(AasPresetHelper.SmtCardinality.ZeroToOne)); + AasSmtQualifiers.CreateQualifierSmtCardinality(AasSmtQualifiers.SmtCardinality.ZeroToOne)); // example value directly associated with the property if (meProp.ExampleValue != null) // ExampleValue qualiferToAdd.Add( - AasPresetHelper.CreateQualifierSmtExampleValue(meProp.ExampleValue)); + AasSmtQualifiers.CreateQualifierSmtExampleValue(meProp.ExampleValue)); // ok, a Submodel element shall be created. // But more details (SMC? Property?) are only avilable via @@ -2575,14 +2588,14 @@ public void CreateSubmodelElementsInto( { // AllowedValue == Regex qualiferToAdd.Add( - AasPresetHelper.CreateQualifierSmtAllowedValue(regexCons.Value)); + AasSmtQualifiers.CreateQualifierSmtAllowedValue(regexCons.Value)); } if (sitCons.ME is Samm.LanguageConstraint langCons) { // RequiredLanguage qualiferToAdd.Add( - AasPresetHelper.CreateQualifierSmtRequiredLang(langCons.LanguageCode)); + AasSmtQualifiers.CreateQualifierSmtRequiredLang(langCons.LanguageCode)); } } } @@ -2592,7 +2605,7 @@ public void CreateSubmodelElementsInto( if (charState.DefaultValue?.HasContent() == true) // Default value .. only for States qualiferToAdd.Add( - AasPresetHelper.CreateQualifierSmtDefaultValue(charState.DefaultValue)); + AasSmtQualifiers.CreateQualifierSmtDefaultValue(charState.DefaultValue)); } // elaborate added element further diff --git a/src/AasxPackageLogic/ExplorerMenuFactory.cs b/src/AasxPackageLogic/ExplorerMenuFactory.cs index a78f183c3..a1a0a1e1f 100644 --- a/src/AasxPackageLogic/ExplorerMenuFactory.cs +++ b/src/AasxPackageLogic/ExplorerMenuFactory.cs @@ -349,7 +349,8 @@ public static AasxMenu CreateMainMenu() .AddWpfBlazor(name: "FileRepoLoadWoPrompt", header: "Load without prompt", isCheckable: true) .AddWpfBlazor(name: "AnimateElements", header: "Animate elements", isCheckable: true) .AddWpfBlazor(name: "ObserveEvents", header: "ObserveEvents", isCheckable: true) - .AddWpfBlazor(name: "CompressEvents", header: "Compress events", isCheckable: true)); + .AddWpfBlazor(name: "CompressEvents", header: "Compress events", isCheckable: true) + .AddWpfBlazor(name: "CheckSmtElements", header: "Check SMT elements (slow!)", isCheckable: true)); // // Help diff --git a/src/AasxPackageLogic/Options.cs b/src/AasxPackageLogic/Options.cs index e8290af05..a2191904b 100644 --- a/src/AasxPackageLogic/Options.cs +++ b/src/AasxPackageLogic/Options.cs @@ -543,7 +543,11 @@ public AnyUiColor GetColor(ColorNames c) "the editor. ")] public bool CompressEvents = false; - [OptionDescription(Description = "Default value for the StayConnected options of PackageContainer. " + + [OptionDescription(Description = "When activated, the UI will detect presence of SMT attributes and " + + "will perform value checks on AAS elements. This might be slow!")] + public bool CheckSmtElements = false; + + [OptionDescription(Description = "Default value for the StayConnected options of PackageContainer. " + "That is, a loaded container will automatically try receive events, e.g. for value update.", Cmd = "-stay-connected")] public bool DefaultStayConnected = false; diff --git a/src/AasxPackageLogic/PackageCentral/IndexOfSignificantAasElements.cs b/src/AasxPackageLogic/PackageCentral/IndexOfSignificantAasElements.cs index e1f10aa0f..0445334ab 100644 --- a/src/AasxPackageLogic/PackageCentral/IndexOfSignificantAasElements.cs +++ b/src/AasxPackageLogic/PackageCentral/IndexOfSignificantAasElements.cs @@ -27,7 +27,7 @@ public enum SignificantAasElement } /// - /// This is the datat structure which is kept for each indexed significant AAS element. + /// This is the data structure which is kept for each indexed significant AAS element. /// The idea is record is to be found fast and to index an AAS element in a way, that /// is can be found by an AAS reference rather than an object reference, as the AAS /// might have been changed (completely) between times of re-indexing it. diff --git a/src/AasxPackageLogic/VisualAasxElements.cs b/src/AasxPackageLogic/VisualAasxElements.cs index 5c54077e5..64e30e767 100644 --- a/src/AasxPackageLogic/VisualAasxElements.cs +++ b/src/AasxPackageLogic/VisualAasxElements.cs @@ -1089,7 +1089,11 @@ public override void RefreshFromMainData() var qt = ext.Name ?? ""; var qv = ext.Value ?? ""; if (qv != "") - qv = "=" + AdminShellUtil.ShortenWithEllipses(qv, 30); + { + qv = qv.Replace('\r', ' '); + qv = qv.Replace('\n', ' '); + qv = "=" + AdminShellUtil.ShortenWithEllipses(qv, 30); + } this.Info += " @{" + qt + qv + "}"; } } @@ -1252,18 +1256,10 @@ public override void RefreshFromMainData() } // SMT - var smtTypeInst = DispEditHelperExtensions.CheckReferableForSmtExtensionType(theCD); - if (smtTypeInst != null && smtTypeInst is ISmtSelfDescription ssd) + var smtTypeInst = DispEditHelperExtensions.CheckReferableForExtensionRecordType(theCD); + if (smtTypeInst != null && smtTypeInst is IExtensionSelfDescription ssd) { - // completely reformat the Caption - this.Caption = $"\"{"" + theCD.IdShort}\" \u29fc{ssd.GetSelfName()}\u29fd {"" + theCD.Id}"; - - // see https://colors.muz.li/palette/0028CD/004190/2915cd/00cd90/009064 - this.TagString = "SMT"; - this.Border = new AnyUiColor(0xff009064); - this.Background = new AnyUiColor(0xff00cd90); - this.TagBg = new AnyUiColor(0xff009064); - this.TagFg = new AnyUiColor(0xffffffff); + this.Info = $"\u29fc{ssd.GetSelfName()}\u29fd " + this.Info; } //TODO (jtikekar, 0000-00-00): support DataSpecificationPhysicalUnit diff --git a/src/AasxWpfControlLibrary/AnyUiWpf.cs b/src/AasxWpfControlLibrary/AnyUiWpf.cs index aad15c027..0cd536706 100644 --- a/src/AasxWpfControlLibrary/AnyUiWpf.cs +++ b/src/AasxWpfControlLibrary/AnyUiWpf.cs @@ -201,7 +201,7 @@ public AnyUiPoint GetAnyUiPoint(Point p) /// public override void EmitOutsideAction(AnyUiLambdaActionBase action) { - if (action == null) + if (action == null || action is AnyUiLambdaActionNone) return; WishForOutsideAction.Add(action); } @@ -642,96 +642,108 @@ private void InitRenderRecs() new RenderRec(typeof(AnyUiBorder), typeof(Border), (a, b, mode, rd) => { - if (a is AnyUiBorder cntl && b is Border wpf - && mode == AnyUiRenderMode.All) + if (a is AnyUiBorder cntl && b is Border wpf) { - // members - if (cntl.Background != null) - wpf.Background = GetWpfBrush(cntl.Background); - if (cntl.BorderThickness != null) - wpf.BorderThickness = GetWpfTickness(cntl.BorderThickness); - if (cntl.BorderBrush != null) - wpf.BorderBrush = GetWpfBrush(cntl.BorderBrush); - if (cntl.Padding != null) - wpf.Padding = GetWpfTickness(cntl.Padding); - if (cntl.CornerRadius != null) - wpf.CornerRadius = new CornerRadius(cntl.CornerRadius.Value); - - // callbacks - if (cntl.IsDropBox) + if (mode == AnyUiRenderMode.All) { - wpf.AllowDrop = true; - wpf.DragEnter += (object sender2, DragEventArgs e2) => - { - e2.Effects = DragDropEffects.Copy; - }; - wpf.PreviewDragOver += (object sender3, DragEventArgs e3) => - { - e3.Handled = true; - }; - wpf.Drop += (object sender4, DragEventArgs e4) => + // members + if (cntl.BorderThickness != null) + wpf.BorderThickness = GetWpfTickness(cntl.BorderThickness); + if (cntl.Padding != null) + wpf.Padding = GetWpfTickness(cntl.Padding); + if (cntl.CornerRadius != null) + wpf.CornerRadius = new CornerRadius(cntl.CornerRadius.Value); + + // callbacks + if (cntl.IsDropBox) { - if (e4.Data.GetDataPresent(DataFormats.FileDrop, true)) + wpf.AllowDrop = true; + wpf.DragEnter += (object sender2, DragEventArgs e2) => { - // Note that you can have more than one file. - string[] files = (string[])e4.Data.GetData(DataFormats.FileDrop); - - // Assuming you have one file that you care about, pass it off to whatever - // handling code you have defined. - if (files != null && files.Length > 0 - && sender4 is FrameworkElement) + e2.Effects = DragDropEffects.Copy; + }; + wpf.PreviewDragOver += (object sender3, DragEventArgs e3) => + { + e3.Handled = true; + }; + wpf.Drop += (object sender4, DragEventArgs e4) => + { + if (e4.Data.GetDataPresent(DataFormats.FileDrop, true)) { - // update UI - if (wpf.Child is TextBlock tb2) - tb2.Text = "" + files[0]; + // Note that you can have more than one file. + string[] files = (string[])e4.Data.GetData(DataFormats.FileDrop); - // value changed - cntl.setValueLambda?.Invoke(files[0]); + // Assuming you have one file that you care about, pass it off to whatever + // handling code you have defined. + if (files != null && files.Length > 0 + && sender4 is FrameworkElement) + { + // update UI + if (wpf.Child is TextBlock tb2) + tb2.Text = "" + files[0]; - // contents changed - WishForOutsideAction.Add(new AnyUiLambdaActionContentsChanged()); + // value changed + cntl.setValueLambda?.Invoke(files[0]); + + // contents changed + WishForOutsideAction.Add(new AnyUiLambdaActionContentsChanged()); + } } - } - e4.Handled = true; - }; - } + e4.Handled = true; + }; + } - // double click - if ((cntl.EmitEvent & AnyUiEventMask.MouseAll) > 0) - { - wpf.MouseDown += (s2,e2) => + // double click + if ((cntl.EmitEvent & AnyUiEventMask.MouseAll) > 0) { - if (((cntl.EmitEvent & AnyUiEventMask.LeftDown) > 0) && (e2.ClickCount == 1)) - cntl.setValueLambda?.Invoke( - new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); + wpf.MouseDown += (s2,e2) => + { + if (((cntl.EmitEvent & AnyUiEventMask.LeftDown) > 0) && (e2.ClickCount == 1)) + cntl.setValueLambda?.Invoke( + new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); - if (((cntl.EmitEvent & AnyUiEventMask.LeftDouble) > 0) && (e2.ClickCount == 2)) - cntl.setValueLambda?.Invoke( - new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); - }; + if (((cntl.EmitEvent & AnyUiEventMask.LeftDouble) > 0) && (e2.ClickCount == 2)) + cntl.setValueLambda?.Invoke( + new AnyUiEventData(AnyUiEventMask.LeftDouble, cntl, 2)); + }; + } } + + if (mode == AnyUiRenderMode.All || mode == AnyUiRenderMode.StatusToUi) + { + if (cntl.Background != null) + wpf.Background = GetWpfBrush(cntl.Background); + if (cntl.BorderBrush != null) + wpf.BorderBrush = GetWpfBrush(cntl.BorderBrush); + } } - }), + }), new RenderRec(typeof(AnyUiLabel), typeof(Label), (a, b, mode, rd) => { - if (a is AnyUiLabel cntl && b is Label wpf - && mode == AnyUiRenderMode.All) - { - if (cntl.Background != null) - wpf.Background = GetWpfBrush(cntl.Background); - if (rd?.ForegroundSelfStand != null) - wpf.Foreground = GetWpfBrush(rd.ForegroundSelfStand); - if (cntl.Foreground != null) - wpf.Foreground = GetWpfBrush(cntl.Foreground); - if (cntl.FontWeight.HasValue) - wpf.FontWeight = GetFontWeight(cntl.FontWeight.Value); - if (cntl.Padding != null) - wpf.Padding = GetWpfTickness(cntl.Padding); - wpf.Content = cntl.Content; - } - }), + if (a is AnyUiLabel cntl && b is Label wpf) + { + if (mode == AnyUiRenderMode.All) + { + if (cntl.Background != null) + wpf.Background = GetWpfBrush(cntl.Background); + if (rd?.ForegroundSelfStand != null) + wpf.Foreground = GetWpfBrush(rd.ForegroundSelfStand); + if (cntl.Foreground != null) + wpf.Foreground = GetWpfBrush(cntl.Foreground); + if (cntl.FontWeight.HasValue) + wpf.FontWeight = GetFontWeight(cntl.FontWeight.Value); + if (cntl.Padding != null) + wpf.Padding = GetWpfTickness(cntl.Padding); + } + + if (mode == AnyUiRenderMode.All || mode == AnyUiRenderMode.StatusToUi) + { + wpf.Content = cntl.Content; + } + } + }), new RenderRec(typeof(AnyUiTextBlock), typeof(TextBlock), (a, b, mode, rd) => { @@ -920,8 +932,9 @@ private void InitRenderRecs() // callbacks cntl.originalValue = "" + cntl.Text; wpf.TextChanged += (sender, e) => { - cntl.setValueLambda?.Invoke(wpf.Text); - WishForOutsideAction.Add(new AnyUiLambdaActionContentsChanged()); + var la = cntl.setValueLambda?.Invoke(wpf.Text); + EmitOutsideAction(la); + EmitOutsideAction(new AnyUiLambdaActionContentsChanged()); }; wpf.KeyUp += (sender, e) => { @@ -989,7 +1002,10 @@ private void InitRenderRecs() System.Windows.Controls.TextChangedEventHandler tceh = (sender, e) => { // for AAS events: only invoke, if required if (cntl.Text != wpf.Text) - cntl.setValueLambda?.Invoke(wpf.Text); + { + var la = cntl.setValueLambda?.Invoke(wpf.Text); + EmitOutsideAction(la); + } cntl.Text = wpf.Text; }; wpf.AddHandler(System.Windows.Controls.Primitives.TextBoxBase.TextChangedEvent, tceh); @@ -1160,7 +1176,8 @@ public UIElement GetOrCreateWpfElement( bool allowCreate = true, bool allowReUse = true, AnyUiRenderMode mode = AnyUiRenderMode.All, - RenderDefaults renderDefaults = null) + RenderDefaults renderDefaults = null, + Dictionary updateElemsOnly = null) { // access if (el == null) @@ -1194,10 +1211,11 @@ public UIElement GetOrCreateWpfElement( var bt2 = searchType.BaseType; if (bt2 != null) GetOrCreateWpfElement(el, superType: bt2, allowReUse: true, - mode: AnyUiRenderMode.StatusToUi, renderDefaults: renderDefaults); - - foundRR.InitLambda?.Invoke(el, dd.WpfElement, AnyUiRenderMode.StatusToUi, renderDefaults); + mode: AnyUiRenderMode.StatusToUi, renderDefaults: renderDefaults, + updateElemsOnly: updateElemsOnly); + if (updateElemsOnly == null || updateElemsOnly.ContainsKey(el)) + foundRR.InitLambda?.Invoke(el, dd.WpfElement, AnyUiRenderMode.StatusToUi, renderDefaults); } el.Touched = false; @@ -1206,7 +1224,8 @@ public UIElement GetOrCreateWpfElement( foreach (var elch in ien.GetChildren()) GetOrCreateWpfElement(elch, allowCreate: false, allowReUse: true, mode: AnyUiRenderMode.StatusToUi, - renderDefaults: renderDefaults); + renderDefaults: renderDefaults, + updateElemsOnly: updateElemsOnly); // return (effectively TOP element) return dd.WpfElement; @@ -1229,10 +1248,12 @@ public UIElement GetOrCreateWpfElement( var bt = searchType.BaseType; if (bt != null) GetOrCreateWpfElement(el, superType: bt, - allowReUse: allowReUse, renderDefaults: renderDefaults); + allowReUse: allowReUse, renderDefaults: renderDefaults, + updateElemsOnly: updateElemsOnly); - // perform the render action (for this level of attributes, second) - foundRR.InitLambda?.Invoke(el, dd.WpfElement, AnyUiRenderMode.All, renderDefaults); + // perform the render action (for this level of attributes, second) + if (updateElemsOnly == null || updateElemsOnly.ContainsKey(el)) + foundRR.InitLambda?.Invoke(el, dd.WpfElement, AnyUiRenderMode.All, renderDefaults); // does the element need child elements? // do a special case handling here, unless a more generic handling is required @@ -1242,7 +1263,8 @@ public UIElement GetOrCreateWpfElement( && cntl.Content != null) { wpf.Content = GetOrCreateWpfElement(cntl.Content, - allowReUse: allowReUse, renderDefaults: renderDefaults); + allowReUse: allowReUse, renderDefaults: renderDefaults, + updateElemsOnly: updateElemsOnly); } } diff --git a/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs b/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs index 403a41a09..19d256994 100644 --- a/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs +++ b/src/AasxWpfControlLibrary/DispEditAasxEntity.xaml.cs @@ -212,14 +212,12 @@ public DisplayRenderHints DisplayMessage(string message) } #endif - - public DisplayRenderHints DisplayOrEditVisualAasxElement( PackageCentral packages, AnyUiDisplayContextWpf displayContext, ListOfVisualElementBasic entities, - bool editMode, bool hintMode = false, bool showIriMode = false, - VisualElementEnvironmentItem.ConceptDescSortOrder? cdSortOrder = null, + bool editMode, bool hintMode = false, bool showIriMode = false, bool checkSmt = false, + VisualElementEnvironmentItem.ConceptDescSortOrder? cdSortOrder = null, IFlyoutProvider flyoutProvider = null, IPushApplicationEvent appEventProvider = null, DispEditHighlight.HighlightFieldInfo hightlightField = null, @@ -325,7 +323,7 @@ public DisplayRenderHints DisplayOrEditVisualAasxElement( // try to delegate to common routine var common = _helper.DisplayOrEditCommonEntity( - packages, stack, superMenu, editMode, hintMode, cdSortOrder, entity); + packages, stack, superMenu, editMode, hintMode, checkSmt, cdSortOrder, entity); if (!common) { @@ -546,7 +544,8 @@ public Tuple GetLastRenderedRoot() public void RedisplayRenderedRoot( AnyUiUIElement root, AnyUiRenderMode mode, - bool useInnerGrid = false) + bool useInnerGrid = false, + Dictionary updateElemsOnly = null) { // safe _lastRenderedRootElement = root; @@ -565,11 +564,13 @@ public void RedisplayRenderedRoot( && stack.Children.Count == 1 && stack.Children[0] is AnyUiGrid grid) { - spwpf = dcwpf.GetOrCreateWpfElement(grid, allowReUse: allowReUse, mode: mode); + spwpf = dcwpf.GetOrCreateWpfElement(grid, allowReUse: allowReUse, mode: mode, + updateElemsOnly: updateElemsOnly); } else { - spwpf = dcwpf.GetOrCreateWpfElement(root, allowReUse: allowReUse, mode: mode); + spwpf = dcwpf.GetOrCreateWpfElement(root, allowReUse: allowReUse, mode: mode, + updateElemsOnly: updateElemsOnly); DockPanel.SetDock(spwpf, Dock.Top); } diff --git a/src/AnyUi/AnyUiBase.cs b/src/AnyUi/AnyUiBase.cs index 5cad9bdf4..a97d857ea 100644 --- a/src/AnyUi/AnyUiBase.cs +++ b/src/AnyUi/AnyUiBase.cs @@ -509,10 +509,28 @@ public AnyUiLambdaActionModalPanelReRender(AnyUiDialogueDataModalPanel diaDataPa } } - /// - /// Requests the main application to display a content file or external link - /// - public class AnyUiLambdaActionDisplayContentFile : AnyUiLambdaActionBase + /// + /// ReRender the main enitity panel + /// + public class AnyUiLambdaActionEntityPanelReRender : AnyUiLambdaActionBase + { + public AnyUiRenderMode Mode = AnyUiRenderMode.StatusToUi; + public bool UseInnerGrid = false; + public Dictionary UpdateElemsOnly = null; + + public AnyUiLambdaActionEntityPanelReRender(AnyUiRenderMode mode, bool useInnerGrid = false, + Dictionary updateElemsOnly = null) + { + Mode = mode; + UseInnerGrid = useInnerGrid; + UpdateElemsOnly = updateElemsOnly; + } + } + + /// + /// Requests the main application to display a content file or external link + /// + public class AnyUiLambdaActionDisplayContentFile : AnyUiLambdaActionBase { public AnyUiLambdaActionDisplayContentFile() { } public AnyUiLambdaActionDisplayContentFile( diff --git a/src/AnyUi/AnyUiSmallWidgetToolkit.cs b/src/AnyUi/AnyUiSmallWidgetToolkit.cs index 32968992d..3279cdbb2 100644 --- a/src/AnyUi/AnyUiSmallWidgetToolkit.cs +++ b/src/AnyUi/AnyUiSmallWidgetToolkit.cs @@ -20,7 +20,7 @@ namespace AnyUi public class AnyUiSmallWidgetToolkit { /// - /// Automatically replace labes starting with IRI with active links + /// Automatically replace labels starting with IRI with active links /// public bool showIriMode = false; diff --git a/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs b/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs index b0953693d..a3f20d313 100644 --- a/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs +++ b/src/BlazorExplorer/Data/BlazorSession.CommandBindings.cs @@ -92,9 +92,10 @@ public async Task CommandBinding_GeneralDispatch( // in session, set these all important settings this.EditMode = MainMenu?.IsChecked("EditMenu") == true; this.HintMode = MainMenu?.IsChecked("HintsMenu") == true; + this.CheckSmtMode = MainMenu?.IsChecked("CheckSmtElements") == true; - // edit mode affects the total element view - RedrawAllAasxElements(nextFocusMdo: currMdo); + // edit mode affects the total element view + RedrawAllAasxElements(nextFocusMdo: currMdo); return; } diff --git a/src/BlazorExplorer/Data/BlazorSession.cs b/src/BlazorExplorer/Data/BlazorSession.cs index cdde63a1a..bb49339c9 100644 --- a/src/BlazorExplorer/Data/BlazorSession.cs +++ b/src/BlazorExplorer/Data/BlazorSession.cs @@ -132,9 +132,16 @@ public PackageCentral PackageCentral /// public bool HintMode = true; - // to be refactored in a class? + /// + /// Session allows to check elements for SMT attributes. + /// This setting shall be controlled by the menu / hotkey/ script + /// functionality. + /// + public bool CheckSmtMode = true; - public VisualElementGeneric LoadedPluginNode = null; + // to be refactored in a class? + + public VisualElementGeneric LoadedPluginNode = null; public Plugins.PluginInstance LoadedPluginInstance = null; public object LoadedPluginSessionId = null; @@ -487,7 +494,7 @@ public bool PrepareDisplayDataAndElementPanel( var common = helper.DisplayOrEditCommonEntity( PackageCentral, elementPanel, - superMenu, EditMode, HintMode, + superMenu, EditMode, HintMode, CheckSmtMode, tiCds?.CdSortOrder ?? VisualElementEnvironmentItem.ConceptDescSortOrder.None, DisplayElements.SelectedItem); diff --git a/src/BlazorExplorer/Pages/AnyUiRenderElem.razor b/src/BlazorExplorer/Pages/AnyUiRenderElem.razor index 97c0e9f59..7b09d5125 100644 --- a/src/BlazorExplorer/Pages/AnyUiRenderElem.razor +++ b/src/BlazorExplorer/Pages/AnyUiRenderElem.razor @@ -66,8 +66,8 @@ if (Element is AnyUiControl ctrl) { - style.Add("color", ctrl.Foreground?.HtmlRgb(), doNotSetIfNull: true); - style.Add("background-color", ctrl.Background?.HtmlRgb(), doNotSetIfNull: true); + style.Add("color", ctrl.Foreground?.HtmlRgba(), doNotSetIfNull: true); + style.Add("background-color", ctrl.Background?.HtmlRgba(), doNotSetIfNull: true); } // @@ -165,6 +165,7 @@ style.SetSpecifics( margin: border.Margin, padding: border.Padding, + background: border.Background, borderBrush: border.BorderBrush, borderThickness: border.BorderThickness); diff --git a/src/BlazorExplorer/Pages/AnyUiRenderGrid.razor b/src/BlazorExplorer/Pages/AnyUiRenderGrid.razor index 7497983b1..3b9a68316 100644 --- a/src/BlazorExplorer/Pages/AnyUiRenderGrid.razor +++ b/src/BlazorExplorer/Pages/AnyUiRenderGrid.razor @@ -254,6 +254,7 @@ tdStyle.SetSpecifics( margin: chbrd.Margin, padding: chbrd.Padding, + background: chbrd.Background, borderBrush: chbrd.BorderBrush, borderThickness: chbrd.BorderThickness, cornerRadius: chbrd.CornerRadius); diff --git a/src/BlazorExplorer/options-debug.MIHO.json b/src/BlazorExplorer/options-debug.MIHO.json index 135e83129..d806b5afc 100644 --- a/src/BlazorExplorer/options-debug.MIHO.json +++ b/src/BlazorExplorer/options-debug.MIHO.json @@ -21,10 +21,11 @@ // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02003-1-2_Template_TechnicalData.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_B.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\IDTA 02006-2-1_Template_Digital Nameplate_V3_lang.aasx", - "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\01_Festo_SPAU_VR3_DPPV2.aasx", + // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\01_Festo_SPAU_VR3_DPPV2.aasx", // "AuxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\SMT_Sample_A.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\01_Festo_SPAU_VR3_V3new_spiel1.aasx", // "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\01_Festo_SPAU_VR3_DPPV2.aasx", + "AasxToLoad": "C:\\HOMI\\Develop\\Aasx\\repo\\samm-test\\samm_spiel_empty-smt.aasx", "WindowLeft": 100, "WindowTop": -1, "WindowWidth": 900,