function hcomponent = uicomponent(varargin)
%UICOMPONENT an enhanced replacement for UICONTROL & JAVACOMPONENT, accepting all Java Swing/AWT style components
%
%   hcomponent = UICOMPONENT('PropertyName1',value1,'PropertyName2',value2,...) 
%   creates a user interface control in the current figure window and returns
%   a handle to it. It assigns the default values to any properties you do not
%   specify. The default style, like in UICONTROL, is a pushbutton. 
%
%   UICOMPONENT(parent,...) creates a control in the specified parent handle
%   (figure, frame, uipanel, uicontainer, uiflowcontainer or uigridcontainer).
%   This is equivalent to UICOMPONENT('Parent',parent,...).
%   Note the extension to JAVACOMPONENT, which only accepts figure parents.
%   Actually, (due to internal Matlab limitations) the component is always
%   created as a child of the figure - the parent is used as a reference
%   location for the component's position (relative to the parent).
%
%   UICOMPONENT(javacomponent,...) uses the pre-existing javacomponent, and
%   just places it on-screen and returns a single handle.
%
%   UICOMPONENT properties can be set at object creation time using
%   PropertyName/PropertyValue pair arguments to UICOMPONENT, or 
%   changed later using the SET command or handle.prop=value notation.
%
%   Execute GET(H) to see a list of UICOMPONENT object properties and
%   their current values. Execute SET(H) to see a list of UICOMPONENT
%   object properties and legal property values. See the
%   <a href="matlab:doc uicontrol_props">Uicontrol Properties reference page</a> for more information.
%
%   UICOMPONENT(H) gives focus to the component specified by the handle H.
%   This enables keyboard actions on the selected component, in addition
%   to the regular mouse actions.
%
%   UICOMPONENT is intended as a direct replacement of Matlab's builtin
%   UICONTROL and JAVACOMPONENT functions. It accepts all parameters and
%   styles that UICONTROL accepts, and in addition also any other presentable 
%   <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/awt/Component.html">Java Swing/AWT component</a>.
%   The calling convention and syntax of UICONTROL were preserved for full
%   backwards compatibility.
%
%   UICOMPONENT('Style',style,{optionalConstructorArgs},...) uses the
%   requested style, which includes all of UICONTROL's styles as well as
%   any java component (see below). Optional java constructor args may be
%   passed to the style upon creation - multiple args as well as a string
%   arg should be placed in a cell-array, in order to differentiate them
%   from the following PropertyName. In most cases, properties may be 
%   modified post-creation, and need not be passed to the constructor.
%
%   UICONTROL's accepted 'Style' values (case insensitive):
%      'pushbutton','togglebutton','radiobutton','checkbox','edit'
%      'text','slider','frame','listbox','popupmenu'
%
%   UICOMPONENT's additional accepted 'Style' values (partial list):
%      1) <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/awt/package-summary.html">java.awt.*</a> objects:
%         'scrollbar','textcomponent','textarea','textfield','label',
%         'list','choice','canvas','container','button','panel',
%         'scrollpane','window','dialog','frame','filedialog'
%      2) <a href="http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/package-summary.html">javax.swing.*</a> objects:
%         'CellRendererPane','JComponent','JApplet','JWindow','JDialog',
%         'AbstractButton','BasicInternalFrameTitlePane','Box','Box.Filler',
%         'JColorChooser','JComboBox','JFileChooser','JInternalFrame',
%         'JInternalFrame.JDesktopIcon','JLabel','JLayeredPane','JList',
%         'JMenuBar','JOptionPane','JPanel','JPopupMenu','JProgressBar',
%         'JRootPane','JScrollBar','JScrollPane','JScrollPane.ScrollBar',
%         'JSeparator','JSlider','JSpinner','JSplitPane','JTabbedPane',
%         'JTable','JTableHeader','JTextComponent','JToolBar','JToolTip',
%         'JTree','JViewport','JButton','JToggleButton','JMenuItem',
%         'JCheckBoxMenuItem','JMenu','JRadioButtonMenuItem','JCheckBox',
%         'JRadioButton','JDesktopPane','JPopupMenu.Separator','JToolBar.Separator',
%         'JEditorPane','JTextArea','JTextField','JTextPane',
%         'JFormattedTextField','JPasswordField'
%      3) Any fully-qualified class name: 'javax.swing.JSlider','com.mywork.MyClass'...
%
%   Examples:
%      Example 1: (uses regular UICONTROL)
%           %creates uicontrol specified in a new figure
%           uicomponent('Style','edit','String','hello'); 
%     
%      Example 2: (uses regular UICONTROL)
%           %creates three figures and only puts uicontrol in the second figure
%           fig1 = figure;
%           fig2 = figure;
%           fig3 = figure;
%           uicomponent('Parent', fig2, 'Style', 'edit','String','hello');
%
%      More Examples: (uses UICOMPONENT's additional styles):
%           uicomponent('style','jspinner','value',7);  % simple spinner with initial value
%           uicomponent('style','javax.swing.jslider','tag','myObj');  % simple horizontal slider
%           uicomponent('style','slider', 'position',[50,50,60,150], 'value',70, ...
%                       'MajorTickSpacing',20, 'MinorTickSpacing',5, ...
%                       'Paintlabels',1,'PaintTicks',1, 'Orientation',1);  % vertical slider
%
%           h=uicomponent('style','JComboBox',{1,pi,'text'},'editable',true); % editable drop-down
%           h.javaComponent.addItem('text2');  % adding data to the drop-down post-creation
%           h.ActionPerformedCallback = @myMatlabFunction;  % setting a callback
%           uicomponent(h);  % sets the focus to component h (enabled keyboard actions)
%           string = h.JavaComponent.getSelectedItem;  % get the currently selected item
%           string = get(h,'selecteditem');  % equivalent way to do the same thing
%           string = h.sElEcTeDiTeM;  % ...a 3rd way to do the same (note case insensitivity)
%
%   Callbacks:
%     Over 30 callback hooks are exposed to the user (the exact list depends on the selected
%     style). These callbacks include mouse movement/clicks, keyboard events, focus gain/loss,
%     data changes etc. In most cases, the user would be concerned mainly with the
%     'StateChangedCallback', which is triggered whenever some property of the component has
%     changed (position, value etc.). In some cases, 'StateChangedCallback' is unavailable
%     and the user should use alternative callbacks, like 'ActionPerformedCallback'. Type
%     'set(hcomponent)' for the full list of callbacks supported by your specific hcomponent.
%
%   Programming tips:
%     1) Unless UICONTROL is used, the returned hcomponent contains 2 useful properties:
%        - JavaComponent - a reference to the created java component.
%        - MatlabHGContainer - the numeric handle h to the Matlab HG container. This is the
%          value returned by the findall/finobj matlab functions. You can still get/set all the
%          component properties using this numeric handle, but they'd be hidden unless you use
%          handle(h) or hcomponent, which is the same thing.
%     2) JAVACOMPONENT normally sets the container's UserData property to the classname of the
%        component (a string). This UICOMPONENT sets UserData to a reference to hcomponent, to
%        make it easy for novice users to see all properties without using handle().
%     3) The user is referred to <a href="http://java.sun.com/docs/books/tutorial/uiswing/index.html">Sun's official Swing Tutorial</a>
%
%   Warning:
%     This code heavily relies on undocumented and unsupported Matlab functionality.
%     It works on Matlab 7+, but use at your own risk!
%
%   Bugs and suggestions:
%     Please send to Yair Altman (altmany at gmail dot com)
%
%   Change log:
%     2007-May-18: Handle pre-existing java components
%     2007-Apr-12: Set large initial size for *Chooser classes; fixed Window subclasses issue; fixed help comment; enabled non-figure parent; enabled non-cell ctorArgs; set default Tag prop
%     2007-Apr-10: First version posted on MathWorks file exchange: <a href="http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14583">http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14583</a>
%
%   See also:
%     uicontrol, javacomponent, java, set, get, FINDJOBJ (on the <a href="http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317">file exchange</a>)

