classdef AbstractGraphicalComponent < handle
    %Abstract class providing default implementations for a graphical view
    %of a tool component.
    %
    %  Subclasses must provide implementations for the methods:
    %    vBuild                    - build the UI
    %    vUpdate                   - update UI upon construction
    %
    %  Subclasses should call
    %
    %    this = this@toolpack.AbstractGraphicalComponent(MODEL,HEIGHT,WIDTH)
    %
    %  where MODEL is the tool component model, HEIGHT and WIDTH specify
    %  the dimensions of the view.
    %
    %  The following new properties and methods are defined by this class. For
    %  inherited properties and methods, type DOC followed by the full
    %  classname.
    %
    %  AbstractGraphicalComponent Properties:
    %    Name
    %
    %  AbstractGraphicalComponent Methods:
    %    setTitle                  - Sets the title of the view
    %    getTitle                  - Returns the title of the view
    %    getHeight                 - Returns the desired height of the GUI
    %    getWidth                  - Returns the desired width of the GUI
    %    getPanel                  - Returns the panel defined by the GUI peer. If not found, a blank MJPanel is returned
    %    getPeer                   - Returns a handle to the GUI peer
    %    setPeer                   - Sets the handle of the GUI peer
    %    getModel                  - Returns the model of the tool component
    %    setParent                 - sets the parent container if any.
    %    getParent                 - returns the parent container if any.
    %    addGUIListener            - Adds listeners to "callback" events generated by the GUI
    %    addModelListener          - Adds listeners to the model of the tool component
    %
    %  See also AbstractAtomicComponent.
    
    % Author(s): Bora Eryilmaz
    % Revised: Murad Abu-Khalaf
    % Copyright 2009-2011 The MathWorks, Inc.
    
    
    %% ----------------------------------- %
    % Properties                           %
    % ------------------------------------ %
    properties (Access = private)
        % A handle to the instantiated UI
        GUIPeer
        
        % List of listeners to UI "callback" events
        GUIListeners
        
        % The tool component model
        ToolComponentModel
        
        % Listeners to the tool component model (WHY?)
        ToolComponentModelListeners
        
        % Parent container
        Parent_
              
        % Height of the GUI
        Height
        
        % Width of the GUI
        Width
        
        % Title of the view
        Title = '';
    end
    
    properties (Hidden, Dependent, GetAccess = public, SetAccess = protected)
    % Component peer (JComponent subclass, read-only, default = MJButton)
    Peer
  end
    
    properties (Access = public)
        % Name of the component (No spaces allowed)
        Name
    end
    
    properties (Access = public, Hidden = true)
        % Receive communication from other tool components
        Inport
        
        % Send communication to other tool components
        Outport
    end    
    
    %% ------ CONSTRUCTION - DESTRUCTION
    methods
        function this = AbstractGraphicalComponent(model,varargin)
            % Constructor of the view of the tool component
            
            % All listeners to ToolComponentModel should be attached to the
            % graphical Component, and not the model. Use event.listener
            % and not addlistener for that purpose.
            % ADD A CHECK TO MAKE SURE THAT varargin{1} is a subclass of
            % AbstractAtomicComponent
            this.ToolComponentModel = model;
            
            % Default Name is same as model
            this.Name = this.ToolComponentModel.Name;            
            
            lsnr = event.listener(this.getModel, 'ObjectBeingDestroyed', @(x,y) this.delete);
            %lsnr = event.listener(this.getModel, 'ObjectBeingDestroyed', @this.delete); 
            % @this.delete does not work due "Too many input arguments."
            % error. And you cannot fix that by changing the signature of
            % the delete method to be delete(this,.src,evnt) because
            % although the delete method runs, it runs as a regular method
            % and does not call delete@handle! There is no escape from
            % using anonymous functions.        
            this.addModelListener(lsnr);
            
            % Do not use addliseter. When GC is cleared, it is still
            % pointed at from TC because of the listener whose life is tied
            % to TC. EVENT.LISTENER instead will go out of scope once GC is
            % cleared.
            %addlistener(this.getModel, 'ObjectBeingDestroyed', @(x,y) this.delete);
            
            % Attach listeners to ToolComponentModel's data changes in order to
            % update GC and the Java peer with these changes
            lsnr = event.listener(this.getModel, 'ComponentChanged', @this.vUpdate);
            this.addModelListener(lsnr);
            
            if nargin > 1                
                this.Height = varargin{1};
                this.Width = varargin{2};
            else
                this.Height = 480;
                this.Width = 300;
            end
            
            % Note that invoking subclass methods from the constructor of
            % the parent class works in MATLAB like java, and unlike C++.
            % For best practice, put these calls in the inheriting
            % subclasses after the call to the parent class.
            % vBuild(this);
            % vUpdate(this);
        end
        
        function delete(this)
            %disp([class(this) ' is deleting...']);
            
            % Remove the UI panel from its parent
            this.removePanelFromParent;
            
            % Remove from Container
            p = this.getParent;
            if isa(p,'toolpack.Container') && isvalid(p)
                p.remove(this.Name);
            end
        end
    end
    
    %% ------ SERILIZATION
    methods
        % Never saved or loaded!
        %         function S = saveobj(obj)
        %         end
        %         function obj = reload(obj, S)
        %         end
    end
    
    %% ------ PORTS CONSTRUCTION AND HANDLING
    methods (Access = protected)
        function addInport(this, number)
            % ADDINPORT(OBJ,IDX) enables an Inport to this tool
            % component and return its handle. IDX is a string index
            % representing an integer.
            if ~isempty(this.Inport)
                portnumbers = {this.Outport.PortNumber};
                if any(strcmp(portnumbers,number))
                    return;
                end
            end
            p = toolpack.Port(this,number);
            addlistener(p, 'PortChanged', @this.processInportEvents);
            this.Inport = [this.Inport p];            
        end
        function addOutport(this, number)
            % ADDOUTPORT(OBJ,IDX) enables an Outport to this tool
            % component and return its handle. IDX is a string index
            % representing an integer.
            if ~isempty(this.Outport)
                portnumbers = {this.Outport.PortNumber};
                if any(strcmp(portnumbers,number))
                    return;
                end
            end
            this.Outport = [this.Outport toolpack.Port(this,number)];
        end
    end
    methods (Access = public)
        function writeToOutport(this, portnumber, data)
            % WRITETOOUTPORT(OBJ,IDX,DATA) writes DATA to Outport IDX.
            portnumbers = {this.Outport.PortNumber};
            idx = find(strcmp(portnumber,portnumbers), 1);
            if ~isempty(idx)
                this.Outport(idx).Data = data;
            else
                ctrlMsgUtils.error('Controllib:databrowser:InvalidPort',portnumber);
            end
        end
    end
        
    %% ------ USER-DEFINED METHODS
    methods (Abstract = true, Access = protected)
        % Construct GUI and propagate GUI changes to the model.
        vBuild(this);
        
        % Update the GUI by reflecting the model's data.
        vUpdate(this);
        
        % Remove the UI
        removePanelFromParent(this);
        
        % How to handle Inport events
        processInportEvents(this, src, evnt);
    end
    
    %% ------ ACCESS METHODS
    methods        
        function setTitle(this, title)
            % Sets the title of the view
            this.Title = title;
            
            % THIS SHOULD BE REROUTED TO THE JAVA GUI LIKE getPANEL
        end
        
        function r = getTitle(this)
            % Returns the title of the view
            r = this.Title;
            % THIS SHOULD BE REROUTED TO THE JAVA GUI LIKE getPANEL
            
        end
        
        function height = getHeight(this)
            % Returns the desired height of the GUI
            height = this.Height;
        end
        
        function width = getWidth(this)
            % Returns the desired width of the GUI
            width = this.Width;
        end
        
        function panel = getPanel(this)
            % Return the panel defined by the GUI peer, otherwise return a
            % blank MJPanel.
            if ~isempty(this.getPeer)
                % Assumes that GUIPeer has been created using javaObjectEDT call.
                panel = this.getPeer.getPanel;
            else
                panel = javaObjectEDT('com.mathworks.mwswing.MJPanel');
            end
        end
        
        function peer = getPeer(this)
            % Returns a handle to the GUI peer.
            peer = this.GUIPeer;
        end
        
        function setPeer(this, peer)
            % Sets the handle of the GUI peer.
            this.GUIPeer = peer;
        end
        
        function model = getModel(this)
            % Returns the model of the tool component
            model = this.ToolComponentModel;
        end
        
        function setParent(this,container)
            % Set the parent container
            this.Parent_ = container;
        end
        
        function r = getParent(this)
            % Return the parent container
            r = this.Parent_;
        end        
        
    end
    
    methods (Access = protected)
        function addGUIListener(this, cb, fcn)
            % Adds listeners to "callback" events generated by the UI
            lsnr = handle.listener( handle(cb), 'delayed', {@cbBridge, fcn} );
            this.GUIListeners = [this.GUIListeners; lsnr];
        end
        
        function addModelListener(this, lsnr)
            % Adds listeners to the model of the tool component
            this.ToolComponentModelListeners = [this.ToolComponentModelListeners; lsnr];
        end
    end
    
    methods
       % Set the name of the component
        function set.Name(this,aName)
            if isempty(this.getParent)
                this.Name = aName;
            else
                ctrlMsgUtils.error('Controllib:databrowser:CannotRename');
            end
        end
        
        function value = get.Peer(this)
            % GET function for Peer property.
            
            % This needs to be derived from JComponent for TSTearOffPopup's
            % show method to work properly            
            value = this.GUIPeer.getPanel;            
        end
        
    end
    
end

%% ----------------------------------- %
% LOCAL FUNCTIONS                      %
% ------------------------------------ %
function cbBridge(eventSource,eventData,fcn)
feval( fcn{1}, java(eventSource), eventData.JavaEvent, fcn{2:end} );
end