diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgd.xml new file mode 100644 index 00000000..2cd2598b --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgp.xml new file mode 100644 index 00000000..7f3d7f74 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/-6uET5ueEMV8-JHVE8F8gxpEZdgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml index 440028b3..ccd7bbfd 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUd.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml index 72ba7863..8bc49bd6 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/GlNzMPyGOM0BrQS1yt1y4cFWeUUp.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml index b59fde57..bc6b4700 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQd.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml index 86e9ac14..4384f39f 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/MCNyQ04ZNyCaVv14MUbnfNGfpPQp.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml index 8320130b..cccbdb0f 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUd.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml index 75750740..b7d6c319 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/sGQWKvrvt2ruQjk-XJfB0Jza4dUp.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml index 407f4985..1a649464 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcd.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml index e1d45355..d604a5b8 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/tCUNwVQSieE2RoENiCkr-MV2apcp.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml index 560c6c85..bc4a0b70 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0d.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml index 3f7392c0..7ddd1494 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/uDx_v6ZvW-Br8iKhWiFaxE8NSJ0p.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml index bf03fbf8..b735f78e 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUd.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml index 663bed51..d29a2560 100644 --- a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/wLsNKNXO4UtoK-s3CTap0Umd4TUp.xml @@ -1,2 +1,2 @@ - - \ No newline at end of file + + \ No newline at end of file diff --git a/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14d.xml b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14d.xml new file mode 100644 index 00000000..378b6137 --- /dev/null +++ b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14d.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14p.xml b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14p.xml new file mode 100644 index 00000000..ff323a3f --- /dev/null +++ b/resources/project/Xc4-tOl6vkwzCeVKSWRahPrXJiQ/51VqhzOOH8Yqqc2oa1CzaYTZP14p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgod.xml b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgod.xml new file mode 100644 index 00000000..4356a6ae --- /dev/null +++ b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgop.xml b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgop.xml new file mode 100644 index 00000000..511fef92 --- /dev/null +++ b/resources/project/f1CasPtPWEKkULO6lQ_WTjau2Nw/f2kdi_ZH-kkLCJnHzBMki3bnqgop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wd.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wd.xml new file mode 100644 index 00000000..4356a6ae --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wp.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wp.xml new file mode 100644 index 00000000..01cb34e6 --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/FVJBsRjCJsxU-SL7Pp8kYCsHV2wp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiod.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiod.xml new file mode 100644 index 00000000..99772b42 --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiod.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiop.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiop.xml new file mode 100644 index 00000000..21ee7e51 --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/NdScGNwbGcD5YerOttsmPZeexiop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8d.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8d.xml new file mode 100644 index 00000000..7a6326b9 --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8d.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8p.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8p.xml new file mode 100644 index 00000000..518b9b5d --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/bAXFH3dEyqTJSksdI1ytU-SpEb8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ed.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ed.xml new file mode 100644 index 00000000..7a6326b9 --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ed.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ep.xml b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ep.xml new file mode 100644 index 00000000..1e84b0d5 --- /dev/null +++ b/resources/project/f2kdi_ZH-kkLCJnHzBMki3bnqgo/zj4G2uES496vbFcI_EghE2iPm2Ep.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/+wt/+test/TemplateApps.m b/test/+wt/+test/TemplateApps.m new file mode 100644 index 00000000..3f0e1c78 --- /dev/null +++ b/test/+wt/+test/TemplateApps.m @@ -0,0 +1,119 @@ +classdef TemplateApps < matlab.uitest.TestCase + % Implements unit tests for the app templates + + % Copyright 2025 The MathWorks Inc. + + %% Properties + properties + App + Figure matlab.ui.Figure + % TempFolder + end + + + %% Test Method Setup + methods (TestMethodSetup) + + % function prepareTempFolder(testCase) + % + % % Use a temporary folder for sessions + % import matlab.unittest.fixtures.TemporaryFolderFixture + % fixture = testCase.applyFixture(TemporaryFolderFixture); + % testCase.TempFolder = fixture.Folder; + % + % end %function + + end %methods + + + %% Test Method Teardown + methods (TestMethodTeardown) + % Teardown for each test + + function deleteApp(testCase) + + if isscalar(testCase.App) && isvalid(testCase.App) + if isscalar(testCase.App.Figure) + delete(testCase.App.Figure) + end + delete(testCase.App) + end + + end %function + + end %methods + + %% Test methods + methods (Test) + + function testTemplateBaseApp(testCase) + % Verifies behavior of the app template + + % Launch the app + diag = "Expected app to launch without warnings."; + fcn = @()TemplateBaseApp(); + testCase.App = testCase.verifyWarningFree(fcn, diag); + + % Verify app and component creation + diag = "Expected app figure to be populated."; + testCase.verifyNotEmpty(testCase.App.Figure, diag) + + % Verify app closes + diag = "Expected app to close without warnings."; + fcn = @()close(testCase.App); + testCase.verifyWarningFree(fcn, diag); + + diag = "Expected app to be deleted."; + testCase.verifyFalse(isvalid(testCase.App), diag) + + end %function + + + function testTemplateBaseSingleSessionApp(testCase) + % Verifies behavior of the app template + + % Launch the app + diag = "Expected app to launch without warnings."; + fcn = @()TemplateBaseSingleSessionApp(); + testCase.App = testCase.verifyWarningFree(fcn, diag); + + % Verify app and component creation + diag = "Expected app figure to be populated."; + testCase.verifyNotEmpty(testCase.App.Figure, diag) + + % Verify app closes + diag = "Expected app to close without warnings."; + fcn = @()close(testCase.App); + testCase.verifyWarningFree(fcn, diag); + + diag = "Expected app to be deleted."; + testCase.verifyFalse(isvalid(testCase.App), diag) + + end %function + + + function testTemplateBaseMultiSessionApp(testCase) + % Verifies behavior of the app template + + % Launch the app + diag = "Expected app to launch without warnings."; + fcn = @()TemplateBaseMultiSessionApp(); + testCase.App = testCase.verifyWarningFree(fcn, diag); + + % Verify app and component creation + diag = "Expected app figure to be populated."; + testCase.verifyNotEmpty(testCase.App.Figure, diag) + + % Verify app closes + diag = "Expected app to close without warnings."; + fcn = @()close(testCase.App); + testCase.verifyWarningFree(fcn, diag); + + diag = "Expected app to be deleted."; + testCase.verifyFalse(isvalid(testCase.App), diag) + + end %function + + end %methods + +end %classdef \ No newline at end of file diff --git a/widgets/+wt/+apps/AbstractSessionApp.m b/widgets/+wt/+apps/AbstractSessionApp.m index 6689af2c..50376bac 100644 --- a/widgets/+wt/+apps/AbstractSessionApp.m +++ b/widgets/+wt/+apps/AbstractSessionApp.m @@ -2,7 +2,7 @@ ?wt.apps.BaseMultiSessionApp}) AbstractSessionApp < wt.apps.BaseApp % Abstract base class for Widgets Toolbox app with 1+ sessions -% Copyright 2024-2025 The MathWorks Inc. + % Copyright 2024-2025 The MathWorks Inc. %% Abstract Public Properties @@ -87,10 +87,6 @@ % Call superclass constructor app@wt.apps.BaseApp(varargin{:}); - % Attach listeners to Session being set - app.SessionSetListener = listener(app,"Session","PostSet",... - @(~,~)app.onSessionSet_Private()); - end %function end %methods @@ -99,6 +95,19 @@ %% Protected Methods methods (Access = protected) + function setup_internal(app) + % Preform internal pre-setup necessary + + % Show output if Debug is on + app.displayDebugText(); + + % Attach listeners to Session being set + app.SessionSetListener = listener(app,"Session","PostSet",... + @(~,~)app.onSessionSet_Private()); + + end %function + + function onSessionSet(app) % This method is called when the Session property has changed, % such as a session being added or removed from the app. The @@ -248,11 +257,12 @@ function saveSession_Internal(app, useSaveAs, session) cleanupObj = onCleanup(@()delete(dlg)); % Save the session + % This will trigger update if path updates and/or session + % is no longer dirty session.save(sessionPath); - % Update the app in case filepath changed + % Update the title app.updateTitle() - app.update() end %if strlength(sessionPath) @@ -359,7 +369,7 @@ function saveSession_Internal(app, useSaveAs, session) % Define arguments arguments app (1,1) wt.apps.BaseApp - session (1,1) wt.model.BaseSession = app.Session + session wt.model.BaseSession = app.Session end % Show output if Debug is on @@ -369,7 +379,7 @@ function saveSession_Internal(app, useSaveAs, session) isCancelled = false; % Don't save and return now if session is invalid or clean - if ~isvalid(session) || ~session.Dirty + if ~isscalar(session) || ~isvalid(session) || ~session.Dirty return end diff --git a/widgets/+wt/+apps/BaseMultiSessionApp.m b/widgets/+wt/+apps/BaseMultiSessionApp.m index 99f4c885..a8783071 100644 --- a/widgets/+wt/+apps/BaseMultiSessionApp.m +++ b/widgets/+wt/+apps/BaseMultiSessionApp.m @@ -28,6 +28,12 @@ % Number of sessions loaded NumSessions (1,1) double + % Names of sessions loaded + SessionNames (:,1) string + + % Names of sessions for display, with * for unsaved session + SessionDisplayNames (:,1) string + % Currently selected session SelectedSession wt.model.BaseSession @@ -57,6 +63,27 @@ value = numel(app.Session); end + function value = get.SessionNames(app) + numSessions = app.NumSessions; + value = repmat("",numSessions,1'); + if numSessions > 0 + isValidSession = isvalid(app.Session); + value(isValidSession) = [app.Session(isValidSession).Name]; + end + end + + function value = get.SessionDisplayNames(app) + numSessions = app.NumSessions; + value = repmat("",numSessions,1'); + if numSessions > 0 + isValidSession = isvalid(app.Session); + value(isValidSession) = [app.Session(isValidSession).Name]; + isDirty = false(numSessions, 1); + isDirty(isValidSession) = [app.Session(isValidSession).Dirty]; + value(isDirty) = value(isDirty) + " *"; + end + end + function value = get.SelectedSession(app) value = app.Session( app.SelectedSessionIndex ); end @@ -126,11 +153,22 @@ function close(app) % Store the session % This also triggers app.update(), app.updateTitle() if isempty(app.Session) + app.Session = session; + else + + % Ensure a unique name + thisName = matlab.lang.makeUniqueStrings(... + session.Name, app.SessionNames); + session.FilePath = thisName; + session.Dirty = false; + + % Append the session app.Session(end+1) = session; - % Don't select by default, but concrete app may do this - end + % Don't select it by default, but concrete app may do this + + end %if end %function @@ -271,6 +309,9 @@ function selectSession(app, session) function setup_internal(app) % Preform internal pre-setup necessary + % Call superclass method + app.setup_internal@wt.apps.AbstractSessionApp(); + % Show output if Debug is on app.displayDebugText(); diff --git a/widgets/+wt/+apps/BaseSingleSessionApp.m b/widgets/+wt/+apps/BaseSingleSessionApp.m index acdb8075..ffdaa530 100644 --- a/widgets/+wt/+apps/BaseSingleSessionApp.m +++ b/widgets/+wt/+apps/BaseSingleSessionApp.m @@ -124,9 +124,11 @@ function close(app) % Call superclass internal load method session = app.loadSession_Internal(sessionPath); - % Store the session + % If successful, store the session % This also triggers app.update(), app.updateTitle() - app.Session = session; + if isscalar(session) && isvalid(session) + app.Session = session; + end end %function @@ -139,6 +141,9 @@ function close(app) function setup_internal(app) % Preform internal pre-setup necessary + % Call superclass method + app.setup_internal@wt.apps.AbstractSessionApp(); + % Show output if Debug is on app.displayDebugText(); diff --git a/widgets/+wt/+model/BaseSession.m b/widgets/+wt/+model/BaseSession.m index a59d95dd..9167cdd5 100644 --- a/widgets/+wt/+model/BaseSession.m +++ b/widgets/+wt/+model/BaseSession.m @@ -65,6 +65,7 @@ end end end + %% Public methods (subclass may override these) @@ -138,28 +139,6 @@ function save(session, filePath) end %methods - % %% Hidden methods - % methods (Hidden) - % - % function setFilePathSilently(obj, filePath) - % % Set FilePath without triggering change notifications - % - % % Define arguments - % arguments - % obj (1,1) wt.model.BaseModel - % filePath (1,1) string - % end - % - % oldValue = obj.EnableChangeListeners; - % obj.EnableChangeListeners = false; - % obj.FilePath = filePath; - % obj.EnableChangeListeners = oldValue; - % - % end %function - % - % end %methods - - %% Protected methods (subclass may override these) methods (Access = protected) @@ -200,7 +179,7 @@ function onModelChanged(obj,evt) end %function end %methods - + %% Accessors methods diff --git a/widgets/examples/templates/TemplateBaseApp.m b/widgets/examples/templates/TemplateBaseApp.m new file mode 100644 index 00000000..8944df06 --- /dev/null +++ b/widgets/examples/templates/TemplateBaseApp.m @@ -0,0 +1,158 @@ +classdef TemplateBaseApp < wt.apps.BaseApp + % Implements a template for a BaseApp + + % Copyright 2022-2025 The MathWorks Inc. + + + %% Internal Components + % Create properties here for each control, layout, or view component + % that will be placed directly into the main app window. + properties ( Transient, NonCopyable, SetAccess = protected ) + + % Once the app is created and debugged, consider also protecting + % get access to these properties. Leave them accessible to unit + % tests, however. Use this example: + % GetAccess = {?matlab.uitest.TestCase, ?wt.apps.BaseApp}) + + % Grid Layouts + % (BaseApp already brings "Grid", the main Grid layout in the window) + Tab1Grid matlab.ui.container.GridLayout + Tab2Grid matlab.ui.container.GridLayout + Panel1Grid matlab.ui.container.GridLayout + Panel2Grid matlab.ui.container.GridLayout + + % Panels + Panel1 matlab.ui.container.Panel + Panel2 matlab.ui.container.Panel + + % Tabs + TabGroup matlab.ui.container.TabGroup + Tab1 matlab.ui.container.Tab + Tab2 matlab.ui.container.Tab + + % View Components + %View1 namespace.ClassName + %View2 namespace.ClassName + + % Temporary label components + Panel1Text matlab.ui.control.Label + Panel2Text matlab.ui.control.Label + Tab1Text matlab.ui.control.Label + Tab2Text matlab.ui.control.Label + + end %properties + + + + %% Setup and Configuration of the App + methods ( Access = protected ) + + function setup(app) + % Runs once on instantiation of the app + + % Set the name + app.Name = "My App"; + + % Configure the main grid + app.Grid.ColumnWidth = {300,'1x'}; + app.Grid.RowHeight = {'1x',150}; + app.Grid.Padding = 5; + + % Create a panel + app.Panel1 = uipanel(app.Grid); + app.Panel1.Title = "Panel 1"; + app.Panel1.Layout.Row = [1 2]; + app.Panel1.Layout.Column = 1; + % Create a panel + app.Panel2 = uipanel(app.Grid); + app.Panel2.Title = "Panel 2"; + app.Panel2.Layout.Row = 2; + app.Panel2.Layout.Column = 2; + + % Create a tab group + app.TabGroup = uitabgroup(app.Grid); + app.TabGroup.Layout.Row = 1; + app.TabGroup.Layout.Column = 2; + app.TabGroup.SelectionChangedFcn = @(h,e)onTabChanged(app,e); + + % Create Tabs, each with a grid + app.Tab1 = uitab(app.TabGroup); + app.Tab1.Title = 'Tab 1'; + + app.Tab2 = uitab(app.TabGroup); + app.Tab2.Title = 'Tab 2'; + + % Create grid layouts to position content inside each container + app.Panel1Grid = uigridlayout(app.Panel1, [1,1],"Padding", 0); + app.Panel2Grid = uigridlayout(app.Panel2, [1,1],"Padding", 0); + app.Tab1Grid = uigridlayout(app.Tab1, [1,1],"Padding", 0); + app.Tab2Grid = uigridlayout(app.Tab2, [1,1],"Padding", 0); + + % Place some temporary content in each container + app.Panel1Text = uilabel(app.Panel1Grid); + app.Panel1Text.Text = "Panel 1 Contents"; + app.Panel1Text.HorizontalAlignment = "center"; + app.Panel1Text.FontSize = 30; + app.Panel1Text.Layout.Row = 1; + app.Panel1Text.Layout.Column = 1; + + app.Panel2Text = uilabel(app.Panel2Grid); + app.Panel2Text.Text = "Panel 2 Contents"; + app.Panel2Text.HorizontalAlignment = "center"; + app.Panel2Text.FontSize = 30; + app.Panel2Text.Layout.Row = 1; + app.Panel2Text.Layout.Column = 1; + + app.Tab1Text = uilabel(app.Tab1Grid); + app.Tab1Text.Text = "Tab 1 Contents"; + app.Tab1Text.HorizontalAlignment = "center"; + app.Tab1Text.FontSize = 30; + app.Tab1Text.Layout.Row = 1; + app.Tab1Text.Layout.Column = 1; + + app.Tab2Text = uilabel(app.Tab2Grid); + app.Tab2Text.Text = "Tab 2 Contents"; + app.Tab2Text.HorizontalAlignment = "center"; + app.Tab2Text.FontSize = 30; + app.Tab2Text.Layout.Row = 1; + app.Tab2Text.Layout.Column = 1; + + % Additional examples: + % (add other views, layouts, and components here as needed) + %app.View1 = namespace.ClassName( app.Tab1Grid ); + %app.View2 = namespace.ClassName( app.Tab2Grid ); + + end %function + + end %methods + + %% Update + methods ( Access = protected ) + + function update(app) + % Update the display of the app + % For the main app, app.update() must be called explicitly + % during callbacks or other changes that require contents to + % refresh. + + end %function + + end %methods + + + %% Callbacks + methods + + function onTabChanged(app,evt) + % Triggered on changing tab + + % (this method is optional if using tabs) + + newTab = evt.NewValue; + disp("Selected Tab: " + newTab.Title); + + end %function + + end %methods + +end %classdef \ No newline at end of file diff --git a/widgets/examples/templates/TemplateBaseMultiSessionApp.m b/widgets/examples/templates/TemplateBaseMultiSessionApp.m new file mode 100644 index 00000000..73fda5d6 --- /dev/null +++ b/widgets/examples/templates/TemplateBaseMultiSessionApp.m @@ -0,0 +1,376 @@ +classdef TemplateBaseMultiSessionApp < wt.apps.BaseMultiSessionApp + % Implements a template for a BaseSingleSessionApp + + % Copyright 2022-2025 The MathWorks Inc. + + %% Internal Components + % Create properties here for each control, layout, or view component + % that will be placed directly into the main app window. + properties ( Transient, NonCopyable, SetAccess = protected ) + + % Once the app is created and debugged, consider also protecting + % get access to these properties. Leave them accessible to unit + % tests, however. Use this example: + % GetAccess = {?matlab.uitest.TestCase, ?wt.apps.BaseApp}) + + % Grid Layouts + % (BaseApp already brings "Grid", the main Grid layout in the window) + Tab1Grid matlab.ui.container.GridLayout + Tab2Grid matlab.ui.container.GridLayout + SessionListPanelGrid matlab.ui.container.GridLayout + SessionDescriptionPanelGrid matlab.ui.container.GridLayout + Panel2Grid matlab.ui.container.GridLayout + + % Panels + SessionListPanel matlab.ui.container.Panel + SessionDescriptionPanel matlab.ui.container.Panel + Panel2 matlab.ui.container.Panel + + % Tabs + TabGroup matlab.ui.container.TabGroup + Tab1 matlab.ui.container.Tab + Tab2 matlab.ui.container.Tab + + % Toolbar and sections + Toolbar wt.Toolbar + FileSection wt.toolbar.HorizontalSection + HelpSection wt.toolbar.HorizontalSection + + % Toolbar buttons + NewButton matlab.ui.control.Button + OpenButton matlab.ui.control.Button + SaveButton matlab.ui.control.Button + SaveAsButton matlab.ui.control.Button + CloseButton matlab.ui.control.Button + HelpButton matlab.ui.control.Button + + % View Components + %View1 namespace.ClassName + %View2 namespace.ClassName + + % Session information components + SessionList matlab.ui.control.ListBox + SessionDescription matlab.ui.control.TextArea + + % Temporary label components + Panel2Text matlab.ui.control.Label + Tab1Text matlab.ui.control.Label + Tab2Text matlab.ui.control.Label + + end %properties + + + + %% Setup and Configuration of the App + methods ( Access = protected ) + + function setup(app) + % Runs once on instantiation of the app + + % Set the name + app.Name = "My App"; + + % Configure the main grid + app.Grid.ColumnWidth = {300,'1x'}; + app.Grid.RowHeight = {100,'1x',150}; + app.Grid.Padding = 5; + + % Create toolbar (split out for brevity) + app.createToolbar() + + % Create a panel for session list + app.SessionListPanel = uipanel(app.Grid); + app.SessionListPanel.Title = "Sessions"; + app.SessionListPanel.Layout.Row = 2; + app.SessionListPanel.Layout.Column = 1; + + % Create a panel for session description + app.SessionDescriptionPanel = uipanel(app.Grid); + app.SessionDescriptionPanel.Title = "Session Description:"; + app.SessionDescriptionPanel.Layout.Row = 3; + app.SessionDescriptionPanel.Layout.Column = 1; + + % Create a panel + app.Panel2 = uipanel(app.Grid); + app.Panel2.Title = "Panel 2"; + app.Panel2.Layout.Row = 3; + app.Panel2.Layout.Column = 2; + + % Create a tab group + app.TabGroup = uitabgroup(app.Grid); + app.TabGroup.Layout.Row = 2; + app.TabGroup.Layout.Column = 2; + app.TabGroup.SelectionChangedFcn = @(h,e)onTabChanged(app,e); + + % Create Tabs, each with a grid + app.Tab1 = uitab(app.TabGroup); + app.Tab1.Title = 'Tab 1'; + + app.Tab2 = uitab(app.TabGroup); + app.Tab2.Title = 'Tab 2'; + + % Create grid layouts to position content inside each container + app.SessionListPanelGrid = uigridlayout(... + app.SessionListPanel, [1,1],"Padding", 0); + app.SessionDescriptionPanelGrid = uigridlayout(... + app.SessionDescriptionPanel, [1,1],"Padding", 0); + app.Panel2Grid = uigridlayout(app.Panel2, [1,1],"Padding", 0); + app.Tab1Grid = uigridlayout(app.Tab1, [1,1],"Padding", 0); + app.Tab2Grid = uigridlayout(app.Tab2, [1,1],"Padding", 0); + + % Put a listbox of the sessions + app.SessionList = uilistbox(app.SessionListPanelGrid); + app.SessionList.FontSize = 16; + app.SessionList.ValueChangedFcn = @(~,evt)onSessionListChanged(app,evt); + + % Put a description for the selected session + app.SessionDescription = uitextarea(app.SessionDescriptionPanelGrid); + app.SessionDescription.ValueChangedFcn = ... + @(~,evt)onSessionDescriptionChanged(app,evt); + + % Add contents to other panes + app.Panel2Text = uilabel(app.Panel2Grid); + app.Panel2Text.Text = "Panel 2 Contents"; + app.Panel2Text.HorizontalAlignment = "center"; + app.Panel2Text.FontSize = 30; + app.Panel2Text.Layout.Row = 1; + app.Panel2Text.Layout.Column = 1; + + app.Tab1Text = uilabel(app.Tab1Grid); + app.Tab1Text.Text = "Tab 1 Contents"; + app.Tab1Text.HorizontalAlignment = "center"; + app.Tab1Text.FontSize = 30; + app.Tab1Text.Layout.Row = 1; + app.Tab1Text.Layout.Column = 1; + + app.Tab2Text = uilabel(app.Tab2Grid); + app.Tab2Text.Text = "Tab 2 Contents"; + app.Tab2Text.HorizontalAlignment = "center"; + app.Tab2Text.FontSize = 30; + app.Tab2Text.Layout.Row = 1; + app.Tab2Text.Layout.Column = 1; + + % Additional examples: + % (add other views, layouts, and components here as needed) + %app.View1 = namespace.ClassName( app.Tab1Grid ); + %app.View2 = namespace.ClassName( app.Tab2Grid ); + + end %function + + + function createToolbar(app) + % Create the toolbar contents + + % (This method is optional if using a toolbar. It's split out + % into the separate method here to keep the setup method + % shorter) + + % Create the toolbar container + app.Toolbar = wt.Toolbar(app.Grid); + app.Toolbar.Layout.Row = 1; + app.Toolbar.Layout.Column = [1 2]; + + % File Section + app.FileSection = wt.toolbar.HorizontalSection(); + app.FileSection.Title = "FILE"; + app.FileSection.ButtonPushedFcn = @(~,evt)onFileToolbarButtonPushed(app,evt); + + app.NewButton = app.FileSection.addButton('add_24.png','New Session'); + app.OpenButton = app.FileSection.addButton('folder_24.png','Open Session'); + app.SaveButton = app.FileSection.addButton('save_24.png','Save Session'); + app.SaveAsButton = app.FileSection.addButton("saveClean_24.png","Save As"); + app.CloseButton = app.FileSection.addButton("close_24.png","Close Session"); + + app.FileSection.ComponentWidth(:) = 55; + + % Help Section + app.HelpSection = wt.toolbar.HorizontalSection(); + app.HelpSection.Title = "HELP"; + app.HelpButton = app.HelpSection.addButton('help_24.png','Help'); + app.HelpButton.ButtonPushedFcn = @(h,e)onHelpButton(app); + + % Add all toolbar sections to the toolbar + % This is done last for performance reasons + app.Toolbar.Section = [ + app.FileSection + app.HelpSection + ]; + + end %function + + + function sessionObj = createNewSession(~) + % Create and return a new session object for this app + + % The session should be a class that inherits wt.model.BaseSession + %sessionObj = namespace.SessionClassName; + + % For example purposes, using this one: + sessionObj = wt.model.BaseSession; + + end %function + + end %methods + + %% Update + methods ( Access = protected ) + + function update(app) + % Update the display of the app + % For the main app, app.update() must be called explicitly + % during callbacks or other changes that require contents to + % refresh. + + % Get selected session + selSession = app.SelectedSession; + + % Update the session list + app.SessionList.Items = app.SessionDisplayNames; + app.SessionList.ItemsData = app.Session; + if isempty(selSession) || ~ismember(selSession, app.Session) + app.SessionList.Value = {}; + else + app.SessionList.Value = selSession; + end + + % Update the session description + if isempty(selSession) + app.SessionDescription.Value = ""; + app.SessionDescription.Enable = false; + app.SessionDescription.UserData = []; + else + app.SessionDescription.Value = selSession.Description; + app.SessionDescription.Enable = true; + app.SessionDescription.UserData = selSession; + end + + % Update toolbar button enables + app.updateToolbarEnables() + + % Examples: + %app.View1.Model = sessionObj; + %app.View2.Model = sessionObj; + + end %function + + + function updateToolbarEnables(app) + + % Get the session states + selSession = app.SelectedSession; + hasSelectedSession = isscalar(selSession); + hasDirtySession = hasSelectedSession && selSession.Dirty; + + % File toolbar enables + app.SaveButton.Enable = hasDirtySession; + app.SaveAsButton.Enable = hasSelectedSession; + app.CloseButton.Enable = hasSelectedSession; + + end %function + + end %methods + + + %% Callbacks + methods + + function onTabChanged(app,evt) + % Triggered on changing tab + + % (this method is optional if using tabs) + + newTab = evt.NewValue; + disp("Selected Tab: " + newTab.Title); + + end %function + + + function onSessionListChanged(app,evt) + % Triggered on selecting from the session list + + % (this method is optional if using tabs) + + % What was selected? + newValue = evt.Value; + + % Select the session + app.selectSession(newValue); + + % Update the app + app.update(); + + end %function + + + function onSessionDescriptionChanged(app,evt) + % Triggered on editing a session description + + % Get the new value + newValue = join(string(evt.Value), newline); + + % Get the session tied to the field + % Otherwise, if you click off the field immediately to select + % another session, it edits the wrong session! + session = app.SessionDescription.UserData; + + % Update the session description + session.Description = newValue; + + end %function + + + function onFileToolbarButtonPushed(app,evt) + + % Which button was pressed? + switch evt.Button + + case app.NewButton + + % Add a new session + session = app.newSession(); + + % Select the new session + if ~isempty(session) + app.selectSession(session); + end + + case app.OpenButton + + % Prompt and load a session + session = app.loadSession(); + + % Select the new session + if ~isempty(session) + app.selectSession(session); + end + + case app.SaveButton + + % Save the selected session + app.saveSession(false); + + case app.SaveAsButton + + % Save the selected session as different file + app.saveSession(true); + + case app.CloseButton + + % Close the currently selected session + app.closeSession(); + + end %switch + + end %function + + + function onHelpButton(app) + % Triggered when the toolbar button is pressed + + disp("Help Button Pushed!"); + + end %function + + end %methods + +end %classdef \ No newline at end of file diff --git a/widgets/examples/templates/TemplateBaseSingleSessionApp.m b/widgets/examples/templates/TemplateBaseSingleSessionApp.m new file mode 100644 index 00000000..6b10fc0f --- /dev/null +++ b/widgets/examples/templates/TemplateBaseSingleSessionApp.m @@ -0,0 +1,301 @@ +classdef TemplateBaseSingleSessionApp < wt.apps.BaseSingleSessionApp + % Implements a template for a BaseSingleSessionApp + + % Copyright 2022-2025 The MathWorks Inc. + + + %% Internal Components + % Create properties here for each control, layout, or view component + % that will be placed directly into the main app window. + properties ( Transient, NonCopyable, SetAccess = protected ) + + % Once the app is created and debugged, consider also protecting + % get access to these properties. Leave them accessible to unit + % tests, however. Use this example: + % GetAccess = {?matlab.uitest.TestCase, ?wt.apps.BaseApp}) + + % Grid Layouts + % (BaseApp already brings "Grid", the main Grid layout in the window) + Tab1Grid matlab.ui.container.GridLayout + Tab2Grid matlab.ui.container.GridLayout + SessionDescriptionPanelGrid matlab.ui.container.GridLayout + Panel2Grid matlab.ui.container.GridLayout + + % Panels + SessionDescriptionPanel matlab.ui.container.Panel + Panel2 matlab.ui.container.Panel + + % Tabs + TabGroup matlab.ui.container.TabGroup + Tab1 matlab.ui.container.Tab + Tab2 matlab.ui.container.Tab + + % Toolbar and sections + Toolbar wt.Toolbar + FileSection wt.toolbar.HorizontalSection + HelpSection wt.toolbar.HorizontalSection + + % Toolbar buttons + NewButton matlab.ui.control.Button + OpenButton matlab.ui.control.Button + SaveButton matlab.ui.control.Button + SaveAsButton matlab.ui.control.Button + ExportGTButton matlab.ui.control.Button + HelpButton matlab.ui.control.Button + + % View Components + %View1 namespace.ClassName + %View2 namespace.ClassName + + % Session information components + SessionDescription matlab.ui.control.TextArea + + % Temporary label components + Panel1Text matlab.ui.control.Label + Panel2Text matlab.ui.control.Label + Tab1Text matlab.ui.control.Label + Tab2Text matlab.ui.control.Label + + end %properties + + + + %% Setup and Configuration of the App + methods ( Access = protected ) + + function setup(app) + % Runs once on instantiation of the app + + % Set the name + app.Name = "My App"; + + % Configure the main grid + app.Grid.ColumnWidth = {300,'1x'}; + app.Grid.RowHeight = {100,'1x',150}; + app.Grid.Padding = 5; + + % Create toolbar (split out for brevity) + app.createToolbar() + + % Create a panel for session description + app.SessionDescriptionPanel = uipanel(app.Grid); + app.SessionDescriptionPanel.Title = "Session Description:"; + app.SessionDescriptionPanel.Layout.Row = [2 3]; + app.SessionDescriptionPanel.Layout.Column = 1; + + % Create a panel + app.Panel2 = uipanel(app.Grid); + app.Panel2.Title = "Panel 2"; + app.Panel2.Layout.Row = 3; + app.Panel2.Layout.Column = 2; + + % Create a tab group + app.TabGroup = uitabgroup(app.Grid); + app.TabGroup.Layout.Row = 2; + app.TabGroup.Layout.Column = 2; + app.TabGroup.SelectionChangedFcn = @(h,e)onTabChanged(app,e); + + % Create Tabs, each with a grid + app.Tab1 = uitab(app.TabGroup); + app.Tab1.Title = 'Tab 1'; + + app.Tab2 = uitab(app.TabGroup); + app.Tab2.Title = 'Tab 2'; + + % Create grid layouts to position content inside each container + app.SessionDescriptionPanelGrid = uigridlayout(... + app.SessionDescriptionPanel, [1,1],"Padding", 0); + app.Panel2Grid = uigridlayout(app.Panel2, [1,1],"Padding", 0); + app.Tab1Grid = uigridlayout(app.Tab1, [1,1],"Padding", 0); + app.Tab2Grid = uigridlayout(app.Tab2, [1,1],"Padding", 0); + + % Put a description for the selected session + app.SessionDescription = uitextarea(app.SessionDescriptionPanelGrid); + app.SessionDescription.ValueChangedFcn = ... + @(~,evt)onSessionDescriptionChanged(app,evt); + + % Place some temporary content in each container + app.Panel2Text = uilabel(app.Panel2Grid); + app.Panel2Text.Text = "Panel 2 Contents"; + app.Panel2Text.HorizontalAlignment = "center"; + app.Panel2Text.FontSize = 30; + app.Panel2Text.Layout.Row = 1; + app.Panel2Text.Layout.Column = 1; + + app.Tab1Text = uilabel(app.Tab1Grid); + app.Tab1Text.Text = "Tab 1 Contents"; + app.Tab1Text.HorizontalAlignment = "center"; + app.Tab1Text.FontSize = 30; + app.Tab1Text.Layout.Row = 1; + app.Tab1Text.Layout.Column = 1; + + app.Tab2Text = uilabel(app.Tab2Grid); + app.Tab2Text.Text = "Tab 2 Contents"; + app.Tab2Text.HorizontalAlignment = "center"; + app.Tab2Text.FontSize = 30; + app.Tab2Text.Layout.Row = 1; + app.Tab2Text.Layout.Column = 1; + + % Additional examples: + % (add other views, layouts, and components here as needed) + %app.View1 = namespace.ClassName( app.Tab1Grid ); + %app.View2 = namespace.ClassName( app.Tab2Grid ); + + end %function + + + function createToolbar(app) + % Create the toolbar contents + + % (This method is optional if using a toolbar. It's split out + % into the separate method here to keep the setup method + % shorter) + + % Create the toolbar container + app.Toolbar = wt.Toolbar(app.Grid); + app.Toolbar.Layout.Row = 1; + app.Toolbar.Layout.Column = [1 2]; + + % File Section + app.FileSection = wt.toolbar.HorizontalSection(); + app.FileSection.Title = "FILE"; + app.FileSection.ButtonPushedFcn = @(~,evt)onFileToolbarButtonPushed(app,evt); + + app.NewButton = app.FileSection.addButton('add_24.png','New Session'); + app.OpenButton = app.FileSection.addButton('folder_24.png','Open Session'); + app.SaveButton = app.FileSection.addButton('save_24.png','Save Session'); + app.SaveAsButton = app.FileSection.addButton("saveClean_24.png","Save As"); + app.FileSection.ComponentWidth(:) = 55; + + % Help Section + app.HelpSection = wt.toolbar.HorizontalSection(); + app.HelpSection.Title = "HELP"; + app.HelpButton = app.HelpSection.addButton('help_24.png','Help'); + app.HelpButton.ButtonPushedFcn = @(h,e)onHelpButton(app); + + % Add all toolbar sections to the toolbar + % This is done last for performance reasons + app.Toolbar.Section = [ + app.FileSection + app.HelpSection + ]; + + end %function + + + function sessionObj = createNewSession(~) + % Create and return a new session object for this app + + % The session should be a class that inherits wt.model.BaseSession + %sessionObj = namespace.SessionClassName; + + % For example purposes, using this one: + sessionObj = wt.model.BaseSession; + + end %function + + end %methods + + %% Update + methods ( Access = protected ) + + function update(app) + % Update the display of the app + % For the main app, app.update() must be called explicitly + % during callbacks or other changes that require contents to + % refresh. + + % Update the session description text + app.SessionDescription.Value = app.Session.Description; + + % Update toolbar button enables + app.updateToolbarEnables() + + % Examples: + %app.View1.Model = sessionObj; + %app.View2.Model = sessionObj; + + end %function + + + function updateToolbarEnables(app) + + % Get the session states + hasDirtySession = isscalar(app.Session) && app.Session.Dirty; + + % File toolbar enables + app.SaveButton.Enable = hasDirtySession; + + end %function + + end %methods + + + %% Callbacks + methods + + function onTabChanged(app,evt) + % Triggered on changing tab + + % (this method is optional if using tabs) + + newTab = evt.NewValue; + disp("Selected Tab: " + newTab.Title); + + end %function + + + function onSessionDescriptionChanged(app,evt) + % Triggered on editing a session description + + % Get the new value + newValue = join(string(evt.Value), newline); + + % Update the session description + app.Session.Description = newValue; + + end %function + + + function onFileToolbarButtonPushed(app,evt) + + % Which button was pressed? + switch evt.Button + + case app.NewButton + + % Add a new session + app.newSession(); + + case app.OpenButton + + % Prompt and load a session + app.loadSession(); + + case app.SaveButton + + % Save the selected session + app.saveSession(false); + + case app.SaveAsButton + + % Save the selected session as different file + app.saveSession(true); + + end %switch + + end %function + + + function onHelpButton(app) + % Triggered when the toolbar button is pressed + + % (this method is optional if using the toolbar) + + disp("Help Button Pushed!"); + + end %function + + end %methods + +end %classdef \ No newline at end of file