% License to use and modify this code is granted freely without warranty to all, as long as the original author is
% referenced and attributed as such. The original author maintains the right to be solely associated with this work.

% Programmed and Copyright by Yair M. Altman: altmany(at)gmail.com
% $Revision: 1.1 $  $Date: 2007/04/10 17:23:52 $

    % First, try to use uicontrol directly
    try
        hcomp = uicontrol(varargin{:});
    catch
        % Ensure that javacomponent is supported on this platform...
        error(javachk('awt'));
        %if ~usejavacomponent, error('YMA:uicomponent:Unsupported','UICOMPONENT is not supported on this platform'); end

        % Check for focus-request
        if nargin==1
            try
                % Request the focus for the supplied component's java peer
                hcomp = varargin{1};
                hcomp.JavaComponent.requestFocus;

                % Succeeded, so exit
                hcomponent = hcomp;
                return;
            catch
                error('YMA:uicomponent:IllegalComponent','In uicomponent(h), h must be a handle returned by uicomponent or uicontrol');
            end
        end

        % Parse argnames/values
        [parent,pvPairs] = parseparams(varargin);

        % Check for pre-existing javaComp
        isJavaObj = ~isempty(parent) && isjava(parent{1}(1));
        if isJavaObj
            javaComp = parent{1}(1);
            parent = [];
        end

        % Get the requested component style, position & parent
        [pvPairs,style,ctorArgs]  = getStyle   (pvPairs);
        [pvPairs,position]        = getPosition(pvPairs,style);
        [pvPairs,position,parent] = getParent  (pvPairs,position,parent);

        % Try to create the java component (if not pre-existing)
        if ~isJavaObj
            try
                % Note: feval is WAY faster than awtinvoke, so use feval whenever possible
                %javaComp = awtcreate(style);
                javaComp = feval(style,ctorArgs{:});
            catch
                % Maybe user didn't enclose ctorArgs with {}...
                javaComp = feval(style,ctorArgs);
            end
        end

        % Place the new component on-screen (invisible until we finish processing - prevents animation)
        if ~isa(javaComp,'java.awt.Window')
            [jcomp, hcontainer] = javacomponent(javaComp,position,parent);
        else
            % Workaround for javacomponent's bug with java.awt.Window and sub-classes...
            [jcomp, hcontainer] = javacomponentFix(javaComp,position,parent);
        end
        set(hcontainer,'Visible','off');

        % Note: Use the undocumented handle() to get the handle of the component's Matlab container
        % ^^^^  This is needed in order to add all of the java component's properties, below
        hcomp = handle(hcontainer);

        % Move all the container's properties to the component
        curUndoc = get(0,'hideundocumented');
        set(0,'hideundocumented','off');
        % Note: sometimes dataStruct does NOT contain all java values, so a very short pause is needed
        %pause(0.003);  % not needed now that we use localGetData
        dataStruct = get(jcomp);
        set(0,'hideundocumented',curUndoc);
        fieldNames = fieldnames(dataStruct);
        for fieldIdx = 1 : length(fieldNames)

            % Add this container field to the component
            thisFieldName = fieldNames{fieldIdx};
            try
                jsp = findprop(jcomp,thisFieldName);
                msp = schema.prop(hcomp,thisFieldName,'mxArray');  %jsp.DataType

                % Note: sometimes dataStruct still does NOT contain all java values, so get them directly
                % Note2: unused for now: we use localGetData instead
                %{
                if isempty(dataStruct.(thisFieldName)) && isempty(strfind(thisFieldName,'Callback'))
                    if ~isequal(dataStruct.(thisFieldName),get(jcomp,thisFieldName))
                        pause(0.001);  %should usually suffice
                        %disp(thisFieldName);
                        dataStruct.(thisFieldName) = get(jcomp,thisFieldName);
                    end
                end
                set(hcomp,thisFieldName,dataStruct.(thisFieldName));
                %}

                % Set the public accessability flags like in the Java original
                msp.AccessFlags.PublicGet = jsp.AccessFlags.PublicGet;
                msp.AccessFlags.PublicSet = jsp.AccessFlags.PublicSet;
                msp.GetFunction = {@localGetData,jcomp,thisFieldName};
                msp.SetFunction = {@localSetData,jcomp,thisFieldName};
                msp.Visible = jsp.Visible;
            catch
                % Never mind: property probably already exists...
                %disp([thisFieldName ': ' lasterr]);
            end

            % Link the new props between the two handles
            % Note: unused for now: we use localGet/SetData instead
            %{
            try
                % Note: can't use linkprop since jcomp goes out of scope soon and linkprop then deletes all linkages...
                linkprops([hcomp,jcomp],thisFieldName);
            catch
                % Never mind: probably cannot modify this prop
            end
            %}
        end

        % Synchronize all props upon state change
        % Note: unused for now: we use localGet/SetData instead
        %setupChangeCallback(hcomp,jcomp);

        % Store the container & component's handles in the component
        storeHandles(hcomp,jcomp,hcontainer);

        % Process the remainder of the user-supplied args (PV-pairs)
        set(hcomp,pvPairs{:});

        % Display the component on-screen, unless user requested a specific visibility
        visIdx = find(strcmpi(pvPairs,'vis'), 1);
        if isempty(visIdx)
            set(hcomp,'Visible','on');
        end
        if isa(javaComp,'java.awt.Window') && strcmpi(hcomp.Visible,'on')
            jcomp.validate();
            jcomp.show();
        end
    end

    % Refresh the screen
    drawnow;

    % Return the component handle, if requested
    if nargout
        hcomponent = hcomp;
    end
