classdef component < matlab.mixin.Heterogeneous & handle
  % Base class for toolstrip components.
  
  % Author(s): Bora Eryilmaz
  % Revised:
  % Copyright 2010-2012 The MathWorks, Inc.
  
  % ----------------------------------------------------------------------------
  properties (Dependent, Access = public)
    % Component name (string, default = '')
    Name
    % Component's enabled state (logical, default = true)
    Enabled
    % Component's description shown as a tooltip (string, default = '')
    Description
  end
  
  properties (Hidden, Dependent, GetAccess = public, SetAccess = protected)
    % Component peer (JComponent subclass, read-only, default = MJButton)
    Peer
  end
  
  properties (Hidden, Dependent, GetAccess = public, SetAccess = private)
    % Parent of the component (component, default = component.empty)
    Parent
    % Children of the component (component array, default = component.empty)
    Children
  end
  
  properties (Access = protected, Transient)
    % Listeners for peer events
    Listeners
  end
  
  properties (Access = protected)
    % Current version
    Version = toolpack.ver();
  end
  
  properties (Access = private)
    % Any JComponent subclass or an MJButton for default value.
    Peer_
    % Parent component or [] for default value component.empty.
    Parent_
    % Child component array or [] for default value component.empty.
    Children_
  end
  
  % ----------------------------------------------------------------------------
  methods
    function this = component(varargin)
      % Constructor.
      %
      % Subclasses should call
      %   obj = obj@toolpack.component.component( varargin{:} )
      
      % Install look and feel for toolstrip components since they don't install
      % it themselves by default.
      com.mathworks.toolstrip.plaf.TSLookAndFeel.install
    end
    
    function value = get.Parent(this)
      % GET function for Parent property.
      value = this.Parent_;
      if isempty(value)
        value = toolpack.component.component.empty; % default
      end
    end
    
    function set.Parent(this, value)
      % SET function for Parent property.
      if isempty(value)
        value = [];
      elseif ~isa(value, 'toolpack.component.component')
        ctrlMsgUtils.error('Controllib:toolpack:InvalidPropertyValue', 'Parent')
      end
      this.Parent_ = value;
    end
    
    function value = get.Children(this)
      % GET function for Children property.
      value = this.Children_;
      if isempty(value)
        value = toolpack.component.component.empty; % default
      end
    end
    
    function set.Children(this, value)
      % SET function for Children property.
      if isempty(value)
        value = [];
      elseif ~isa(value, 'toolpack.component.component')
        ctrlMsgUtils.error('Controllib:toolpack:InvalidPropertyValue', 'Parent')
      end
      this.Children_ = value;
    end
    
    function value = get.Name(this)
      % GET function for Name property.
      value = getName_(this);
    end
    
    function set.Name(this, value)
      % SET function for Name property.
      value = controllib.internal.util.hString2Char(value);
      if isempty(value)
        value = '';
      elseif ~ischar(value)
        ctrlMsgUtils.error('Controllib:toolpack:StringArgumentNeeded')
      end
      setName_(this, value);
    end
    
    function value = get.Enabled(this)
      % GET function for Enabled property.
      value = getEnabled_(this);
    end
    
    function set.Enabled(this, value)
      % SET function for Enabled property.
      if ~islogical(value)
        ctrlMsgUtils.error('Controllib:toolpack:InvalidPropertyValue', 'Enabled')
      end
      setEnabled_(this, value);
    end
    
    function value = get.Description(this)
      % GET function for Description property.
      value = getDescription_(this);
    end
    
    function set.Description(this, value)
      % SET function for Description property.
      value = controllib.internal.util.hString2Char(value);
      if isempty(value)
        value = '';
      elseif ~ischar(value)
        ctrlMsgUtils.error('Controllib:toolpack:StringArgumentNeeded')
      end
      setDescription_(this, value);
    end
    
    function value = get.Peer(this)
      % GET function for Peer property.
      value = this.Peer_;
      if isempty(value)
        % Default peer: MJButton
        value = javaObjectEDT('com.mathworks.mwswing.MJButton', '<empty>');
        this.Peer_ = value; % Do not recreate the button every time.
      end
    end
    
    function set.Peer(this, value)
      % SET function for Peer property.
      if ~isjava(value)
        ctrlMsgUtils.error('Controllib:toolpack:InvalidPropertyValue', 'Peer')
      end
      this.Peer_ = value;
    end
    
    function add(this, obj)
      % Adds the specified component as a child in the component tree.
      tree_insert(this, obj);
    end
    
    function remove(this, obj)
      % Removes the specified child component from the component tree.
      tree_delete(this, obj);
    end
    
    function removeAll(this)
      % Removes all child components from the component tree.
      while ~isempty(this.Children)
        % Will call polymorphic remove method.
        remove(this, this.Children(end))
      end
    end
    
    function delete(this)
      % Deletes the component.
      % Remove all children from the component.
      while ~isempty(this.Children)
        %fprintf('Removing child %s from %s.\n',this.Children(end).Name,this.Name)
        remove(this, this.Children(end));
      end
      % Remove from current parent.
      if ~isempty(this.Parent)
        %fprintf('Removing %s from parent %s.\n',this.Name,this.Parent.Name)
        remove(this.Parent, this)
      end
      % fprintf('Deleting "%s" of class %s.\n', this.Name, class(this));
    end
  end
  
  methods (Access = protected)
    function value = getName_(this)
      % Default implementation. Can be overloaded by subclasses.
      value = char(this.Peer.getName);
    end
    
    function setName_(this, value)
      % Default implementation. Can be overloaded by subclasses.
      this.Peer.setName(value);
    end
    
    function value = getEnabled_(this)
      % Default implementation. Can be overloaded by subclasses.
      try
        value = this.Peer.isEnabled;
      catch %#ok<CTCH>
        value = false;
      end
    end
    
    function setEnabled_(this, value)
      % Default implementation. Can be overloaded by subclasses.
      try %#ok<TRYNC>
        this.Peer.setEnabled(value);
      end
    end
      
    function value = getDescription_(this)
      % Default implementation. Can be overloaded by subclasses.
      try
        value = char(this.Peer.getToolTipText);
      catch %#ok<CTCH>
        value = '';
      end
    end
    
    function setDescription_(this, value)
      % Default implementation. Can be overloaded by subclasses.
      try %#ok<TRYNC>
        this.Peer.setToolTipText(value);
      end
    end
  end
  
  methods (Access = private)
    function tree_insert(parent, node)
      p = node.Parent;
      if p == parent
        % Quick return if node already parented to specified parent.
        return
      end
      % Remove from current parent.
      if ~isempty(p)
        tree_delete(p, node)
      end
      node.Parent = parent;
      parent.Children(end+1) = node;
    end
    
    function tree_delete(parent, node)
      % Remove node from parent's children list
      siblings = parent.Children;
      k = length(siblings);
      while (k > 0) && (siblings(k) ~= node)
        k = k-1;
      end
      if (k>0)
        parent.Children(k) = [];
      end
      % Remove parent from node's Parent property if it is the same parent
      if node.Parent == parent
        node.Parent = [];
      end
    end
    
  end
  
  methods (Sealed, Access = protected)
    function obj = tree_search(parent, name)
      % Returns the component with the specified name among the children of the
      % specified parent or [] if no such component is found.
      children = parent.Children;
      obj = [];
      for k = 1:length(children)
        if strcmp(children(k).Name, name)
          obj = children(k);
          break;
        end
      end
    end
  end
  
  methods (Hidden)
    function qeRemoveParentReference(this)
      % Set Parent property to empty for test only
      this.Parent = [];
    end
    function qeRemoveChildReference(this,idx)
      % Set Parent property to empty for test only
      this.Children(idx) = [];
    end
  end
end
