classdef ViewerPresenter < handle & ros.internal.mixin.ROSInternalAccess
    %This class is for internal use only. It may be removed in the future.

    %   Copyright 2022-2024 The MathWorks, Inc.

    properties (Access = ?ros.internal.mixin.ROSInternalAccess)
        % Shared object for notifying and listening for events
        EventObject

        % UI elements
        AppContainer
        Toolstrip
        TopicList
        VisualizerDocGroup
        Timeline
        UIAlert
        UIProgress

        % Structure mapping data sources to the visualizer to update
        VisualizerMapping

        % Information on the structure of the topic and message types
        TopicTree

        % Indicator if app is currently in playback mode (false = paused)
        PlaybackState = false

        PlaybackDirection = 1

        % This timer controls the playback events
        PlaybackTimer

        % Previously used bagfile path
        LastOpenedBagPath

        % To determine Automatic Topic for playback
        AutomaticTopic

        % Save Cache related
        LayoutFromCache
        VisualizerInfo
        RestoringCache = false
        SkipUpdateCache = false
        CacheLoaded = false
        % Bookmark
        BookmarkTabObj
        BookmarkPanelObj
        BookmarkDataFromSession

        % tag
        AddTagsPanelObj
        AddTagsTableObj
        TagsDataFromSession
        
        % Mode of the app : Live topic / bag file visualization 
        AppMode = ""

        % Search ROS bag App
        SearchRosbagAppObj

        % Flag to check if the current event handler is in progress
        ProcessingFlag = false;
    end

    properties(Access = ?matlab.unittest.TestCase)
        EnableSaveAppState = true % switch for testing
    end

    properties (Hidden)
        UniqueTagApp
        UniqueTimerName
        CacheDataForSearch
    end

    properties (Constant, Access = ?ros.internal.mixin.ROSInternalAccess)
        TagAppContainer = "RosbagViewerAppContainer"
        TagDocGroup = "RosbagViewerDocGroup"

        % Minimal pause time to give up the thread
        PauseTime = 0.03

        % Preference group for MATLAB preference
        PrefGroup = 'ROS_Toolbox'

        % Previously used bag path
        LastUsedBagPathPrefName = 'VIS_APP_LAST_USED_BAG_PATH'

        LastUsedROSMasterURIs = 'VIS_APP_LAST_USED_ROS_MASTER_URIS'

        LastUsedROSDomainIDs = 'VIS_APP_LAST_USED_ROS_DOMAIN_IDS'


        % PlaybackTimer Name
        PlaybackTimerName = "RosbagViewerPlaybackTimer"

        % Represents forward playback mode
        ForwardPlayback = 1

        % Represents reverse playback mode
        ReversePlayback = -1

        %Automatic Topic Priority List for playback
        AutomaticTopicPriorityList = ["image", "pointcloud", "laserscan", "odometry"];
    end

    properties (Constant)
        RosbagVisualization = "RosbagVisualization"
        LiveRosTopicVisualization = "LiveRosTopicVisualization"
    end

    methods
        function obj = ViewerPresenter(eventObject)
            %RosbagViewer Create UI elements for initial display
            %   Also prepare to react to events and UI interactions.

            % Parse input
            narginchk(1, 1)
            validateattributes(eventObject, ...
                "ros.internal.RosbagViewerEvents", ...
                "scalar", ...
                "ViewerPresenter", ...
                "eventObject")
            obj.EventObject = eventObject;

            % This will generate unique tag and timer name where multiple
            % sessions are launched.
            %it is required that each app session has a unique tag
            persistent appIdx;
            if isempty(appIdx)
                appIdx = 1;
            end
            obj.UniqueTagApp = obj.TagAppContainer + num2str(appIdx);
            obj.UniqueTimerName = obj.PlaybackTimerName + num2str(appIdx) + ...
                string(datetime("now","Format","ddHHmmssSSSS"));

            % Construct the app
            createAppContainerAndContents(obj);

            % Create UI dlgs for App
            obj.UIAlert = ros.internal.view.UIAlert();
            obj.UIProgress = ros.internal.view.UIProgressdlg();

            % Initialize mapping of visualizer data sources
            obj.VisualizerMapping = ...
                struct("ID", [], "Handle", [], "Topic", string.empty, ...
                "DataSource", string.empty, "DataType", string.empty);
            obj.VisualizerMapping(1) = [];

            obj.PlaybackTimer = timer('Name', obj.UniqueTimerName, ...
                'Period', obj.PauseTime, 'ExecutionMode', 'fixedSpacing');

            % Listen to events
            setupCallbacksListeners(obj)

            % Make the app visible and accessible
            obj.AppContainer.Visible = true;
            % % collapse the bookmark by default
            % waitfor(obj.AppContainer, 'State', matlab.ui.container.internal.appcontainer.AppState.RUNNING);
            % obj.AppContainer.RightCollapsed = 1;

            % Load last time used bag path from preference
            if ispref(obj.PrefGroup,obj.LastUsedBagPathPrefName)
                obj.LastOpenedBagPath = getpref(obj.PrefGroup,obj.LastUsedBagPathPrefName);
            else
                obj.LastOpenedBagPath = '';
            end
            appIdx = appIdx+1;
        end

        function delete(obj)
            %delete Close the app and free up resources

            import matlab.ui.container.internal.appcontainer.AppState;

            weakHndlObj = matlab.internal.WeakHandle(obj);
            ros.internal.ViewerPresenter.cleanupAndCloseApp(weakHndlObj);

            if isvalid(obj.AppContainer) && ...
                    ~(obj.AppContainer.State == AppState.TERMINATED)
                obj.AppContainer.CanCloseFcn = [];
                close(obj.AppContainer);
            end
        end
    end

    methods (Access = ?ros.internal.mixin.ROSInternalAccess)    % Helper methods
        function addDataSourceToMapping(obj, id, hVisualizer, dataType)
            %addDataSourceToMapping Add new data source for tracking
            %   The ID is unique to that specific data source
            %   The handle is to the visualizer with that data source
            %   The data type refers to the type of data to be displayed

            % Data source should initially be empty
            % Just set ID, handle, and data type (based on visualizer type)
            obj.VisualizerMapping(end+1) = ...
                struct("ID", id, "Handle", hVisualizer, "Topic", "", ...
                "DataSource", "", "DataType", dataType);
        end

        function sendSourceListToModel(obj)
            %sendSourceListToModel Sends the sources and their types to model.
            %Usually this should be invoked after a change in the source list
            %to notify model.
            mappingTable = table(vertcat(obj.VisualizerMapping.DataSource), ...
                vertcat(obj.VisualizerMapping.DataType));
            mappingTable.Properties.VariableNames = ["DataSource", "DataType"];
            whichEmpty = strcmp(mappingTable.DataSource, "");
            mappingTable(whichEmpty, :) = [];
            eventDataOut = ros.internal.EventDataContainer(mappingTable);
            notify(obj.EventObject, "DataSourcesRequiredPM", eventDataOut)
        end

        function createAppContainerAndContents(obj)
            %createAppContainerAndContents Initialize the App UI

            appOptions = struct("Tag", obj.UniqueTagApp, ...
                "Title", getString(message("ros:visualizationapp:view:AppTitle")), ...
                "EnableTheming", true);

            obj.AppContainer = matlab.ui.container.internal.AppContainer(appOptions);
            appWindowBounds = ros.internal.utils.getWindowBounds();
            obj.AppContainer.WindowBounds = appWindowBounds;
            %Add a global toolstrip tab group
            obj.Toolstrip = ros.internal.ViewerToolstrip(obj.AppContainer);

            % Add a document group
            obj.VisualizerDocGroup = matlab.ui.internal.FigureDocumentGroup("Tag", obj.TagDocGroup);
            add(obj.AppContainer, obj.VisualizerDocGroup);

            % Add the topic tree panel to the left
            obj.TopicList = ros.internal.ViewerTopicList(obj.AppContainer);

            % Add the timeline panel to the bottom
            obj.Timeline = ros.internal.ViewerTimeline(obj.AppContainer);

            obj.BookmarkTabObj = ros.internal.model.BookmarkTable();
            obj.BookmarkPanelObj = ros.internal.view.ViewerBookmark(obj.AppContainer, obj.BookmarkTabObj.BookmarkTableList);
            
            % Add the tag panel
            obj.AddTagsPanelObj = ros.internal.view.ViewerTags(obj.AppContainer);

        end

        function setupCallbacksListeners(obj)
            %setupCallbacksListeners Enable UI interaction and event reaction

            objWeakHndl = matlab.internal.WeakHandle(obj);

            % Set up initial UI for interaction
            % toolstrip widgets callbacks
            obj.Toolstrip.OpenFileCallback = @(~, ~) ros.internal.ViewerPresenter.openRosbag(objWeakHndl);
            obj.Toolstrip.OpenROSMasterURICallback = @(~, ~) ros.internal.ViewerPresenter.inputRosNetwork(objWeakHndl,'ros1');
            obj.Toolstrip.OpenROS2DomainIDCallback = @(~, ~) ros.internal.ViewerPresenter.inputRosNetwork(objWeakHndl,'ros2');
            obj.Toolstrip.SearchCallback = @(~,~) ros.internal.ViewerPresenter.searchRosbagCallback(objWeakHndl);
            obj.TopicList.RefreshCallback = @(~, ~) ros.internal.ViewerPresenter.refreshTopicList(objWeakHndl);
            obj.Toolstrip.ImageViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.ImageVisualizer, "image", false);
            obj.Toolstrip.PointCloudViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.PointCloudVisualizer, "pointcloud", false);
            obj.Toolstrip.LaserScanViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.LaserScanVisualizer, "laserscan", false);
            obj.Toolstrip.OdometryViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.OdometryVisualizer, "odometry", true);
            obj.Toolstrip.XYPlotViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.XYPlotVisualizer, "numeric", true);
            obj.Toolstrip.TimePlotViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.TimePlotVisualizer, "numeric", true);
            obj.Toolstrip.MessageViewerCallback = @(~, ~) ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, @ros.internal.MessageVisualizer, "message", false);
            obj.Toolstrip.GridLayoutCallback = @(source, eventData) ros.internal.ViewerPresenter.gridlayout(objWeakHndl, source, eventData);
            obj.Toolstrip.DefaultLayoutCallback = @(source, eventData) ros.internal.ViewerPresenter.gridlayout(objWeakHndl, source, eventData);
            obj.Toolstrip.AddBookmarkCallback = @(~, ~) ros.internal.ViewerPresenter.addBookmark(objWeakHndl);
            obj.Toolstrip.AddTagCallback = @(~, ~) ros.internal.ViewerPresenter.addTagsToRosbag(objWeakHndl);
            obj.Toolstrip.ManageBookmarkCallback = @(~, ~) ros.internal.ViewerPresenter.manageBookmark(objWeakHndl);
            obj.Toolstrip.PlayCallback = @(~, ~) ros.internal.ViewerPresenter.playPausePushed(objWeakHndl);
            %timeline widgets callbacks
            obj.Timeline.SliderValueChangedCallback = @(source, ~) ros.internal.ViewerPresenter.timelineValueChanged(objWeakHndl, source);
            obj.Timeline.TimeTypeValueChangedCallback   = @(source, event) ros.internal.ViewerPresenter.updateCurrentTimeFieldChanged(objWeakHndl, source, event);
            obj.Timeline.SliderValueChangingCallback = @(source, ~) ros.internal.ViewerPresenter.timelineValueChanging(objWeakHndl, source);
            obj.Timeline.TimeFieldValueChangedCallback = @(source, event) ros.internal.ViewerPresenter.currentTimeFieldChanged(objWeakHndl, source, event);
            obj.Timeline.PlayCallback = @(source, ~) ros.internal.ViewerPresenter.playPausePushed(objWeakHndl, source);
            obj.Timeline.NextCallback = @(~, ~) ros.internal.ViewerPresenter.nextPushed(objWeakHndl);
            obj.Timeline.PreviousCallback = @(~, ~) ros.internal.ViewerPresenter.previousPushed(objWeakHndl);
            obj.Timeline.ShowBookmarkValueChangedFcn = @(~, ~) ros.internal.ViewerPresenter.showBookmarkOnTimeline(objWeakHndl);
            
            obj.AppContainer.CanCloseFcn = @(~, ~) ros.internal.ViewerPresenter.cleanupAndCloseApp(objWeakHndl);
            obj.PlaybackTimer.TimerFcn = @(source,~)ros.internal.ViewerPresenter.playBackTimerCallback(objWeakHndl,source);

            % Set up to react to events
            addlistener(obj.EventObject, "RosbagLoadedMP", @(~, eventData) updateAppWithLoadedRosbag(obj, eventData));
            addlistener(obj.EventObject, "DataForTimeMP", @(~, eventData) updateVisualizers(obj, eventData));
            addlistener(obj.EventObject, "DataForTimeRangeMP", @(~, eventData) updateVisualizersWithDataRange(obj, eventData));
			addlistener(obj.EventObject, "TopicsInfoMP", @(~, eventData) updateTopicInfo(obj, eventData));

            % Setup Layout
            addlistener(obj.EventObject, 'ReturnAppSessionCacheDataMP', @(~, eventData) getAppCacheData(obj, eventData));
            %Bookmark widget callback
            obj.BookmarkPanelObj.TableSelectionChangedFcn = @(source, event) ros.internal.ViewerPresenter.bmTableSelectionChanged(objWeakHndl, source, event);
            obj.BookmarkPanelObj.TableCellSelectionCallback = @(source, event) ros.internal.ViewerPresenter.bmTableSelectionChanged(objWeakHndl, source, event);
            obj.BookmarkPanelObj.TableCellEditCallback = @(source, event) ros.internal.ViewerPresenter.bmTableCellEditCallback(objWeakHndl, source, event);
            obj.BookmarkPanelObj.TableClickedFcn = @(source, event) ros.internal.ViewerPresenter.bmTableClickedFcn(objWeakHndl, source, event);

            %Tag rosbag  callback
            obj.AddTagsPanelObj.TagEditFieldCallback = @(source, event) ros.internal.ViewerPresenter.tagsAddCallback(objWeakHndl, source, event);
            
        end

        function requestDataForTime(obj, t)
            %requestDataForTime Inform model time has changed

            eventDataOut = ros.internal.EventDataContainer(t);
            notify(obj.EventObject, "CurrentTimeChangedPM", eventDataOut)
        end

        function stepTime(obj, direction)
            %stepTime Move to a new message based on current position
            %   The next time depends on the provided direction, selected main
            %   signal, and selected rate.
            %   direction = 1 for forward, -1 for backwards

            if isequal(obj.AppMode,obj.RosbagVisualization)
                % get current time
                t = getCurrentTime(obj.Timeline);
    
                % get current speed
                rate = getRate(obj.Timeline);
    
                % get the reference signal
                [referenceTopicIdx, referenceTopic] = getSelectedSignal(obj.Timeline);
                if referenceTopicIdx == 0
                   referenceTopic = obj.AutomaticTopic;
                end
    
                % Send request to determine the next message to the model
                if ~isempty(referenceTopic)
                    %Don't send any request to model if reference topic is
                    %empty. It could happen when all visualizers are closed and
                    %reference topic is Automatic.
                    eventDataOutStruct = struct("CurrentTime", t, ...
                        "Direction", direction, ...
                        "Rate", rate, ...
                        "MainSignal", referenceTopic);
                    eventDataOut = ros.internal.EventDataContainer(eventDataOutStruct);
    
                    notify(obj.EventObject, "MoveToNextMessagePM", eventDataOut);
                elseif obj.PlaybackState
                    %If playback is going on and reference topic is empty due
                    %to all visualizers are closed, stop the playback
                    stopPlayBack(obj);
                end
            else
                eventDataOutStruct = struct();
                eventDataOut = ros.internal.EventDataContainer(eventDataOutStruct);
                notify(obj.EventObject, "MoveToNextMessagePM", eventDataOut);
            end
        end

        function updateGraphicHandleIdx(obj)
            for iVis = 1:numel(obj.VisualizerMapping)
                if isprop(obj.VisualizerMapping(iVis).Handle, 'GraphicHandleIdx')
                    updateGraphicHandleIdx(obj.VisualizerMapping(iVis).Handle);
                end
            end
        end

        function resetCacheProperties(obj)
            obj.TagsDataFromSession = [];
            obj.BookmarkDataFromSession = [];
            obj.VisualizerInfo = [];
            obj.LayoutFromCache = [];
        end

    end

    methods (Access = ?ros.internal.mixin.ROSInternalAccess)    % Event callbacks
        function automaticTopic = getReferenceTopicForAutomatic(obj)
            %getReferenceTopicForAutomatic - algorithm to determine
            %automatic reference topic.

            % if no visualizer loaded then return empty
            if isempty(obj.VisualizerMapping)
                automaticTopic = '';
                return;
            end
            % filter out the visualizer where data source is not selected
            visualizerList = obj.VisualizerMapping([obj.VisualizerMapping.Topic] ~="");
            if isempty(visualizerList)
                automaticTopic = '';
                return;
            end
            % if only one visualizer has datasource is selected
            automaticTopic = visualizerList(1).Topic;
            % if more than one visualizer then prioritize as in order
            % ["image", "pointcloud", "laserscan", "odometry"]
            if numel(visualizerList) >1
                for tindx = 1:numel(obj.AutomaticTopicPriorityList)
                    % prioritize as in order ["image", "pointcloud", "laserscan", "odometry"]
                    if any(ismember([visualizerList.DataType], obj.AutomaticTopicPriorityList(tindx)))
                        % find if there are more than one vis with same
                        % datatype
                        indexes = [visualizerList.DataType] == obj.AutomaticTopicPriorityList(tindx);
                        % get min freq when multiple visualizer with
                        % same data type
                        topicsWithMinFreq = obj.TopicList.getFrequencyForTopic([visualizerList(indexes).Topic]);
                        % returns the logical array where the condition is
                        % satisfied.for example [20 20 10] if searching for
                        % 10 it returns [0 0 1]
                        minFreqTopics = [topicsWithMinFreq.Frequency] == min([topicsWithMinFreq.Frequency]);
                        % matlab will select indexes which has boolean
                        automaticTopic = topicsWithMinFreq(minFreqTopics).Topic;
                        return;
                    end
                end
                topicsWithMinFreq = obj.TopicList.getFrequencyForTopic([visualizerList(:).Topic]);
                % returns the logical array where the condition is
                % satisfied.for example [20 20 10] if searching for
                % 10 it returns [0 0 1]
                minFreqTopics = [topicsWithMinFreq.Frequency] == min([topicsWithMinFreq.Frequency]);
                % matlab will select indexes which has boolean
                automaticTopic = topicsWithMinFreq(minFreqTopics).Topic;
            end
        end

        function changeAppMode(obj)
            obj.TopicList.setAppMode(obj.AppMode)
            obj.Toolstrip.setAppMode(obj.AppMode)
            obj.BookmarkPanelObj.setAppMode(obj.AppMode)
            obj.AddTagsPanelObj.setAppMode(obj.AppMode)
            obj.Timeline.setAppMode(obj.AppMode)
        end

		function updateTopicInfo(obj, eventData)
            
            if ~isempty(eventData.Data.Error)
                throwError(obj, eventData.Data.Error);
            else
                if obj.EnableSaveAppState
                    if ~isempty(obj.TopicTree) && obj.CacheLoaded 
                        %obj.updateSaveAppStateFile();
                        listofopenvisualizer = obj.VisualizerMapping;
                        obj.SkipUpdateCache = true; % skip updating the cache file
                        c = onCleanup(@()setrestoring(obj, 'SkipUpdateCache', false));
                        for indx =1:numel(listofopenvisualizer)
                            if isvalid(listofopenvisualizer(indx).Handle)
                                listofopenvisualizer(indx).Handle.Document.close; % close all visualizers if any
                            end
                        end
                        if isequal(obj.AppMode,obj.RosbagVisualization)
                            obj.BookmarkPanelObj.resetBookmarkTable;
                            obj.BookmarkTabObj.resetTable;
                        end
                        obj.AppContainer.DocumentGridDimensions = [1 1];
                        obj.LayoutFromCache = [];
                        obj.VisualizerInfo = [];
                        obj.CacheLoaded =  false;
                    end
                end
                obj.AppMode = obj.LiveRosTopicVisualization;
                changeAppMode(obj);

                obj.TopicTree = eventData.Data.Tree;
                createTopicTreeForLive(obj.TopicList, eventData.Data);
                % refresh the DataSource dropdown and VisualizerMapping
                resetVisualizerMappingAndDataSource(obj,false);
    
                if isequal(eventData.Data.rosVer,'ros1')
                    networkDetails = ['ROS_MASTER_URI : ' eventData.Data.rosNetworkInput];
                else
                    networkDetails = ['ROS_DOMAIN_ID : ' num2str(eventData.Data.rosNetworkInput)];
                end

                obj.AppContainer.Title = [getString(message("ros:visualizationapp:view:AppTitle")) ' - ' networkDetails];

                if eventData.Data.HasCache
                    % parse the cache file data
                    obj.getAppCacheData(eventData.Data.CacheData);
                    % create visualizer from cache data
                    createVisualizerUsingCacheData(obj);
                end
                %obj.startPlayBack
            end
        end

        function updateAppWithLoadedRosbag(obj, eventData)
            %updateAppWithLoadedRosbag will update all the UI components of
            %the app with the loaded rosbag data.

            if ~isempty(eventData.Data.Error)
                throwError(obj, eventData.Data.Error);
            else
                obj.AppMode = obj.RosbagVisualization;
                changeAppMode(obj)
                obj.TopicTree = eventData.Data.Tree;
                % update TopicList Panel
                createTopicTree(obj.TopicList, obj.TopicTree);
                % refresh the DataSource dropdown and VisualizerMapping
                resetVisualizerMappingAndDataSource(obj);
                % reset the Time for NewBag
                resetTimeForNewBag(obj, eventData);
                % reset Timeline accordingly
                obj.Timeline.updateTimeSettings;
                % reset the automatic signal
                obj.AutomaticTopic = '';
                % parse the cache file data
                obj.getAppCacheData(eventData.Data.CacheData);
                % create visualizer from cache data
                createVisualizerUsingCacheData(obj);
            end
        end

        function resetVisualizerMappingAndDataSource(obj, ifResetVisualizer)
            %resetDataSource will referesh the DataSource dropdown items
            %for all the open visulizier with the loaded rosbag file
            
            if nargin < 2
                ifResetVisualizer = false;
            end

            if ~isempty(obj.VisualizerMapping)
                for indx = 1: length(obj.VisualizerMapping)
                    visualizer = obj.VisualizerMapping(indx).Handle;
                    % reset the dropdown
                    updateDataSourceOptions(visualizer, obj.TopicTree);
                    
                    if ifResetVisualizer
                        % reset the graphics in the visualizer
                        visualizer.reinit();
                        % reset the VisualizerMapping Fields
                        obj.VisualizerMapping(indx).Topic = "";
                        obj.VisualizerMapping(indx).DataSource = "";
                    end
                end
            end
        end

        function createVisualizerUsingCacheData(obj)
            %createVisualizerUsingCacheData method is used to create the
            %visualizer and set the data source from the cache data
            
            % if the layout is not empty
            if isempty(obj.LayoutFromCache)
                obj.CacheLoaded =  true;
                return;
            else
                
                obj.RestoringCache = true; % set the flag that it is restoring the cache to avoid updating the cache file
                c = onCleanup(@()setrestoring(obj, 'RestoringCache', false));
                %get the original layout setting to reset incase there is an error
                originalgridDimension = obj.AppContainer.DocumentGridDimensions;
                originallayoutjson = obj.AppContainer.DocumentLayout;
                try
                    % set the layout
                    obj.AppContainer.DocumentGridDimensions = obj.LayoutFromCache.DocumentGridDimensions;
                    obj.AppContainer.DocumentLayout = obj.LayoutFromCache.LayoutJSON;
                    if isequal(obj.AppMode,obj.RosbagVisualization)
                        obj.BookmarkPanelObj.updateBookmarkTable(obj.BookmarkDataFromSession);
                        obj.BookmarkTabObj.BookmarkTableList = obj.BookmarkDataFromSession;
                        obj.BookmarkPanelObj.showBookmarkTable();
                        if ~isequal(obj.TagsDataFromSession, {''})
                            for indx =1:length(obj.TagsDataFromSession)
                                obj.AddTagsPanelObj.createTagComponent(obj.TagsDataFromSession{indx});
                            end
                        end
                    end
                    objWeakHndl = matlab.internal.WeakHandle(obj);
                    indx=1;
                    % create the visualizers and set the datasource value
                    while indx <= numel(obj.VisualizerInfo)
                        if any(strcmp({'ros.internal.XYPlotVisualizer', 'ros.internal.OdometryVisualizer', 'ros.internal.TimePlotVisualizer'}, ...
                                obj.VisualizerInfo(indx).VisualizerName))
                            isFullrange = true;
                        else
                            isFullrange = false;
                        end

                        ros.internal.ViewerPresenter.createVisualizer(objWeakHndl, ...
                            str2func(obj.VisualizerInfo(indx).VisualizerName), ...
                            obj.VisualizerInfo(indx).DataType, isFullrange);
                        previousValue = '';
                        % Create event data for 'ValueChanged'
                        if strcmp(obj.VisualizerInfo(indx).VisualizerName, 'ros.internal.XYPlotVisualizer')

                            newValue = obj.VisualizerInfo(indx).DataSource;
                            if ismember(newValue,obj.VisualizerMapping(indx).Handle.DataSources(1).Items)
                                obj.VisualizerMapping(indx).Handle.DataSources(1).Value = newValue;
                                valueChangedEventData = matlab.ui.eventdata.ValueChangedData(newValue, previousValue);
                                ros.internal.ViewerPresenter.dataSourceValueChanged(objWeakHndl, ...
                                    obj.VisualizerMapping(indx).Handle.DataSources(1), valueChangedEventData, ...
                                    obj.VisualizerMapping(indx).ID, obj.VisualizerMapping(indx).Handle.CompatibleTypes, isFullrange);
                               drawnow;
                            end
                            
                            newValue = obj.VisualizerInfo(indx+1).DataSource;
                            if ismember(newValue,obj.VisualizerMapping(indx+1).Handle.DataSources(2).Items)
                                obj.VisualizerMapping(indx+1).Handle.DataSources(2).Value = newValue;
                                valueChangedEventData = matlab.ui.eventdata.ValueChangedData(newValue, previousValue);
                                ros.internal.ViewerPresenter.dataSourceValueChanged(objWeakHndl, ...
                                    obj.VisualizerMapping(indx+1).Handle.DataSources(2), valueChangedEventData, ...
                                    obj.VisualizerMapping(indx+1).ID, obj.VisualizerMapping(indx).Handle.CompatibleTypes, isFullrange);
                            end
                            indx = indx +1;
                        else
                            newValue = obj.VisualizerInfo(indx).DataSource;
                            if ismember(newValue,obj.VisualizerMapping(indx).Handle.DataSources.Items)
                                obj.VisualizerMapping(indx).Handle.DataSources.Value = newValue;
                                valueChangedEventData = matlab.ui.eventdata.ValueChangedData(newValue, previousValue);
                                ros.internal.ViewerPresenter.dataSourceValueChanged(objWeakHndl, ...
                                    obj.VisualizerMapping(indx).Handle.DataSources, valueChangedEventData, ...
                                    obj.VisualizerMapping(indx).ID, obj.VisualizerMapping(indx).Handle.CompatibleTypes, ...
                                    isFullrange);
                            end
                        end
                        indx = indx+1;
                        obj.CacheLoaded = true;
                    end
                catch ME %#ok<NASGU>
                    obj.AppContainer.DocumentGridDimensions = originalgridDimension;
                    obj.AppContainer.DocumentLayout = originallayoutjson;
                    obj.RestoringCache = false;
                    return; % use the default layout
                end
            end
        end % END createVisualizerUsingCacheData

        function getAppCacheData(obj, dataIn)
            % getAppCacheData
            if ~isempty(dataIn)
                obj.LayoutFromCache = dataIn.layoutInfo;
                obj.VisualizerInfo = dataIn.visualizerInfo;
                obj.BookmarkDataFromSession = dataIn.bookmarkData;
                obj.TagsDataFromSession = dataIn.tags;
            end
        end

        function throwErrorAsDialog(obj, eventData)
            %throwErrorAsDialog function is used to throw an error from
            % the event as UI Alert.

            bringToFront(obj.AppContainer);
            obj.UIAlert.run(obj.AppContainer, ...
                eventData.Data.Error.message, ...  % message
                getString(message('ros:visualizationapp:view:TextError')), ... % title
                'Icon', 'error');
        end

        function throwError(obj, errorMessage)
            %throwError function is used to throw an error from
            % the function callback

            bringToFront(obj.AppContainer);
            obj.UIAlert.run(obj.AppContainer, ...
                errorMessage.message, ...  % message
                getString(message('ros:visualizationapp:view:TextError')), ... % title
                'Icon', 'error');
        end

        function throwWarning(obj, topic, visualizer, timestamp, mlException)
            %throwWarning function is used to throw an warning on ML

            backtracestate = warning('off', 'backtrace');
            b = onCleanup(@() warning(backtracestate.state, 'backtrace'));
            verbosestate = warning('off', 'verbose');
            v = onCleanup(@() warning(verbosestate.state, 'verbose'));
            if isequal(obj.AppMode,obj.RosbagVisualization)
                msg = [getString(message('ros:visualizationapp:view:TimeStampLabel')) ': '...
                sprintf('%9.9f', timestamp) newline ...
                getString(message('ros:visualizationapp:view:DataSourceLabel')) ': ' ...
                char(topic) newline ...
                getString(message('ros:visualizationapp:view:VisualizersLabel')) ': ' ...
                visualizer newline mlException.message];
            else
                msg = [getString(message('ros:visualizationapp:view:DataSourceLabel')) ': ' ...
                char(topic) newline ...
                getString(message('ros:visualizationapp:view:VisualizersLabel')) ': ' ...
                visualizer newline mlException.message];
            end
            warning(msg);
        end

        function resetTimeForNewBag(obj, eventData)
            %resetTimeForNewBag Set up timeline for new rosbag

            % Set time limits
            if ~isempty(eventData.Data.Error)
                return;
            end

            tStart = eventData.Data.Rosbag.StartTime;
            tEnd = eventData.Data.Rosbag.EndTime;
            setTimeLimits(obj.Timeline, tStart, tEnd)

            % Set title
            [~, fName, fExt] = fileparts(eventData.Data.Rosbag.FilePath);
            obj.AppContainer.Title = [getString(message("ros:visualizationapp:view:AppTitle")) ' - ' fName fExt];

            % Set topics to main signal options
            topicList = convertCharsToStrings(eventData.Data.Tree.Topics);
            updateSignalOptions(obj.Timeline, topicList)
        end

        function updateVisualizers(obj, eventData)
            %updateVisualizers Distribute new messages to relevant visualizers

            if ~isVisualizerLoadedWithTopicForPlayback(obj)
                stopPlayBack(obj)
                return;
            end

            crPgIdHdle = obj.VisualizerMapping(1).Handle.launchCircularProgressIndicator;
            c = onCleanup(@()delete(crPgIdHdle));
			
			% filtering out the unique topics to avoid repeated updating of
            % data
            eventData.Data.TopicData = filterRepeatedTopics(eventData.Data.TopicData);

            for iTopic = 1:numel(eventData.Data.TopicData)

                data = eventData.Data.TopicData(iTopic);

                % Add the timestamp to the source-specific data passed
                if isequal(obj.AppMode,obj.RosbagVisualization)
                    data.Time = eventData.Data.Time;
                end

                % Find the appropriate visualizers for the data source
                whichSources = arrayfun(@(visMap) strcmp(data.Topic, visMap.DataSource) && strcmp(data.DataType, visMap.DataType), ...
                    obj.VisualizerMapping);
                visualizerMappingSubset = obj.VisualizerMapping(whichSources);

                for iVis = 1:numel(visualizerMappingSubset)
                    % check if there is any error in data
                    if isempty(eventData.Data.TopicData(iTopic).Error)
                        try
                            updateData(visualizerMappingSubset(iVis).Handle, ...
                                visualizerMappingSubset(iVis).DataSource, ...
                                data)
                        catch ex
                            if isequal(obj.AppMode,obj.RosbagVisualization)
                                obj.throwWarning(eventData.Data.TopicData(iTopic).Topic, ...
                                    visualizerMappingSubset(iVis).Handle.InitialTitle, ...
                                    eventData.Data.Time, ex);
                            elseif isequal(obj.AppMode,obj.LiveRosTopicVisualization)
                                obj.throwWarning(eventData.Data.TopicData(iTopic).Topic, ...
                                    visualizerMappingSubset(iVis).Handle.InitialTitle, ...
                                    0, ex);
                            end
                        end
                    elseif isequal(obj.AppMode,obj.RosbagVisualization)
                         obj.throwWarning(eventData.Data.TopicData(iTopic).Topic, ...
                            visualizerMappingSubset(iVis).Handle.InitialTitle, ...
                            eventData.Data.Time, eventData.Data.TopicData(iTopic).Error);
                    elseif isequal(obj.AppMode,obj.LiveRosTopicVisualization)    
                        obj.throwWarning(eventData.Data.TopicData(iTopic).Topic, ...
                            visualizerMappingSubset(iVis).Handle.InitialTitle, ...
                            0, eventData.Data.TopicData(iTopic).Error);
                    end
                end
            end

            if isequal(obj.AppMode,obj.RosbagVisualization)
                curTime = eventData.Data.Time;
                setCurrentTime(obj.Timeline, curTime);
    
                if isfield(eventData.Data,'MainSignalStartTime') && ...
                        isfield(eventData.Data,'MainSignalEndTime')
                    tStart = eventData.Data.MainSignalStartTime;
                    tEnd = eventData.Data.MainSignalEndTime;
                    if ~(obj.PlaybackState && ...
                        ((curTime < tEnd && isequal(obj.PlaybackDirection,obj.ForwardPlayback)) || ...
                            (curTime > tStart && isequal(obj.PlaybackDirection,obj.ReversePlayback))))
    
                        % Request the timer to stop the playback
                        obj.PlaybackState = false;
                    end
                end
            end
        end

        function updateVisualizersWithDataRange(obj, eventData)
            %updateVisualizersWithDataRange Pass full bag of data to visualizer
            %   Visualizers that display a subset of data from all messages in
            %   a rosbag need all that data for display at once.
            
            % following lines of code is used to handle the race condition
            % that occurs while loading the cache file with xy data . In XY
            % this event call is triggered back to back in short span of
            % time which is leading to race condition and dropping the
            % second event.
            if obj.ProcessingFlag 
                waitfor(obj, 'ProcessingFlag', false);
            end
            obj.ProcessingFlag = true;
            c = onCleanup(@()setrestoring(obj, 'ProcessingFlag', false));

            crPgIdHdle = obj.VisualizerMapping(1).Handle.launchCircularProgressIndicator;
            c = onCleanup(@()delete(crPgIdHdle));
            dataSource = eventData.Data.DataSource;
            dataArray = eventData.Data.Data;
            timeArray = eventData.Data.Time;
            fieldMap = eventData.Data.FieldMap;

            % Find the appropriate visualizers for the data source
            whichVisualizers = arrayfun(@(visMap) strcmp(dataSource, visMap.DataSource), ...
                obj.VisualizerMapping);
            visualizerMappingSubset = obj.VisualizerMapping(whichVisualizers);
            for iVis = 1:numel(visualizerMappingSubset)
                setFullData(visualizerMappingSubset(iVis).Handle, dataSource, dataArray, timeArray, fieldMap)
            end
        end

        function updateSaveAppStateFile(obj)
            %updateSaveAppStateFile method is used to update the cache file
            %with the new information. this is called when a new visualizer
            %is created, or exisitng visualizer is closed, app is closed,
            %datasource value is changed.
            if obj.SkipUpdateCache
                return;
            end
            try
                if ~obj.RestoringCache
                    layoutjson = obj.AppContainer.DocumentLayout;
                    layout = struct("LayoutJSON", layoutjson, ...
                        "DocumentGridDimensions", obj.AppContainer.DocumentGridDimensions);
                    visualizers = struct("VisualizerName", arrayfun(@(x)class(obj.VisualizerMapping(x).Handle), 1:numel(obj.VisualizerMapping),'UniformOutput',false), ...
                        "DataSource", arrayfun(@(x)obj.VisualizerMapping(x).DataSource, 1:numel(obj.VisualizerMapping),'UniformOutput',false), ...
                        "DataType", arrayfun(@(x)obj.VisualizerMapping(x).DataType, 1:numel(obj.VisualizerMapping),'UniformOutput',false));
                    if isequal(obj.AppMode,obj.RosbagVisualization)
                        rosbaginfo = dir(obj.TopicTree.Rosbag.FilePath);
                    elseif isequal(obj.AppMode,obj.LiveRosTopicVisualization)
                        rosbaginfo = obj.TopicTree.Network;
                    end
                    bookmarkdata = obj.BookmarkTabObj.BookmarkTableList;
                    if isempty(obj.AddTagsPanelObj.TagsHandleTab)
                        tagsdata = '';
                    else
                        tagsdata = obj.AddTagsPanelObj.TagsHandleTab.TagValue;
                    end
                    eventDataOutStruct = struct("RosbagInfo", rosbaginfo, ...
                                                "Layout", layout, ...
                                                "VisualizerInfo", visualizers, ...
                                                "BookmarkData", bookmarkdata, ...
                                                "TagsData", tagsdata);
                    eventDataOutStruct = ros.internal.EventDataContainer(eventDataOutStruct);
                    notify(obj.EventObject, "UpdateAppSessionCachePM", eventDataOutStruct);
                end
            catch ME %#ok<NASGU>
                return;
            end
        end

        function openSelectedRosbag(obj, fileFullPath)

            if isempty(fileFullPath)
                return;
            end
            if ~exist(fileFullPath) %#ok<EXIST>
                errmsg.message = getString(message("ros:visualizationapp:view:FileNotExist", fileFullPath));
                throwError(obj, errmsg);
                return;
            end

            [dirName, fileName, ext] = fileparts(fileFullPath);
            fileName = [fileName, ext];

            if ~isequal(fileName, 0) && ...
                    (isempty(obj.TopicTree) || ...
                    ~isequal(obj.AppMode,obj.RosbagVisualization) || ...
                    ~isequal(fullfile(dirName,fileName), obj.TopicTree.Rosbag.FilePath))

                % If playback is going on, stop it before loading the bag file
                if obj.PlaybackState
                    stopPlayBack(obj);
                end

                if obj.EnableSaveAppState
                    % is the bag is already loaded clear all the
                    % visualizers
                    if ~isempty(obj.TopicTree) && obj.CacheLoaded
                        obj.updateSaveAppStateFile();
                        listofopenvisualizer = obj.VisualizerMapping;
                        obj.SkipUpdateCache = true; % skip updating the cache file
                        c = onCleanup(@()setrestoring(obj, 'SkipUpdateCache', false));
                        for indx =1:numel(listofopenvisualizer)
                            if isvalid(listofopenvisualizer(indx).Handle)
                                listofopenvisualizer(indx).Handle.Document.close; % close all visualizers if any
                            end
                        end
                        obj.AppContainer.DocumentGridDimensions = [1 1];
                        obj.LayoutFromCache = [];
                        obj.VisualizerInfo = [];
                        obj.CacheLoaded =  false;
                    end
                    if isequal(obj.AppMode,obj.RosbagVisualization)
                        obj.BookmarkPanelObj.resetBookmarkTable;
                        obj.BookmarkTabObj.resetTable;
                        obj.AddTagsPanelObj.resetTagPanel();
                        obj.resetCacheProperties();
                    end
                end

                % File selection was not canceled
                filePath = fullfile(dirName, fileName);

                filePathArr = split(dirName,filesep);
                bagDirName = filePathArr{end};
                if isempty(bagDirName)
                    bagDirName = filePathArr{end-1}; %Parent folder of the file
                end

                obj.LastOpenedBagPath = dirName;

                % Show progress bar until bag file is loaded
                d = obj.UIProgress.run(obj.AppContainer, 'Message', ...
                    getString(message('ros:visualizationapp:view:LoadRosbagFile', ...
                    fullfile(bagDirName,fileName))));
                c = onCleanup(@() delete(d));

                eventDataStruct = struct("FilePath", filePath);
                eventDataStruct = ros.internal.EventDataContainer(eventDataStruct);
                notify(obj.EventObject, "RosbagSelectedPM", eventDataStruct);
                if strcmp(obj.AppMode, "RosbagVisualization")
                    obj.AppContainer.RightCollapsed = 0;
                    obj.AppContainer.RightCollapsed = 1;
                end
            end

        end

        function searchBagAlgorithm(obj)
            cacheFieldName = {'tags', 'bookmarks', 'visualizerTypes'};
            objectName = {'TagPanelObj', 'BookmarkPanelObj', 'VisualizerPanelObj'};
            BagPathArray = [];
            % check if the any or all the active filter have data and
            % get active filter array and search the bag file
            if ~isequal((strsplit(obj.SearchRosbagAppObj.TagPanelObj.Value{1},';')), {''}) ...
                    || ~isequal((strsplit(obj.SearchRosbagAppObj.BookmarkPanelObj.Value{1},';')), {''}) ...
                    || ~isequal((strsplit(obj.SearchRosbagAppObj.VisualizerPanelObj.Value{1},';')), {''})
                for indx = 1: numel(cacheFieldName)
                    activeFilter = strsplit(obj.SearchRosbagAppObj.(objectName{indx}).Value{1}, ';');
                    if ~isequal(activeFilter, {''})                        
                        for i = 1:numel(activeFilter)
                            if isempty(activeFilter{i})
                                continue;
                            end
                            jindx = ros.internal.utilities.findElement(obj.CacheDataForSearch.(cacheFieldName{indx}), activeFilter{i});
                            if ~isempty(jindx)
                                for jjindx=1:length(jindx)
                                    BagPathArray = [BagPathArray, obj.CacheDataForSearch.bagPaths(jindx{jjindx}(1))]; %#ok<AGROW>
                                end
                            end

                        end
                    end
                end
                if isempty(BagPathArray)
                    obj.SearchRosbagAppObj.updateResultsTableData({});
                else
                    obj.SearchRosbagAppObj.updateResultsTableData(unique(BagPathArray)');
                end
            else
                if istable(obj.CacheDataForSearch)
                    BagPathArray = obj.CacheDataForSearch.bagPaths;
                    obj.SearchRosbagAppObj.updateResultsTableData(unique(BagPathArray));
                end
            end
        end
    end %END Method

    methods (Static, Access = ?ros.internal.mixin.ROSInternalAccess)    % UI Callbacks
        function openRosbag(objWeakHndl)
            %openRosbag Open file selection for rosbag and initiate loading

            obj = objWeakHndl.get;

            if isempty(obj.LastOpenedBagPath)
                obj.LastOpenedBagPath = '';
            end

            title = getString(message("ros:visualizationapp:view:OpenDialogTitle"));
            rosbagFileFilter = getString(message("ros:visualizationapp:view:RosbagFileFilter"));
            ros2bagFileFilter = getString(message("ros:visualizationapp:view:Ros2bagFileFilter"));
            rosRos2bagFileFilter = getString(message("ros:visualizationapp:view:RosRos2bagFileFilter"));
            allFileFilter = getString(message("ros:visualizationapp:view:AllFileFilter"));

            filter = { '*.bag;*.db3;*.mcap;metadata.yaml',rosRos2bagFileFilter; ...
                '*.bag',rosbagFileFilter; ...
                '*.db3;*.mcap;metadata.yaml',ros2bagFileFilter; ...
                '*.*',allFileFilter};
            [fileName, dirName] = uigetfile(filter, title, obj.LastOpenedBagPath);

            % Workaround for app going behind MATLAB
            bringToFront(obj.AppContainer);
            if ~isequal(fileName, 0)
                obj.openSelectedRosbag([dirName, fileName])
            end

        end

        function inputRosNetwork(objWeakHndl, networkType)
            obj = objWeakHndl.get;
            
            fig = uifigure('Position',[500 500 430 200],'WindowStyle','modal');
            matlab.graphics.internal.themes.figureUseDesktopTheme(fig);
            fig.Name = getString(message("ros:visualizationapp:view:EnterNetworkDetails"));
            if isequal(networkType,'ros1')
                inputTypeString = getString(message("ros:visualizationapp:view:ROSMasterURI"));
                prefName = obj.LastUsedROSMasterURIs;
                defaultNetworkInput = 'http://localhost:11311';
            else
                inputTypeString = getString(message("ros:visualizationapp:view:ROSDomainID"));
                prefName = obj.LastUsedROSDomainIDs;
                defaultNetworkInput = '0';
            end
            uilabel(fig,...
                    'Position',[100 150 215 15],...
                    'Text',inputTypeString,'HorizontalAlignment','center','Tag','NetworkTypeLabel');

            % Load last time used sources from preference
            prefNetworkInput = {};
            if ispref(obj.PrefGroup,prefName)
                prefNetworkInput = getpref(obj.PrefGroup,prefName);
            end

            
            if ~ismember(defaultNetworkInput,prefNetworkInput)
                prefNetworkInput{end+1} = defaultNetworkInput;
                if numel(prefNetworkInput) >10
                    prefNetworkInput(1) = [];
                end
            end

            textarea = uidropdown(fig,...
                    'Position',[100 100 215 30]);
            textarea.Items = prefNetworkInput;
            textarea.Value = defaultNetworkInput;
            textarea.Tag = 'LiveROSNetwork';
            textarea.Editable = 'on';
            uibutton(fig, ...
                "Text",getString(message("ros:visualizationapp:view:SubmitDetails")),...
                "ButtonPushedFcn",@(src,event)rosNetworkDetailsEntered(obj,textarea,fig),...
                'Position',[100 50 215 30]);

            function rosNetworkDetailsEntered(obj,textarea,fig)
                if obj.PlaybackState
                    stopPlayBack(obj);
                end
                if obj.EnableSaveAppState
                    if ~isempty(obj.TopicTree) && obj.CacheLoaded 
                        obj.updateSaveAppStateFile();
                    end
                end

                if ~ismember(textarea.Value, prefNetworkInput)
                    prefNetworkInput{end+1} = textarea.Value;
                    
                    if numel(prefNetworkInput) >10
                        prefNetworkInput(1) = [];
                    end
                end

                networkInput = textarea.Value;
                close(fig);

                if isequal(networkType,'ros1')
                    messageStr = getString(message('ros:visualizationapp:view:ConnectRos1Network', networkInput));
                else
                    messageStr = getString(message('ros:visualizationapp:view:ConnectRos2Network', networkInput));
                end
                % Show progress bar until bag file is loaded
                d = obj.UIProgress.run(obj.AppContainer, 'Message',messageStr);
                c = onCleanup(@() delete(d));

                data.NetworkInput = networkInput;
                data.NetworkType = networkType;
                eventData = ros.internal.EventDataContainer(data);
                notify(obj.EventObject, "InputRosNetworkPM", eventData);
                setpref(obj.PrefGroup,prefName,prefNetworkInput);
            end
        end

        %Function to refresh topic list
        function refreshTopicList(objWeakHndl)
            obj = objWeakHndl.get;
            eventData = ros.internal.EventDataContainer([]);
            notify(obj.EventObject, "InputRosNetworkPM", eventData);
        end

        function searchRosbagCallback(objWeakHndl)
            %searchRosbagCallback callback for search rosbag button
            obj = objWeakHndl.get;
            %read cache files
            cachedata = ros.internal.utils.readAppCacheFiles;
            % filter live network data
            % Find rows where 'bagPath' contains 'http'

            tagItems = {' '};
            bmItems = {' '};
            bagPath = {};
            if ~isempty(cachedata)
                rowsToRemove = contains(cachedata.bagPaths, 'http');
                cachedata(rowsToRemove, :) = [];
                obj.CacheDataForSearch = cachedata;
                tagItems = ros.internal.utilities.removeEmptyElement(ros.internal.utilities.flattenNestedCellArray(cachedata.tags));
                tagItems = [' ', tagItems];

                bmItems = ros.internal.utilities.removeEmptyElement(ros.internal.utilities.flattenNestedCellArray(cachedata.bookmarks));
                bmItems = [' ', bmItems];

                bagPath = cachedata.bagPaths;
            end
            obj.SearchRosbagAppObj = ros.internal.view.UISearchRosbag;
            obj.SearchRosbagAppObj.TagSFListBoxObj.ValueChangedFcn = @(src, event) ros.internal.ViewerPresenter.listValueChangedCallbackFcn(objWeakHndl, src, event, obj.SearchRosbagAppObj.TagPanelObj);
            obj.SearchRosbagAppObj.BookmarkSFListBoxObj.ValueChangedFcn =  @(src, event) ros.internal.ViewerPresenter.listValueChangedCallbackFcn(objWeakHndl, src, event, obj.SearchRosbagAppObj.BookmarkPanelObj);
            obj.SearchRosbagAppObj.VisualizerSFListBoxObj.ValueChangedFcn =  @(src, event) ros.internal.ViewerPresenter.listValueChangedCallbackFcn(objWeakHndl, src, event, obj.SearchRosbagAppObj.VisualizerPanelObj);
            obj.SearchRosbagAppObj.ResultsTableObj.CellSelectionCallback =  @(src, event) ros.internal.ViewerPresenter.resultTableClickedFcn(objWeakHndl, src, event);
            obj.SearchRosbagAppObj.ClearTagTextAreaButtonObj.ButtonPushedFcn = @(source, event) ros.internal.ViewerPresenter.onClickClearTextAreaButton(objWeakHndl, source, event, obj.SearchRosbagAppObj.TagPanelObj);
            obj.SearchRosbagAppObj.ClearBMTextAreaButtonObj.ButtonPushedFcn = @(source, event) ros.internal.ViewerPresenter.onClickClearTextAreaButton(objWeakHndl, source, event, obj.SearchRosbagAppObj.BookmarkPanelObj);
            obj.SearchRosbagAppObj.ClearVisualizerTextAreaButtonObj.ButtonPushedFcn = @(source, event) ros.internal.ViewerPresenter.onClickClearTextAreaButton(objWeakHndl, source, event, obj.SearchRosbagAppObj.VisualizerPanelObj);

            obj.SearchRosbagAppObj.updateTagItems(unique(tagItems));
            obj.SearchRosbagAppObj.updateBmItems(unique(bmItems));
            obj.SearchRosbagAppObj.updateResultsTableData(bagPath);
            obj.SearchRosbagAppObj.showApp();
        end

        function listValueChangedCallbackFcn(objWeakHndl, source, event, textareaobj)
            % listValueChangedCallbackFcn - Handle list value change event

            % Extract the object from the weak handle
            obj = objWeakHndl.get;

            % Get the current text from the textarea object
            currentText = textareaobj.Value{1};

            % Split the current text into an array of values
            currentValues = strsplit(currentText, ';');

            % Get the new value from the event
            newValue = event.Value;

            % Check if the new value is not already in the list
            if ~any(ismember(currentValues, newValue))
                % Add the new value to the text area
                newVal = [currentText, newValue, ';'];
                textareaobj.Value = newVal;
            end

            % Reset the source value
            source.Value = ' ';

            % Check if CacheDataForSearch is empty
            if isempty(obj.CacheDataForSearch)
                return;
            else
                % Perform the search algorithm
                obj.searchBagAlgorithm();
            end
        end

        function resultTableClickedFcn(objWeakHndl, source, event)
            %resultTableClickedFcn
            
            obj = objWeakHndl.get;
            filePath = source.Data(event.DisplayIndices(1));

            % Remove "href" using regular expressions
            filePath = regexprep(filePath{1}, '<a\s+href="[^"]*">', '');
            filePath = regexprep(filePath, '</a>', '');
            %load the rosbag
            obj.openSelectedRosbag(filePath);
            %close the search app
            obj.SearchRosbagAppObj.closeApp();
        end

        function onClickClearTextAreaButton(objWeakHndl, ~, ~, textareaobj)
            %onClickClearTextAreaButton
            obj = objWeakHndl.get;
            % clear the selected textarea
            textareaobj.Value = {''};
            % Search Algorithm
            obj.searchBagAlgorithm();
        end

        function createVisualizer(objWeakHndl, constructorFcn, type, isFullRange)
            %createVisualizer Create document and initialize specified visualizer
            %   The constructorFcn argument is the specific visualizer constructor.
            %   The type argument is related to the data to be displayed.

            obj = objWeakHndl.get;
            visualizer = constructorFcn(obj.AppContainer, obj.TagDocGroup, obj.AppMode);
            visualizer.DataSourceChangedCallback = ...
                @(src, valueChangedEventData, id) ros.internal.ViewerPresenter.dataSourceValueChanged(...
                objWeakHndl, ...
                src, valueChangedEventData, id, ...
                visualizer.CompatibleTypes, isFullRange);
            updateDataSourceOptions(visualizer, obj.TopicTree);
            for k = 1:numel(visualizer.DataSourcesID)
                addDataSourceToMapping(obj, visualizer.DataSourcesID(k), visualizer, type)
            end
            visualizer.CloseCallback = ...
                @(~, ~) ros.internal.ViewerPresenter.removeDataSourcesFromMapping(objWeakHndl, visualizer.DataSourcesID);

            if obj.EnableSaveAppState
                if ~isempty(obj.TopicTree)
                    obj.updateSaveAppStateFile();
                end
            end
        end

        function dataSourceValueChanged(objWeakHndl, ...
                source, valueChangedEventData, dataSourceID, ...
                compatibleTypes, isFullRange)


            %dataSourceValueChanged Update mapping of visualizer to topics
            %   The source will be the data source selection field.
            %   The compatible types will come from the visualizer type.
            %   The data source ID is in the visualizer topic mapping.
            %   The full range boolean indicates if it is necessary to extract
            %   the data range from the bag to initialize the display.
            obj = objWeakHndl.get;
            % progress status indicator launch
            indx = [obj.VisualizerMapping.ID] == dataSourceID;
            crPgIdHdle = obj.VisualizerMapping(indx).Handle.launchCircularProgressIndicator;
            c = onCleanup(@()delete(crPgIdHdle));

            % validate the data sources
            try
                obj.VisualizerMapping(indx).Handle.validateDataSources
            catch ex
                if strcmp('ros:visualizationapp:view:InvalidDataSources',ex.identifier)
                    % throw error and reset to previous value.
                    source.Value = valueChangedEventData.PreviousValue;
                end
                obj.throwError(ex)
            end

            % Get current data source and message/field type
            fieldPath = source.Value;
            if isempty(fieldPath)
                return;
            end
            currType = getTypeWithFieldPath(obj.TopicTree, fieldPath);

            % Check that the selected source is valid for that visualizer
            if ~isempty(currType) && ...
                    (ismember(currType, compatibleTypes) || ...
                    (any(ismember(compatibleTypes, 'message')) && contains(currType, '/')))
                topicName = splitTopicFieldPath(fieldPath);

                % Update the visualizer topic mapping
                whichVis = [obj.VisualizerMapping.ID] == dataSourceID;
                obj.VisualizerMapping(whichVis).Topic = string(topicName);
                obj.VisualizerMapping(whichVis).DataSource = string(fieldPath);

                if isequal(obj.AppMode,obj.RosbagVisualization)
                    % Update visualizer with latest time changes
                    settings.ticks = getTimeTicks(obj.Timeline);
                    [settings.tStart, settings.tEnd] = getTimeLimits(obj.Timeline);
                    updateTimeSettings(obj.VisualizerMapping(whichVis).Handle,settings)
                end

                if strcmp(obj.AppMode,obj.LiveRosTopicVisualization)
                    % Reset the UI of visualizer
                    resetUI(obj.VisualizerMapping(whichVis).Handle);
                end

                % Notify the model about the data source change
                sendSourceListToModel(obj)

                if isequal(obj.AppMode,obj.RosbagVisualization)
                    % If necessary, request full range of data for view
                    if isFullRange
                        [tStart, tEnd] = getTimeLimits(obj.Timeline);
                        eventDataStruct = struct("StartTime", tStart, ...
                            "EndTime", tEnd, ...
                             "DataSource", fieldPath, ...
                            "DataType", obj.VisualizerMapping(indx).DataType);
                        eventDataOut = ros.internal.EventDataContainer(eventDataStruct);
                        notify(obj.EventObject, "DataRangeRequestedPM", eventDataOut)
                    end
    
                    % Display initial data on the graphics
                    % For full-range visuals this will update the position indicator
                    tNow = getCurrentTime(obj.Timeline);
                    obj.AutomaticTopic = obj.getReferenceTopicForAutomatic();
                    requestDataForTime(obj, tNow)
                end
            end

            % first time when the user select the datasource from the
            % dropdown remove the empty items.
            source.Items = source.Items(~cellfun('isempty', source.Items));
            %obj.updateSaveAppStateFile();
        end
        
        function currentTimeFieldChanged(objWeakHndl, source, eventData)
            %currentTimeFieldChanged React to change in time edit field

            obj = objWeakHndl.get;
            inputdata = str2double(eventData.Value);
            if isnan(inputdata)||~isreal(inputdata)||isinf(inputdata)
                %throw an error
                timelabelval = obj.Timeline.getTimeLabel;
                errmsg.message = getString(message("ros:visualizationapp:view:InvalidCurrentTime", timelabelval));
                throwError(obj, errmsg);
                source.Value = eventData.PreviousValue;
                return;
            end
            if ~isempty(obj.TopicTree)
                newTime = str2double(source.Value);
                if obj.Timeline.getIsViewElapseTime
                    [tStart, ~] = getTimeLimits(obj.Timeline);
                    newTime = newTime + tStart;
                end
                if ~isempty(obj.VisualizerMapping)
                    requestDataForTime(obj, newTime);
                else
                    obj.Timeline.setCurrentTime(newTime);
                end
            else
                source.Value = eventData.PreviousValue;
            end
        end


        function updateCurrentTimeFieldChanged(objWeakHndl, ~, ~)
            %updateCurrentTimeFieldChanged callback updates the
            %CurrentTimeField to show the time format selected from the
            %dropdown i.e. either elapse time or timestamp

            obj = objWeakHndl.get;
            if ~isempty(obj.TopicTree)
                obj.Timeline.updateTimeSettings

                % Update visualizers with latest time changes
                settings.ticks = getTimeTicks(obj.Timeline);
                [settings.tStart, settings.tEnd] = getTimeLimits(obj.Timeline);
                for iVis = 1:numel(obj.VisualizerMapping)
                    updateTimeSettings(obj.VisualizerMapping(iVis).Handle,settings)
                end
            end
            if ~ isempty(obj.BookmarkTabObj.BookmarkTableList)

                [s, ~] = obj.Timeline.getTimeLimits;
                if obj.Timeline.getIsViewElapseTime
                    if ~obj.BookmarkTabObj.IsElapseTimeFormat
                        obj.BookmarkTabObj.BookmarkTableList(:,2) = ...
                            obj.BookmarkTabObj.BookmarkTableList(:, 2)- s;
                    end
                else
                    if obj.BookmarkTabObj.IsElapseTimeFormat
                        obj.BookmarkTabObj.BookmarkTableList(:,2) = ...
                        obj.BookmarkTabObj.BookmarkTableList(:,2) +s;
                    end                    
                end
                obj.BookmarkPanelObj.updateBookmarkTable(...
                    obj.BookmarkTabObj.BookmarkTableList);
                obj.BookmarkTabObj.IsElapseTimeFormat = obj.Timeline.getIsViewElapseTime;
            end
        end

        function timelineValueChanged(objWeakHndl, source)
            %timelineValueChanged Request updated data for new time

            obj = objWeakHndl.get;
            newTime = source.Value;

            % Check if the source details are loaded before requesting Data
            if ~isempty(obj.TopicTree)
                requestDataForTime(obj, newTime);
            end

            % Stop playback after timeline is clicked
            stopPlayBack(obj)
        end

        function timelineValueChanging(objWeakHndl, ~)
            %timelineValueChanging Request updated data for new time

            obj = objWeakHndl.get;
            if obj.PlaybackState
                stopPlayBack(obj)
            end
        end
        
        function playBackTimerCallback(objWeakHndl, ~)
        %playBackTimerCallback callback for playback timer

            obj = objWeakHndl.get;

            if isequal(obj.AppMode,obj.RosbagVisualization)
                % Continue playback until stopped by user or reached end
                [tStart, tEnd] = getTimeLimits(obj.Timeline);
                t = getCurrentTime(obj.Timeline);
                if obj.PlaybackState && ...
                        ((t < tEnd && isequal(obj.PlaybackDirection,obj.ForwardPlayback)) || ...
                        (t > tStart && isequal(obj.PlaybackDirection,obj.ReversePlayback)))
                    stepTime(obj, obj.PlaybackDirection)
                else
                    stopPlayBack(obj)
                    % After reaching end of the bag file, reset time slider to start position
                    requestDataForTime(obj, tStart)
                end
            else
                stepTime(obj, {})
            end
        end

        function playPausePushed(objWeakHndl, ~)
            %playPausePushed Start or stop playback through the rosbag
            %   Source is the play/pause button that was pushed

            obj = objWeakHndl.get;
            % Toggle playback state
            if ~isVisualizerLoadedWithTopicForPlayback(obj)
                stopPlayBack(obj)
                if isequal(obj.AppMode, obj.LiveRosTopicVisualization)
                    ex = MException(message('ros:visualizationapp:view:NoVisualizerIsOpen'));
                    throwError(obj, ex);
                end
                return;
            end
            obj.PlaybackState = ~obj.PlaybackState;

            %set forward playback
            obj.PlaybackDirection = obj.ForwardPlayback;

            if obj.PlaybackState
                startPlayBack(obj)
            else
                stopPlayBack(obj)
                updateGraphicHandleIdx(obj)
            end
        end

        function removeDataSourcesFromMapping(objWeakHndl, idValues)
            %removeDataSourcesFromMapping Remove data sources from tracking
            %   The idValues contain all data source ID values to remove

            obj = objWeakHndl.get;
            if ~isempty(obj) && isvalid(obj)
                whichSource = ismember([obj.VisualizerMapping.ID], idValues);
                dataSource = obj.VisualizerMapping(whichSource).DataSource;
                obj.VisualizerMapping(whichSource) = [];

                if ~isequal(dataSource,"")
                    % update Topic for Automatic Playback
                    obj.AutomaticTopic = obj.getReferenceTopicForAutomatic();

                    % Notify the model about the data source change
                    sendSourceListToModel(obj);
                end

                if ~isempty(obj.TopicTree  )
                   obj.updateSaveAppStateFile();
                end
            end
        end

        function nextPushed(objWeakHndl)
            %nextPushed Move on to next message
            %   The new time will depend on the main signal and rate selected

            obj = objWeakHndl.get;
            if ~isVisualizerLoadedWithTopicForPlayback(obj)
                %play only when the data source is selected
                return
            end
            if obj.PlaybackState
                obj.PlaybackDirection = obj.ForwardPlayback;
            else
                stepTime(obj, obj.ForwardPlayback);
            end
        end
 
        function previousPushed(objWeakHndl)
            %nextPushed Move back to the previous message
            %   The new time will depend on the main signal and rate selected

            obj = objWeakHndl.get;
            if ~isVisualizerLoadedWithTopicForPlayback(obj)
                %play only when the data source is selected
                return
            end
            if obj.PlaybackState
                obj.PlaybackDirection = obj.ReversePlayback;
            else
                stepTime(obj, obj.ReversePlayback);
            end
        end
         
        function ret = cleanupAndCloseApp(objWeakHndl)
            %cleanupAndCloseApp cleanup app before closing

            obj = objWeakHndl.get;

            % Stop the playback
            if obj.PlaybackState
                obj.stopPlayBack();
            end
            % delete the timer
            timerhdl = timerfind('Name', obj.UniqueTimerName);
            if ~isempty(timerhdl) && isvalid(timerhdl)
                obj.PlaybackTimer.stop;
                delete(timerhdl);
            end

            % Save the last time used bag file in preference before App is
            % closed
            if ~isempty(obj.LastOpenedBagPath)
                setpref(obj.PrefGroup,obj.LastUsedBagPathPrefName,obj.LastOpenedBagPath);
                obj.LastOpenedBagPath = '';
            end
            if obj.EnableSaveAppState
                if ~isempty(obj.TopicTree)
                    obj.updateSaveAppStateFile();
                end
            end
            ret = true;
            

            appMap = ros.internal.RosDataAnalyzer.getAppMap;
            appMap(obj.UniqueTagApp) = []; %#ok<NASGU>
        end

        function gridlayout(objWeakHndl, source, eventData)
            %gridlayout is a callback function for the gridlayout toolstrip
            %option. This callback sets the AppContainer
            %DocumentGridDimensions to the event data i.e. row and column
            %which user selects

            obj = objWeakHndl.get;
            if isa(source, 'matlab.ui.internal.toolstrip.GridPickerButton')
                obj.AppContainer.DocumentGridDimensions = ...
                    [eventData.EventData.NewValue.column eventData.EventData.NewValue.row];
            else
                obj.AppContainer.DocumentGridDimensions = [1 1];
                obj.Toolstrip.setGridLayoutButtonProperties('Selection', struct('row', 1, 'column', 1));
            end
        end

        function defaultlayout(objWeakHndl, ~)
            %defaultlayout is a callback function for the defaultlayout
            % toolstrip option. This callback sets the AppContainer
            %DocumentGridDimensions to [1 1](single and single column)

            obj = objWeakHndl.get;
            obj.AppContainer.DocumentGridDimensions = [1 1];
        end

        function addBookmark(objWeakHndl, ~)
            %addBookmark is used to add a bookmark at a particular time

            obj = objWeakHndl.get;
            % if bag not loaded throw message and return
            if isempty(obj.TopicTree)
                errmsg.message = getString(message("ros:visualizationapp:view:LoadRosbagToAddBookmark"));
                obj.throwError(errmsg);
                return;
            end
            % stop playback
            if obj.PlaybackState
                %If playback is going stop the playback
                stopPlayBack(obj);
            end
            % define input dialog variable names    
            startimelabel = getString(message("ros:visualizationapp:view:StartTime"));
            durationlabel = getString(message("ros:visualizationapp:view:Duration"));
            labelbookmarklabel = getString(message("ros:visualizationapp:view:LabelBookmark"));
            dlgtitle = getString(message("ros:visualizationapp:view:AddBookmarkTitle"));
            prompt = {startimelabel, durationlabel, labelbookmarklabel};
            dims = [1 75];
            % get default values
            currTime = getCurrentTime(obj.Timeline);
            [startLim, endLim] = obj.Timeline.getTimeLimits();
            if obj.Timeline.getIsViewElapseTime
                [s,~] = obj.Timeline.getTimeLimits;
                currTime = currTime-s;
                [startLim, endLim] = obj.Timeline.getTimeLimits();
                startLim = startLim -s;
                endLim = endLim -s;
            end
            definput = {num2str(currTime), '0', ['Bookmark_' ros.internal.utils.generateRandomString(4)]};
            bookmarkdata = inputdlg(prompt, dlgtitle, dims, definput);
            if ~isempty(bookmarkdata)
                % parse input data
                starttime = bookmarkdata(1);
                starttime = str2double(starttime{1});
                % validate starttime is valid within limits of the bag
                if ~((startLim-starttime)<=1e-4 && (starttime <= endLim))
                    %throw an error
                    errmsg.message = getString(message("ros:visualizationapp:view:InvalidStartTime", ...
                        bookmarkdata{1}, num2str(startLim), num2str(endLim)));
                    throwError(obj, errmsg);
                    return;
                end
                % validate duration is valid 
                duration = bookmarkdata(2);
                duration = str2double(duration{1});
                if duration <0 || ~(duration <= endLim-starttime)
                    %throw an error
                    errmsg.message = getString(message("ros:visualizationapp:view:InvalidDuration", ...
                        bookmarkdata{2}, num2str(endLim-starttime)));
                    throwError(obj, errmsg);
                    return;
                end
                label = bookmarkdata(3);
                label = label{1};
                % append data to the table
                obj.BookmarkTabObj.appendToBookmarkTable(starttime, duration, label);
                % update to manage bookmark panel
                obj.BookmarkPanelObj.updateBookmarkTable(obj.BookmarkTabObj.BookmarkTableList);
                obj.updateSaveAppStateFile();
            end
            obj.AppContainer.bringToFront();
            obj.BookmarkPanelObj.showBookmarkTable();
            
        end

        function addTagsToRosbag(objWeakHndl, ~)
            %addTagsToRosbag is used to add a tags at a particular time

            obj = objWeakHndl.get;
            obj.AppContainer.RightCollapsed = 0;
            obj.AddTagsPanelObj.TagsPanel.Selected = 1;

        end

        function manageBookmark(objWeakHndl)
            % manageBookmark will open the bookmarkpanel

            obj = objWeakHndl.get;
            obj.AppContainer.RightCollapsed = 0;
            obj.BookmarkPanelObj.BookmarkPanel.Selected = 1;
        end

        function bmTableSelectionChanged(objWeakHndl, source, event)
            %bmTableSelectionChanged is called when user selects the row,
            %whichever row is selected the timeline should goto the
            %selected time

            obj = objWeakHndl.get;
            if isa(event, 'matlab.ui.eventdata.TableSelectionChangedData')
                selectedData = source.Data(event.Selection, :);
                currTime = selectedData.Starttime;
                if obj.Timeline.getIsViewElapseTime
                    [s,~] = obj.Timeline.getTimeLimits;
                    currTime = selectedData.Starttime +s;                    
                end
                setCurrentTime(obj.Timeline, currTime);
                if ~isempty(obj.TopicTree)
                    requestDataForTime(obj, currTime);
                end
            elseif isa(event, 'matlab.ui.eventdata.CellSelectionChangeData')
                return;
            end
        end

        function bmTableCellEditCallback(objWeakHndl, ~, event)
            %BmTableCellEditCallback is called when edits the table

            obj = objWeakHndl.get;
            currTime = event.NewData;
            if isa(event, 'matlab.ui.eventdata.CellEditData')
                [startLim, endLim] = obj.Timeline.getTimeLimits();
                if obj.Timeline.getIsViewElapseTime
                    endLim = endLim - startLim;
                    startLim = 0;                    
                end
                % Startime
                if event.DisplayIndices(2) == 2 && (~((currTime >= startLim) && (currTime <= endLim)) || isnan(event.NewData))
                    %throw an error and reset to previous value
                    errmsg.message = getString(message("ros:visualizationapp:view:InvalidStartTime", ...
                        event.EditData, num2str(startLim), num2str(endLim)));
                    throwError(obj, errmsg);
                    obj.BookmarkPanelObj.TableHandle.Data(event.DisplayIndices(1), event.DisplayIndices(2)) = {event.PreviousData};
                    return;
                % Duration
                elseif event.DisplayIndices(2) == 3 && (((currTime <0)|| (currTime >endLim) || ~(currTime <= endLim- currTime))|| isnan(event.NewData))
                    %throw an error and reset to previous value
                    if (currTime >endLim)
                        endlimitforError = endLim;
                    else 
                        endlimitforError = endLim - obj.BookmarkPanelObj.TableHandle.Data(event.DisplayIndices(1), event.DisplayIndices(2)-1).Starttime;
                    end
                    errmsg.message = getString(message("ros:visualizationapp:view:InvalidDuration", ...
                        event.EditData, num2str(endlimitforError)));
                    throwError(obj, errmsg);
                    obj.BookmarkPanelObj.TableHandle.Data(event.DisplayIndices(1), event.DisplayIndices(2)) = {event.PreviousData};
                    return;
                end
            end
            if event.DisplayIndices(2) == 2
                if obj.Timeline.getIsViewElapseTime
                    [s,~] = obj.Timeline.getTimeLimits;
                    currTime = currTime +s;                    
                end
                setCurrentTime(obj.Timeline, currTime);
                if ~isempty(obj.TopicTree)
                    requestDataForTime(obj, currTime);
                end
            end
            obj.BookmarkTabObj.updateBookmarkTable(event.DisplayIndices(1), event.DisplayIndices(2), currTime);
        end

        function bmTableClickedFcn(objWeakHndl, ~, event)
            %BmTableDoubleClickedFcn is called when user double click on
            %the bookmarktable , if it is double clicked on the cell 5 the
            %highlighted item in the table should get deleted.

            obj = objWeakHndl.get;
            if isa(event, 'matlab.ui.eventdata.ClickedData')
                if event.InteractionInformation.DisplayColumn == 4 ...
                        && ~isempty( event.InteractionInformation.DisplayRow)
                    answer = ros.internal.view.UIConfirm.run(obj.AppContainer, ...
                        getString(message("ros:visualizationapp:view:ConfirmDeleteBookmark")), ...  % message
                        getString(message("ros:visualizationapp:view:DeleteLabel")), ... % title
                        'Icon', 'question');
                    if strcmp(answer, 'OK')
                        obj.BookmarkTabObj.removeFromBookmarkTable(event.InteractionInformation.DisplayRow);
                        obj.BookmarkPanelObj.updateBookmarkTable(obj.BookmarkTabObj.BookmarkTableList);
                        obj.updateSaveAppStateFile();
                    end

                end
                if ~isempty(event.Source.Selection)
                    currTime = event.Source.Data(event.Source.Selection,:).Starttime;
                    if obj.Timeline.getIsViewElapseTime
                        [s,~] = obj.Timeline.getTimeLimits;
                        currTime = event.Source.Data(event.Source.Selection,:).Starttime +s;
                    end
                    setCurrentTime(obj.Timeline, currTime);
                    if ~isempty(obj.TopicTree)
                        requestDataForTime(obj, currTime);
                    end
                end
            end
        end

        function showBookmarkOnTimeline(objWeakHndl)
            %showBookmarkOnTimeline 
            obj = objWeakHndl.get;
            % TO DO once the slider is ready
            %disp("from viewerPresenter line 1072 - TO DO");
        end
        
        function tagsAddCallback(objWeakHndl, source, event)
            % tagsAddCallback callback to editfield in the tags panel
            obj = objWeakHndl.get;
            % do not add empty value
            newVal = strrep(event.Value, ' ', '');
            if isempty(newVal)
                return;
            end
            % duplicate tags check
            if ~isempty(obj.AddTagsPanelObj.TagsHandleTab) && ~isempty(ros.internal.utilities.findElement(obj.AddTagsPanelObj.TagsHandleTab.TagValue', newVal))
                errmsg.message = getString(message("ros:visualizationapp:view:DuplicateTag", newVal));
                throwError(obj, errmsg);
            else
                obj.AddTagsPanelObj.createTagComponent(newVal)
            end
            % reset the editfield
            source.Value = '';
            obj.updateSaveAppStateFile();
        end        
    end

    methods (Access = ?ros.internal.mixin.ROSInternalAccess) % Helper functions
        function stopPlayBack(obj)
            %stopPlayBack stop the playback

            obj.PlaybackState = false;
            obj.PlaybackTimer.stop();

            if isequal(obj.AppMode,obj.RosbagVisualization)
                stopPlayback(obj.Timeline);
            else
                stopPlayback(obj.Toolstrip);
            end
        end

        function startPlayBack(obj)
            %startPlayBack start the playback
            if isequal(obj.AppMode,obj.RosbagVisualization)
                startPlayback(obj.Timeline);
                
                %startPlayback(obj.Toolstrip);
    
                %Continue playback until stopped by user or reached end
                [tStart, tEnd] = getTimeLimits(obj.Timeline);
                t = getCurrentTime(obj.Timeline);
                if obj.PlaybackState && ...
                       ((t < tEnd && isequal(obj.PlaybackDirection,obj.ForwardPlayback)) || ...
                       (t > tStart && isequal(obj.PlaybackDirection,obj.ReversePlayback)))
    
                obj.PlaybackTimer.start();
                else
                   stopPlayBack(obj)
                   % After reaching end of the bag file, reset time slider to start position
                   requestDataForTime(obj, tStart)
                end
            else
            
                startPlayback(obj.Toolstrip);
                obj.PlaybackTimer.start();
            end
        end
    end
end

function [topic, fieldPath] = splitTopicFieldPath(fullPath)
%splitTopicFieldPath Split the topic from the path to the data field

% TODO: Make into a shared utility
splitPath = strsplit(fullPath, ".");
topic = splitPath{1};
fieldPath = {};
if numel(splitPath) > 1
    fieldPath = splitPath(2:end);
end
end

function out = isVisualizerLoadedWithTopicForPlayback(obj)
%isVisualizerLoadedWithTopicForPlayback verifies if the visualizer
% is open and datasource is not selected

out = ~(isempty(obj.VisualizerMapping) || ...
    isequal(obj.VisualizerMapping(:).DataSource, ""));
end

function newDataContainer = filterRepeatedTopics(dataContainer)
    topics = string.empty(numel(dataContainer),0);

    for idx = 1:numel(dataContainer)
        topics(idx) = strcat(dataContainer(idx).Topic, "/", dataContainer(idx).DataType);
    end

    [~,idx] = unique(topics);

    newDataContainer = dataContainer(idx);
end

function setrestoring(obj, property, value)
obj.(property) = value;
end