end

%% Get the java class from the classname (case-insensitive)
function javaClass = getJavaClass(className)
    % First get the class-loader fired-up
    try
        jloader = com.mathworks.jmi.ClassLoaderManager.getClassLoaderManager;
    catch
        error('YMA:uicomponent:ClassLoaderNotFound', 'Failed to get a valid Java ClassLoader');
    end

    % First try finding the class without any changes in java.awt or javax.swing
    javaClass = findAwtSwingClass(jloader, className);

    % If unfound, try fixing the className's case and retry
    if isempty(javaClass)
        javaClass = findAwtSwingClass(jloader, fixClassNameCase(className));
    end
end

%% Try to find stripped-down class in java.awt.* or javax.swing.*
function javaClass = findAwtSwingClass(jloader, className)
    % First try using the classname as-is as a first attempt
    try
        javaClass = char(jloader.findClass(className).getCanonicalName);
        % succeeded!
    catch
        % failed - name might be a stripped-down class name - try javax.swing...
        try
            javaClass = char(jloader.findClass(['javax.swing.' className]).getCanonicalName);
        catch
            % failed - try java.awt...
            try
                javaClass = char(jloader.findClass(['java.awt.' className]).getCanonicalName);
            catch
                % failed - try javax.swing.J*...
                try
                    javaClass = char(jloader.findClass(['javax.swing.J' className]).getCanonicalName);
                catch
                    javaClass = [];
                end
            end
        end
    end
end

%% Try to fix a className's case to the format used by Sun
function className = fixClassNameCase(className)
    % First lowercase everything
    className = lower(className);

    % First char is always UPPER, except if part of a package name
    if length(strfind(className,'.')) < 2
        className(1) = upper(className(1));

        % Second char is UPPER if first char == 'J'
        if className(1)=='J'
            className(2) = upper(className(2));
        end
    end

    % First char following '.' is always UPPER
    dotIdx = strfind(className,'.');
    for charIdx = 1 : length(dotIdx)
        thisIdx = dotIdx(charIdx);
        thisToken = className(thisIdx:end);
        if ~strncmp(thisToken,'.swing',6) && ~strncmp(thisToken,'.awt',4)
            className(thisIdx+1) = upper(className(thisIdx+1));
        end

        % Second char is UPPER if first char == 'J'
        if className(thisIdx+1)=='J'
            className(thisIdx+2) = upper(className(thisIdx+2));
        end
    end

    % Finally, some keywords always start with UPPER
    tokenWords = '(bar|component|area|field|pane|dialog|renderer|button|internal|frame|title|box|chooser|icon|menu|header|tip|item|)';
    className = regexprep(className,tokenWords,'${[upper($1(1)) $1(2:end)]}');
end

%% Get the requested component style
function [pvPairs,style,ctorArgs] = getStyle(pvPairs)
    % Get the requested component Style
    styleIdx = find(strcmpi(pvPairs,'style'));
    style = 'javax.swing.JButton';  % Default style is a JButton
    ctorArgs = {};
    if any(styleIdx) && styleIdx(end) < length(pvPairs)
        style = pvPairs{styleIdx(end)+1};
        if ~ischar(style)
            error('YMA:uicomponent:InvalidStyle','Invalid component style specified - must be a string');
        end

        % Get the fully-qualified (canonical) class name for the given Style, if found
        newStyle = getJavaClass(style);

        % If not found, raise error
        if isempty(newStyle)
            error('YMA:uicomponent:ClassNotFound', ['Failed to find Java class ''' style '''.']);
        end

        % Search for optional ctorArgs
        while (styleIdx(end)+2 <= length(pvPairs)) && ~ischar(pvPairs{styleIdx(end)+2})
            ctorArgs = [ctorArgs pvPairs{styleIdx(end)+2}];
            pvPairs(styleIdx(end)+2) = [];
        end

        style = newStyle;
        pvPairs([styleIdx,styleIdx+1]) = [];
    end
end

%% Get the requested component position
function [pvPairs,position] = getPosition(pvPairs,style)
    position = [];  % default position set by javacomponent to [20,20,60,20]
    if ~isempty(strfind(lower(style),'chooser'))
        position = [0,0,400,250];  % JFileChooser & JColorChooser need a large initial size
        % TODO: use getMinimumSize
    end
    positionIdx = find(strcmpi(pvPairs,'position'));
    if any(positionIdx) && positionIdx(end) < length(pvPairs)
        position = pvPairs{positionIdx(end)+1};
        if ~isnumeric(position)
            error('YMA:uicomponent:InvalidPosition','Invalid component position specified - must be a 4-element numeric position vector');
        end
        pvPairs([positionIdx,positionIdx+1]) = [];
    end
end

%% Get the requested component parent
function [pvPairs,position,parent] = getParent(pvPairs,position,parent)
    % Default parent is the current figure (create new figure if none is open)
    if isempty(parent) || ~ishandle(parent{1}(1))
        curVis = get(0,'showHiddenHandles');
        set(0,'showHiddenHandles','on');
        parent = gcf;
        set(0,'showHiddenHandles',curVis);
    else
        parent = parent{1};
        if length(parent)>1
            warning('YMA:uicomponent:TooManyParents','UICOMPONENT accepts only a single parent container - only first is used');
            parent = parent(1);
        end
    end

    % Check if a container parent is specified in the PV pairs - use the last one if possible
    parentIdx = find(strcmpi(pvPairs,'parent'));
    if any(parentIdx) && parentIdx(end) < length(pvPairs)
        parent = pvPairs{parentIdx(end)+1};
        pvPairs([parentIdx,parentIdx+1]) = [];
    end

    % Only figures are currently supported by JAVACOMPONENT as valid parents...
    hParent = handle(parent);
    if ~ishghandle(parent)
        parentStr = '';
        try
            if isnumeric(parent)
                parentStr = num2str(parent);
            else
                parentStr = char(parent);
            end
        catch
        end
        error('YMA:uicomponent:InvalidParentHandle',['Invalid parent handle specified: ' parentStr]);
    elseif ~(isa(hParent,'figure') || isa(hParent,'uicontainer') || isa(hParent,'uiflowcontainer') || isa(hParent,'uigridcontainer'))
        % We get here for all parents not accepted by JAVACOMPONENT as valid parents - try a workaround
        %error('YMA:uicomponent:InvalidParentType','Invalid parent container specified - only figure/panel handles are accepted');
        warning('YMA:uicomponent:NonFigureParent','Non-figure parent was specified - using the parent''s figure as the component''s parent\n(Type "<a href="matlab:warning off YMA:uicomponent:NonFigureParent">warning off YMA:uicomponent:NonFigureParent</a>" to suppress this warning.)');
        parentPosition = getpixelposition(parent,true);
        if isempty(position)
            position = [20,20,60,20];  % default position = bottom-left corner of parent
        end
        position = position + [parentPosition(1:2),0,0];  % pixel position relative to figure
        parent = ancestor(parent,'figure');
    end
end

%% Link property fields
function linkprops(handles,propName)
    msp = findprop(handles(1),propName);
    msp.GetFunction = {@localGetData,handles(2),propName};
    msp.SetFunction = {@localSetData,handles(2),propName};

    % This is a slower method no longer in use
    %{
    propertyListeners___ = getappdata(handles(1),'propertyListeners___');
    if isempty(propertyListeners___)
        propertyListeners___ = handle([]);
    end

    % Listener to property changes
    for hIdx = 1 : length(handles)
        prop = findprop(handles(hIdx),propName);
        if ~isempty(prop)
            listenerIdx = length(propertyListeners___) + 1;
            propertyListeners___(listenerIdx) = handle.listener(handles(hIdx),prop,'PropertyPostSet',{@localUpdateProp,handles,listenerIdx});
        else
            % never mind...
            disp(['Problematic property: ' propName]);
        end
    end
    setappdata(handles(1),'propertyListeners___',propertyListeners___);
    %}
end

%% Property update function
function localUpdateProp(eventsrc,eventdata,handles,listenerIdx)  %#ok
    try
        % Determine which is the original & target handles
        hSrc = eventdata.AffectedObject;
        if isequal(hSrc,handles(1))
            % hcomp was modified
            hDst = handles(2);  % =jcomp
            peerIdx = 1;
        else
            % jcomp was modified
            hDst = handles(1);  % =hcomp
            peerIdx = -1;
        end

        % Exit if handles are already synchronized for this property
        propName = eventsrc.Name;
        if isEqual(eventdata.NewValue,get(hDst,propName))
            return;
        end

        % Temporarily turn off listener for this property to avoid endless loop
        propertyListeners___ = getappdata(handles(1),'propertyListeners___');
        hListener = propertyListeners___(listenerIdx+peerIdx);
        set(hListener,'Enabled','off');

        % Update all linked objects that have this property
        %try
        %    newValStr = eventdata.NewValue;
        %    newValStr = num2str(newValStr);
        %catch
        %    newValStr = char(newValStr.toString);
        %end
        %disp([hDst.class ' ' propName ' => ' newValStr]);
        if isprop(hDst,propName)
            set(hDst,propName,eventdata.NewValue);
        end

        % Ensure that all props are stil in sync between the handles
        % Note: this is needed since the new value may have triggered other changes in the target handle
        if ~isprop(handles(1),'StateChangedCallback') || isempty(get(handles(1),'StateChangedCallback'))
            % ...But only do this if the state-changed callback (that takes care of this) is not available
            syncProps([],[],handles(2),handles(1));
        end

        % Restore listeners
        set(hListener,'Enabled','on');
    catch
        %disp(lasterr);  % Never mind...
    end
end

%% Check for data equality
function equalsFlag = isEqual(srcValue,dstValue)
    % Use matlab's generic isequal() method first
    equalsFlag = isequal(srcValue,dstValue);
    try
        if ~equalsFlag
            % Many java objects have applicative equals(), so use it whenever possible
            % This way, even if the reference changed it may still be considered "equal"
            equalsFlag = srcValue.equals(dstValue);
        end
    catch
        % never mind - no equals() method available in this case
    end
end

%% Synchronize java component & Matlab HG container properties
function syncProps(eventsrc,eventdata,hSrc,hDst)  %#ok

    % Init
    srcData = get(hSrc);
    dstData = get(hDst);
    fieldNames = intersect(fieldnames(srcData),fieldnames(dstData));

    % Loop over all fields and check for unsynchronized values
    for fieldIdx = 1 : length(fieldNames)
        thisFieldName = fieldNames{fieldIdx};
        if ~isEqual(srcData.(thisFieldName),dstData.(thisFieldName))
            % Found an unsynchronized value - update from jcomp => hcomp
            %disp(['  => ' hSrc.class ' ' thisFieldName]);
            overrideSet(hDst,thisFieldName,srcData.(thisFieldName));
        end
    end

    % Check for ComponentModifiedCallback
    if isprop(hDst,'ComponentChangedCallback') && ~isempty(hDst.ComponentChangedCallback)
        overrideSet(hDst,'ComponentChangedCallbackData',eventdata);
        hgfeval(hDst.ComponentChangedCallback,hDst,eventdata);
    end
end

%% Temporarily set a property value, EVEN IF it is read-only (PublicSet='off')
function overrideSet(object,fieldName,newValue)
    try
        % Get the property's read/write indication
        sp = findprop(object,fieldName);
        oldPublicSet = sp.AccessFlags.PublicSet;

        % Temporarily allow writing, EVEN IF property is read-only (PublicSet='off')
        sp.AccessFlags.PublicSet = 'on';

        % Set the property to the new value
        set(object,fieldName,newValue);

        % Restore the property's original read/write indication
        sp.AccessFlags.PublicSet = oldPublicSet;
    catch
        %disp(lasterr);  % Never mind...
    end
end

%% Setup the component change callback hooks for internal & user-defined uses
function setupChangeCallback(hcomp,jcomp)  %#ok - unused for now: we use localGet/SetData
    try
        % Disable public access to the default StateChangedCallback (used by uicomponent below)
        set(hcomp,'StateChangedCallback',{@syncProps,jcomp,hcomp});
        sp(1) = findprop(hcomp,'StateChangedCallback');
        sp(2) = findprop(hcomp,'StateChangedCallbackData');
        % Note: unfortunately, we cannot modify existing jcomp fields (see workaround below)
        %sp(3) = findprop(jcomp,'StateChangedCallback');
        %sp(4) = findprop(jcomp,'StateChangedCallbackData');
        set(sp,'Visible','off');
        set(sp,'AccessFlags.PublicSet','off');

        % We can't prevent users from modifying jcomp.StateChangedCallback, but we can alert and revert
        prop = findprop(jcomp,'StateChangedCallback');
        if ~isempty(prop)
            propertyListeners___ = getappdata(hcomp,'propertyListeners___');
            propertyListeners___(end+1) = handle.listener(jcomp,prop,'PropertyPostSet',{@localAlertCallbackModified,jcomp,hcomp});
            setappdata(hcomp,'propertyListeners___',propertyListeners___);
        end

        % Create a publicly accessible callback hook (called by StateChangedCallback)
        clear sp;
        sp(1) = schema.prop(hcomp,'ComponentChangedCallback','mxArray');
        sp(2) = schema.prop(jcomp,'ComponentChangedCallback','mxArray');
        sp(3) = schema.prop(hcomp,'ComponentChangedCallbackData','mxArray');
        sp(4) = schema.prop(jcomp,'ComponentChangedCallbackData','mxArray');
        linkprops([hcomp,jcomp],'ComponentChangedCallback');
        set(sp(3:4),'AccessFlags.PublicSet','off');  % only CallbackData is read-only
    catch
        % never mind...
        %disp(lasterr);
    end
end

%% We can't prevent the user from modifying jcomp.StateChangedCallback, but we can alert and revert
function localAlertCallbackModified(eventsrc,eventdata,jcomp,hcomp)  %#ok
    % Send a 'soft' warning to Matlab desktop
    warning('YMA:uicomponent:InvalidCallbackModified', ...
            ['Cannot modify ''StateChangedCallback'' (used internally by uicomponent). ' ...
             'Modify ''ComponentChangedCallback'' instead.']);

    % Set ComponentChangedCallback to the requested callback
    set(jcomp,'ComponentChangedCallback',get(jcomp,'StateChangedCallback'));

    % Revert StateChangedCallback
    set(jcomp,'StateChangedCallback',{@syncProps,jcomp,hcomp});
end

%% Store the container & component's handles in the component
function storeHandles(hcomp,jcomp,hcontainer)
    try
        % Matlab HG container handle
        sp(1) = schema.prop(jcomp,'MatlabHGContainer','mxArray');
        sp(2) = schema.prop(hcomp,'MatlabHGContainer','mxArray');
        set([hcomp,jcomp],'MatlabHGContainer',hcontainer);
        linkprops([hcomp,jcomp],'MatlabHGContainer');

        % Store the javaclassnamein the Tag property
        set(hcontainer,'Tag',get(hcontainer,'UserData'));

        % Store the handle in the container's UserData
        % Note: javacomponent placed the jcomp classname in here, but the correct place is
        % ^^^^  really in the Tag property, and use UserData to store the handle reference
        set(hcontainer,'UserData',hcomp);

        % Java component handle (no need to store within jcomp - only in hcomp...)
        sp(3) = schema.prop(hcomp,'JavaComponent','mxArray');
        set(hcomp,'JavaComponent',jcomp);

        % Disable public set of these handles - read only
        set(sp,'AccessFlags.PublicSet','off');
    catch
        % never mind...
        %disp(lasterr);
    end
end

%% Get the relevant property value from jcomp
function propValue = localGetData(object,propValue,jcomp,propName)  %#ok
    propValue = get(jcomp,propName);
end

%% Set the relevant property value in jcomp
function propValue = localSetData(object,propValue,jcomp,propName)  %#ok
    set(jcomp,propName,propValue);
end

%% Workaround for javacomponent's bug with java.awt.Window and sub-classes...
function [jcomp, hcontainer] = javacomponentFix(javaComp,position,parent)
    % Prepare a Matlab HG container for the java component
    % Note: this container will be a child of the requested parent, while javaComp is another window!
    if isempty(position)
        % Default window size should be larger than javacomponent's default of 60x20
        position = [100,100,100,100];
    else
        % Disallow window sizes less than 100x100
        position = [position(1:2) max(position(3:4),100)];
    end
    hcontainer = hgjavacomponent('Parent',parent,'Units','Pixels','JavaPeer',javaComp,'Position', position);

    % Prepare a handle for the java component, including all available callback hooks
    jcomp = handle(javaComp,'callbackProperties');

    % Prepare deletion callbacks, so that if any of the component or container is deleted, so will the other
    set(hcontainer, 'DeleteFcn', {@componentDelete, jcomp});
    set(jcomp, 'WindowClosingCallback', {@componentDelete, hcontainer});
    hl = handle.listener(jcomp, jcomp, 'ObjectBeingDestroyed', {@componentDelete, hcontainer});
    p = schema.prop(jcomp, 'Listeners__', 'handle vector');
    set(p,'AccessFlags.Serialize','off','AccessFlags.Copy','off','FactoryValue',[],'Visible','off');
    set(jcomp, 'Listeners__', hl);

    % Set the new figure's screen position & size
    % Note: remember that Matlab origin = bottom left while java origin = top left...
    screenSize = get(0,'ScreenSize');
    jcomp.setLocation(java.awt.Point(position(1), screenSize(4)-position(2)-position(4)));
    jcomp.setSize(java.awt.Dimension(position(3), position(4)));
end

%% Callback function for Window/Frame Java component deletion
function componentDelete(obj, evd, component) %#ok - mlint
    % delete component if it exists
    if any(ishandle(component))
        delete(component);
    end
end


%% TODO TODO TODO
