Contents
- Display a warning if the requested handle was not found because it's invisible
- Check existence of a (case-insensitive) optional parameter in the params list
- Get current figure (even if its 'HandleVisibility' property is 'off')
- Get Java reference to top-level (root) panel - actually, a reference to the java figure
- Traverse the container hierarchy and extract the elements within
- Get the XY location of a Java component
- Get the number of menu sub-elements
- Remove a specific tree node's data
- Process optional args
- Process 'print' option
- Process 'position' option
- Process 'size' option
- Process 'class' option
- Process 'property' option
- Process 'depth' option
- Convert property data into a string
- Get callbacks table data
- Get relative (0.0-1.0) divider location
- Try to set a treenode icon based on a container's icon
- Present the object hierarchy tree
- Rresize image pane
- "dbstop if error" causes inspect.m to croak due to a bug - so workaround by temporarily disabling this dbstop
- Restore any previous "dbstop if error"
- Recursively expand all nodes (except toolbar/menubar) in startup
- Create utility buttons
- Flash a component off/on for the specified duration
- Select tree node
- IFF utility function for annonymous cellfun funcs
- Get an HTML representation of the object's properties
- Update tooltip string with a node's data
- Expand tree node
- Get an icon image no larger than 16x16 pixels
- Get list of children nodes
- Get a node's name
- Strip standard Swing callbacks from a list of events
- Callback function for standard callbacks checkbox
- Callback function for UndocumentedMatlab.com button
- Callback function for data button
- Callback function for Export button
- Callback function for focus button
- Callback function for Inspect button
- Callback function for for updates button
- Check for existence of a newer version
- Get the first selected object (might not be the top one - depends on selection order)
- Update component callback upon callbacksTable data change
- Revert Callback table modification
- Get the Java positions of all HG text labels
- Traverse an HG container hierarchy and extract the HG elements within
- Debuggable "quiet" error-handling
- ML 7.0 - compatible ischar() function
- Set up the uitree context (right-click) menu
- Set the mouse-press callback
- Remove the extra context menu item after display
- Get the title for the tooltip and context (right-click) menu
- Handle tree mouse movement callback - used to set the tooltip & context-menu
- Request focus for a specific object handle
- TODO TODO TODO
function [handles,levels,parentIdx,listing] = findjobj(container,varargin)
%findjobj Find java objects contained within a specified java container or Matlab GUI handle % % Syntax: % [handles, levels, parentIds, listing] = findjobj(container, 'PropName',PropValue(s), ...) % % Input parameters: % container - optional handle to java container uipanel or figure. If unsupplied then current figure will be used % 'PropName',PropValue - optional list of property pairs (case insensitive). PropName may also be named -PropName % 'position' - filter results based on those elements that contain the specified X,Y position or a java element % Note: specify a Matlab position (X,Y = pixels from bottom left corner), not a java one % 'size' - filter results based on those elements that have the specified W,H (in pixels) % 'class' - filter results based on those elements that contain the substring (or java class) PropValue % Note1: filtering is case insensitive and relies on regexp, so you can pass wildcards etc. % Note2: '-class' is an undocumented findobj PropName, but only works on Matlab (not java) classes % 'property' - filter results based on those elements that possess the specified case-insensitive property string % Note1: passing a property value is possible if the argument following 'property' is a cell in the % format of {'propName','propValue'}. Example: FINDJOBJ(...,'property',{'Text','click me'}) % Note2: partial property names (e.g. 'Tex') are accepted, as long as they're not ambiguous % 'depth' - filter results based on specified depth. 0=top-level, Inf=all levels (default=Inf) % 'flat' - same as specifying: 'depth',0 % 'not' - negates the following filter: 'not','class','c' returns all elements EXCEPT those with class 'c' % 'persist' - persist figure components information, allowing much faster results for subsequent invocations % 'nomenu' - skip menu processing, for "lean" list of handles & much faster processing; % This option is the default for HG containers but not for figure, Java or no container % 'print' - display all java elements in a hierarchical list, indented appropriately % Note1: optional PropValue of element index or handle to java container % Note2: normally this option would be placed last, after all filtering is complete. Placing this % option before some filters enables debug print-outs of interim filtering results. % Note3: output is to the Matlab command window unless the 'listing' (4th) output arg is requested % 'list' - same as 'print' % 'debug' - list found component positions in the Command Window % % Output parameters: % handles - list of handles to java elements % levels - list of corresponding hierarchy level of the java elements (top=0) % parentIds - list of indexes (in unfiltered handles) of the parent container of the corresponding java element % listing - results of 'print'/'list' options (empty if these options were not specified) % % Note: If no output parameter is specified, then an interactive window will be displayed with a % ^^^^ tree view of all container components, their properties and callbacks. % % Examples: % findjobj; % display list of all javaelements of currrent figure in an interactive GUI % handles = findjobj; % get list of all java elements of current figure (inc. menus, toolbars etc.) % findjobj('print'); % list all java elements in current figure % findjobj('print',6); % list all java elements in current figure, contained within its 6th element % handles = findjobj(hButton); % hButton is a matlab button % handles = findjobj(gcf,'position',getpixelposition(hButton,1)); % same as above but also return hButton's panel % handles = findjobj(hButton,'persist'); % same as above, persist info for future reuse % handles = findjobj('class','pushbutton'); % get all pushbuttons in current figure % handles = findjobj('class','pushbutton','position',123,456); % get all pushbuttons at the specified position % handles = findjobj(gcf,'class','pushbutton','size',23,15); % get all pushbuttons with the specified size % handles = findjobj('property','Text','not','class','button'); % get all non-button elements with 'text' property % handles = findjobj('-property',{'Text','click me'}); % get all elements with 'text' property = 'click me' % % Sample usage: % hButton = uicontrol('string','click me'); % jButton = findjobj(hButton,'nomenu'); % % or: jButton = findjobj('property',{'Text','click me'}); % jButton.setFlyOverAppearance(1); % jButton.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); % set(jButton,'FocusGainedCallback',@myMatlabFunction); % some 30 callback points available... % jButton.get; % list all changeable properties... % % hEditbox = uicontrol('style','edit'); % jEditbox = findjobj(hEditbox,'nomenu'); % jEditbox.setCaretColor(java.awt.Color.red); % jEditbox.KeyTypedCallback = @myCallbackFunc; % many more callbacks where this came from... % jEdit.requestFocus; % % Known issues/limitations: % - Cannot currently process multiple container objects - just one at a time % - Initial processing is a bit slow when the figure is laden with many UI components (so better use 'persist') % - Passing a simple container Matlab handle is currently filtered by its position+size: should find a better way to do this % - Matlab uipanels are not implemented as simple java panels, and so they can't be found using this utility % - Labels have a write-only text property in java, so they can't be found using the 'property',{'Text','string'} notation % % 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: % 2012-07-25: Fixes for R2012b as well as some older Matlab releases % 2011-12-07: Fixed 'File is empty' messages in compiled apps % 2011-11-22: Fix suggested by Ward % 2011-02-01: Fixes for R2011a % 2010-06-13: Fixes for R2010b; fixed download (m-file => zip-file) % 2010-04-21: Minor fix to support combo-boxes (aka drop-down, popup-menu) on Windows % 2010-03-17: Important release: Fixes for R2010a, debug listing, objects not found, component containers that should be ignored etc. % 2010-02-04: Forced an EDT redraw before processing; warned if requested handle is invisible % 2010-01-18: Found a way to display label text next to the relevant node name % 2009-10-28: Fixed uitreenode warning % 2009-10-27: Fixed auto-collapse of invisible container nodes; added dynamic tree tooltips & context-menu; minor fix to version-check display % 2009-09-30: Fix for Matlab 7.0 as suggested by Oliver W; minor GUI fix (classname font) % 2009-08-07: Fixed edge-case of missing JIDE tables % 2009-05-24: Added support for future Matlab versions that will not support JavaFrame % 2009-05-15: Added sanity checks for axes items % 2009-04-28: Added 'debug' input arg; increased size tolerance 1px => 2px % 2009-04-23: Fixed location of popupmenus (always 20px high despite what's reported by Matlab...); fixed uiinspect processing issues; added blog link; narrower action buttons % 2009-04-09: Automatic 'nomenu' for uicontrol inputs; significant performance improvement % 2009-03-31: Fixed position of some Java components; fixed properties tooltip; fixed node visibility indication % 2009-02-26: Indicated components visibility (& auto-collapse non-visible containers); auto-highlight selected component; fixes in node icons, figure title & tree refresh; improved error handling; display FindJObj version update description if available % 2009-02-24: Fixed update check; added dedicated labels icon % 2009-02-18: Fixed compatibility with old Matlab versions % 2009-02-08: Callbacks table fixes; use uiinspect if available; fix update check according to new FEX website % 2008-12-17: R2008b compatibility % 2008-09-10: Fixed minor bug as per Johnny Smith % 2007-11-14: Fixed edge case problem with class properties tooltip; used existing object icon if available; added checkbox option to hide standard callbacks % 2007-08-15: Fixed object naming relative property priorities; added sanity check for illegal container arg; enabled desktop (0) container; cleaned up warnings about special class objects % 2007-08-03: Fixed minor tagging problems with a few Java sub-classes; displayed UIClassID if text/name/tag is unavailable % 2007-06-15: Fixed problems finding HG components found by J. Wagberg % 2007-05-22: Added 'nomenu' option for improved performance; fixed 'export handles' bug; fixed handle-finding/display bugs; "cleaner" error handling % 2007-04-23: HTMLized classname tooltip; returned top-level figure Frame handle for figure container; fixed callbacks table; auto-checked newer version; fixed Matlab 7.2 compatibility issue; added HG objects tree % 2007-04-19: Fixed edge case of missing figure; displayed tree hierarchy in interactive GUI if no output args; workaround for figure sub-menus invisible unless clicked % 2007-04-04: Improved performance; returned full listing results in 4th output arg; enabled partial property names & property values; automatically filtered out container panels if children also returned; fixed finding sub-menu items % 2007-03-20: First version posted on the MathWorks file exchange: <a href="http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317">http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317</a> % % See also: % java, handle, findobj, findall, javaGetHandles, uiinspect (on the File Exchange) % License to use and modify this code is granted freely to all interested, 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 % Ensure Java AWT is enabled error(javachk('awt')); persistent pContainer pHandles pLevels pParentIdx pPositions try % Initialize handles = handle([]); levels = []; parentIdx = []; positions = []; % Java positions start at the top-left corner %sizes = []; listing = ''; hg_levels = []; hg_handles = handle([]); % HG handles are double hg_parentIdx = []; nomenu = false; menuBarFoundFlag = false; % Force an EDT redraw before processing, to ensure all uicontrols etc. are rendered drawnow; pause(0.02); % Default container is the current figure's root panel if nargin if isempty(container) % empty container - bail out return; elseif ischar(container) % container skipped - this is part of the args list... varargin = {container, varargin{:}}; origContainer = getCurrentFigure; [container,contentSize] = getRootPanel(origContainer); elseif isequal(container,0) % root origContainer = handle(container); container = com.mathworks.mde.desk.MLDesktop.getInstance.getMainFrame; contentSize = [container.getWidth, container.getHeight]; elseif ishghandle(container) % && ~isa(container,'java.awt.Container') container = container(1); % another current limitation... hFig = ancestor(container,'figure'); origContainer = handle(container); if isa(origContainer,'uimenu') % getpixelposition doesn't work for menus... - damn! varargin = {'class','MenuPeer', 'property',{'Label',strrep(get(container,'Label'),'&','')}, varargin{:}}; elseif ~isa(origContainer, 'figure') && ~isempty(hFig) % See limitations section above: should find a better way to directly refer to the element's java container try % Note: 'PixelBounds' is undocumented and unsupported, but much faster than getpixelposition! % ^^^^ unfortunately, its Y position is inaccurate in some cases - damn! %size = get(container,'PixelBounds'); pos = fix(getpixelposition(container,1)); %varargin = {'position',pos(1:2), 'size',pos(3:4), 'not','class','java.awt.Panel', varargin{:}}; catch try figName = get(hFig,'name'); if strcmpi(get(hFig,'number'),'on') figName = regexprep(['Figure ' num2str(hFig) ': ' figName],': $',''); end mde = com.mathworks.mde.desk.MLDesktop.getInstance; jFig = mde.getClient(figName); if isempty(jFig), error('dummy'); end catch warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility jFig = get(get(hFig,'JavaFrame'),'FigurePanelContainer'); end pos = []; try pxsize = get(container,'PixelBounds'); pos = [pxsize(1)+5, jFig.getHeight - (pxsize(4)-5)]; catch % never mind... end end if size(pos,2) == 2 pos(:,3:4) = 0; end if ~isempty(pos) if isa(handle(container),'uicontrol') && strcmp(get(container,'style'),'popupmenu') % popupmenus (combo-box dropdowns) are always 20px high pos(2) = pos(2) + pos(4) - 20; pos(4) = 20; end %varargin = {'position',pos(1:2), 'size',size(3:4)-size(1:2)-10, 'not','class','java.awt.Panel', varargin{:}}; varargin = {'position',pos(1:2)+[0,pos(4)], 'size',pos(3:4), 'not','class','java.awt.Panel', 'nomenu', varargin{:}}; end elseif isempty(hFig) hFig = handle(container); end [container,contentSize] = getRootPanel(hFig); elseif isjava(container) % Maybe a java container obj (will crash otherwise) origContainer = container; contentSize = [container.getWidth, container.getHeight]; else error('YMA:findjobj:IllegalContainer','Input arg does not appear to be a valid GUI object'); end else % Default container = current figure origContainer = getCurrentFigure; [container,contentSize] = getRootPanel(origContainer); end % Check persistency if isequal(pContainer,container) % persistency requested and the same container is reused, so reuse the hierarchy information [handles,levels,parentIdx,positions] = deal(pHandles, pLevels, pParentIdx, pPositions); else % Pre-allocate space of complex data containers for improved performance handles = repmat(handles,1,1000); positions = zeros(1000,2); % Check whether to skip menu processing nomenu = paramSupplied(varargin,'nomenu'); % Traverse the container hierarchy and extract the elements within traverseContainer(container,0,1); % Remove unnecessary pre-allocated elements dataLen = length(levels); handles (dataLen+1:end) = []; positions(dataLen+1:end,:) = []; end % Process persistency check before any filtering is done if paramSupplied(varargin,'persist') [pContainer, pHandles, pLevels, pParentIdx, pPositions] = deal(container,handles,levels,parentIdx,positions); end % Save data for possible future use in presentObjectTree() below allHandles = handles; allLevels = levels; allParents = parentIdx; selectedIdx = 1:length(handles); %[positions(:,1)-container.getX, container.getHeight - positions(:,2)] % Debug-list all found compponents and their positions if paramSupplied(varargin,'debug') for handleIdx = 1 : length(allHandles) thisObj = handles(handleIdx); pos = sprintf('%d,%d %dx%d',[positions(handleIdx,:) getWidth(thisObj) getHeight(thisObj)]); disp([repmat(' ',1,levels(handleIdx)) '[' pos '] ' char(toString(thisObj))]); end end % Process optional args % Note: positions is NOT returned since it's based on java coord system (origin = top-left): will confuse Matlab users processArgs(varargin{:}); %#ok % De-cell and trim listing, if only one element was found (no use for indented listing in this case) if iscell(listing) && length(listing)==1 listing = strtrim(listing{1}); end % If no output args and no listing requested, present the FINDJOBJ interactive GUI if nargout == 0 && isempty(listing) % Get all label positions hg_labels = []; labelPositions = getLabelsJavaPos(container); % Present the GUI (object tree) if ~isempty(container) presentObjectTree(); else warnInvisible; end % Display the listing, if this was specifically requested yet no relevant output arg was specified elseif nargout < 4 && ~isempty(listing) if ~iscell(listing) disp(listing); else for listingIdx = 1 : length(listing) disp(listing{listingIdx}); end end end % Display a warning if the requested handle was not found because it's invisible if nargout && isempty(handles) warnInvisible; end return; %debug point catch % 'Cleaner' error handling - strip the stack info etc. err = lasterror; %#ok err.message = regexprep(err.message,'Error using ==> [^\n]+\n',''); if isempty(findstr(mfilename,err.message)) % Indicate error origin, if not already stated within the error message err.message = [mfilename ': ' err.message]; end rethrow(err); end
Display a warning if the requested handle was not found because it's invisible
function warnInvisible try stk = dbstack; stkNames = {stk.name}; OutputFcnIdx = find(~cellfun(@isempty,regexp(stkNames,'_OutputFcn'))); if ~isempty(OutputFcnIdx) OutputFcnName = stkNames{OutputFcnIdx}; warning('YMA:FindJObj:OutputFcn',['No Java reference was found for the requested handle, because the figure is still invisible in ' OutputFcnName '()']); elseif ishandle(origContainer) && isprop(origContainer,'Visible') && strcmpi(get(origContainer,'Visible'),'off') warning('YMA:FindJObj:invisibleHandle','No Java reference was found for the requested handle, probably because it is still invisible'); end catch % Never mind... end end
Check existence of a (case-insensitive) optional parameter in the params list
function [flag,idx] = paramSupplied(paramsList,paramName) %idx = find(~cellfun('isempty',regexpi(paramsList(cellfun(@ischar,paramsList)),['^-?' paramName]))); idx = find(~cellfun('isempty',regexpi(paramsList(cellfun('isclass',paramsList,'char')),['^-?' paramName]))); % 30/9/2009 fix for ML 7.0 suggested by Oliver W flag = any(idx); end
Get current figure (even if its 'HandleVisibility' property is 'off')
function curFig = getCurrentFigure oldShowHidden = get(0,'ShowHiddenHandles'); set(0,'ShowHiddenHandles','on'); % minor fix per Johnny Smith curFig = gcf; set(0,'ShowHiddenHandles',oldShowHidden); end
Get Java reference to top-level (root) panel - actually, a reference to the java figure
function [jRootPane,contentSize] = getRootPanel(hFig) try contentSize = [0,0]; % initialize jRootPane = hFig; figName = get(hFig,'name'); if strcmpi(get(hFig,'number'),'on') figName = regexprep(['Figure ' num2str(hFig) ': ' figName],': $',''); end mde = com.mathworks.mde.desk.MLDesktop.getInstance; jFigPanel = mde.getClient(figName); jRootPane = jFigPanel; jRootPane = jFigPanel.getRootPane; catch try warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility jFrame = get(hFig,'JavaFrame'); jFigPanel = get(jFrame,'FigurePanelContainer'); jRootPane = jFigPanel; jRootPane = jFigPanel.getComponent(0).getRootPane; catch % Never mind end end try % If invalid RootPane - try another method... warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility jFrame = get(hFig,'JavaFrame'); jAxisComponent = get(jFrame,'AxisComponent'); jRootPane = jAxisComponent.getParent.getParent.getRootPane; catch % Never mind end try % If invalid RootPane, retry up to N times tries = 10; while isempty(jRootPane) && tries>0 % might happen if figure is still undergoing rendering... drawnow; pause(0.001); tries = tries - 1; jRootPane = jFigPanel.getComponent(0).getRootPane; end % If still invalid, use FigurePanelContainer which is good enough in 99% of cases... (menu/tool bars won't be accessible, though) if isempty(jRootPane) jRootPane = jFigPanel; end contentSize = [jRootPane.getWidth, jRootPane.getHeight]; % Try to get the ancestor FigureFrame jRootPane = jRootPane.getTopLevelAncestor; catch % Never mind - FigurePanelContainer is good enough in 99% of cases... (menu/tool bars won't be accessible, though) end end
Traverse the container hierarchy and extract the elements within
function traverseContainer(jcontainer,level,parent) persistent figureComponentFound menuRootFound % Record the data for this node %disp([repmat(' ',1,level) '<= ' char(jcontainer.toString)]) thisIdx = length(levels) + 1; levels(thisIdx) = level; parentIdx(thisIdx) = parent; handles(thisIdx) = handle(jcontainer,'callbackproperties'); try positions(thisIdx,:) = getXY(jcontainer); %sizes(thisIdx,:) = [jcontainer.getWidth, jcontainer.getHeight]; catch positions(thisIdx,:) = [0,0]; %sizes(thisIdx,:) = [0,0]; end if level>0 positions(thisIdx,:) = positions(thisIdx,:) + positions(parent,:); if ~figureComponentFound && ... strcmp(jcontainer.getName,'fComponentContainer') && ... isa(jcontainer,'com.mathworks.hg.peer.FigureComponentContainer') % there are 2 FigureComponentContainers - only process one... % restart coordinate system, to exclude menu & toolbar areas positions(thisIdx,:) = positions(thisIdx,:) - [jcontainer.getRootPane.getX, jcontainer.getRootPane.getY]; figureComponentFound = true; end elseif level==1 positions(thisIdx,:) = positions(thisIdx,:) + positions(parent,:); else % level 0 - initialize flags used later figureComponentFound = false; menuRootFound = false; end parentId = length(parentIdx); % Traverse Menu items, unless the 'nomenu' option was requested if ~nomenu try for child = 1 : getNumMenuComponents(jcontainer) traverseContainer(jcontainer.getMenuComponent(child-1),level+1,parentId); end catch % Probably not a Menu container, but maybe a top-level JMenu, so discard duplicates %if isa(handles(end).java,'javax.swing.JMenuBar') if ~menuRootFound && strcmp(handles(end).java.class,'javax.swing.JMenuBar') %faster... if removeDuplicateNode(thisIdx) menuRootFound = true; return; end end end end % Now recursively process all this node's children (if any), except menu items if so requested %if isa(jcontainer,'java.awt.Container') try % try-catch is faster than checking isa(jcontainer,'java.awt.Container')... %if jcontainer.getComponentCount, jcontainer.getComponents, end if ~nomenu || menuBarFoundFlag || isempty(strfind(jcontainer.class,'FigureMenuBar')) lastChildComponent = java.lang.Object; child = 0; while (child < jcontainer.getComponentCount) childComponent = jcontainer.getComponent(child); % Looping over menus sometimes causes jcontainer to get mixed up (probably a JITC bug), so identify & fix if isequal(childComponent,lastChildComponent) child = child + 1; childComponent = jcontainer.getComponent(child); end lastChildComponent = childComponent; %disp([repmat(' ',1,level) '=> ' num2str(child) ': ' char(childComponent.class)]) traverseContainer(childComponent,level+1,parentId); child = child + 1; end else menuBarFoundFlag = true; % use this flag to skip further testing for FigureMenuBar end catch % do nothing - probably not a container %dispError end % ...and yet another type of child traversal... try if ~isdeployed % prevent 'File is empty' messages in compiled apps jc = jcontainer.java; else jc = jcontainer; end for child = 1 : jc.getChildCount traverseContainer(jc.getChildAt(child-1),level+1,parentId); end catch % do nothing - probably not a container %dispError end % TODO: Add axis (plot) component handles end
Get the XY location of a Java component
function xy = getXY(jcontainer) % Note: getX/getY are better than get(..,'X') (mem leaks), % ^^^^ but sometimes they fail and revert to getx.m ... % Note2: try awtinvoke() catch is faster than checking ismethod()... % Note3: using AWTUtilities.invokeAndWait() directly is even faster than awtinvoke()... try %if ismethod(jcontainer,'getX') %positions(thisIdx,:) = [jcontainer.getX, jcontainer.getY]; cls = getClass(jcontainer); location = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getLocation',[]),[]); x = location.getX; y = location.getY; catch %else try x = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getX',[]),[]); y = com.mathworks.jmi.AWTUtilities.invokeAndWait(jcontainer,getMethod(cls,'getY',[]),[]); catch try x = awtinvoke(jcontainer,'getX()'); y = awtinvoke(jcontainer,'getY()'); catch x = get(jcontainer,'X'); y = get(jcontainer,'Y'); end end end %positions(thisIdx,:) = [x, y]; xy = [x,y]; end
Get the number of menu sub-elements
function numMenuComponents = getNumMenuComponents(jcontainer) % The following line will raise an Exception for anything except menus numMenuComponents = jcontainer.getMenuComponentCount; % No error so far, so this must be a menu container... % Note: Menu subitems are not visible until the top-level (root) menu gets initial focus... % Try several alternatives, until we get a non-empty menu (or not...) % TODO: Improve performance - this takes WAY too long... if jcontainer.isTopLevelMenu && (numMenuComponents==0) jcontainer.requestFocus; numMenuComponents = jcontainer.getMenuComponentCount; if (numMenuComponents == 0) drawnow; pause(0.001); numMenuComponents = jcontainer.getMenuComponentCount; if (numMenuComponents == 0) jcontainer.setSelected(true); numMenuComponents = jcontainer.getMenuComponentCount; if (numMenuComponents == 0) drawnow; pause(0.001); numMenuComponents = jcontainer.getMenuComponentCount; if (numMenuComponents == 0) jcontainer.doClick; % needed in order to populate the sub-menu components numMenuComponents = jcontainer.getMenuComponentCount; if (numMenuComponents == 0) drawnow; %pause(0.001); numMenuComponents = jcontainer.getMenuComponentCount; jcontainer.doClick; % close menu by re-clicking... if (numMenuComponents == 0) drawnow; %pause(0.001); numMenuComponents = jcontainer.getMenuComponentCount; end else % ok - found sub-items % Note: no need to close menu since this will be done when focus moves to the FindJObj window %jcontainer.doClick; % close menu by re-clicking... end end end jcontainer.setSelected(false); % de-select the menu end end end end
Remove a specific tree node's data
function nodeRemovedFlag = removeDuplicateNode(thisIdx) nodeRemovedFlag = false; for idx = 1 : thisIdx-1 if isequal(handles(idx),handles(thisIdx)) levels(thisIdx) = []; parentIdx(thisIdx) = []; handles(thisIdx) = []; positions(thisIdx,:) = []; %sizes(thisIdx,:) = []; nodeRemovedFlag = true; return; end end end
Process optional args
function processArgs(varargin) % Initialize invertFlag = false; listing = ''; % Loop over all optional args while ~isempty(varargin) && ~isempty(handles) % Process the arg (and all its params) foundIdx = 1 : length(handles); if iscell(varargin{1}), varargin{1} = varargin{1}{1}; end if ~isempty(varargin{1}) && varargin{1}(1)=='-' varargin{1}(1) = []; end switch lower(varargin{1}) case 'not' invertFlag = true; case 'position' [varargin,foundIdx] = processPositionArgs(varargin{:}); if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end case 'size' [varargin,foundIdx] = processSizeArgs(varargin{:}); if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end case 'class' [varargin,foundIdx] = processClassArgs(varargin{:}); if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end case 'property' [varargin,foundIdx] = processPropertyArgs(varargin{:}); if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end case 'depth' [varargin,foundIdx] = processDepthArgs(varargin{:}); if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end case 'flat' varargin = {'depth',0, varargin{min(2:end):end}}; [varargin,foundIdx] = processDepthArgs(varargin{:}); if invertFlag, foundIdx = ~foundIdx; invertFlag = false; end case {'print','list'} [varargin,listing] = processPrintArgs(varargin{:}); case {'persist','nomenu','debug'} % ignore - already handled in main function above otherwise error('YMA:findjobj:IllegalOption',['Option ' num2str(varargin{1}) ' is not a valid option. Type ''help ' mfilename ''' for the full options list.']); end % If only parent-child pairs found foundIdx = find(foundIdx); if ~isempty(foundIdx) && isequal(parentIdx(foundIdx(2:2:end)),foundIdx(1:2:end)) % Return just the children (the parent panels are uninteresting) foundIdx(1:2:end) = []; end % If several possible handles were found and the first is the container of the second if length(foundIdx) > 1 && isequal(handles(foundIdx(1)).java, handles(foundIdx(2)).getParent) % Discard the uninteresting component container foundIdx(1) = []; end % Filter the results selectedIdx = selectedIdx(foundIdx); handles = handles(foundIdx); levels = levels(foundIdx); parentIdx = parentIdx(foundIdx); positions = positions(foundIdx,:); % Remove this arg and proceed to the next one varargin(1) = []; end % Loop over all args end
Process 'print' option
function [varargin,listing] = processPrintArgs(varargin) if length(varargin)<2 || ischar(varargin{2}) % No second arg given, so use the first available element listingContainer = handles(1); %#ok - used in evalc below else % Get the element to print from the specified second arg if isnumeric(varargin{2}) && (varargin{2} == fix(varargin{2})) % isinteger doesn't work on doubles... if (varargin{2} > 0) && (varargin{2} <= length(handles)) listingContainer = handles(varargin{2}); %#ok - used in evalc below elseif varargin{2} > 0 error('YMA:findjobj:IllegalPrintFilter','Print filter index %g > number of available elements (%d)',varargin{2},length(handles)); else error('YMA:findjobj:IllegalPrintFilter','Print filter must be a java handle or positive numeric index into handles'); end elseif ismethod(varargin{2},'list') listingContainer = varargin{2}; %#ok - used in evalc below else error('YMA:findjobj:IllegalPrintFilter','Print filter must be a java handle or numeric index into handles'); end varargin(2) = []; end % use evalc() to capture output into a Matlab variable %listing = evalc('listingContainer.list'); % Better solution: loop over all handles and process them one by one listing = cell(length(handles),1); for componentIdx = 1 : length(handles) listing{componentIdx} = [repmat(' ',1,levels(componentIdx)) char(handles(componentIdx).toString)]; end end
Process 'position' option
function [varargin,foundIdx] = processPositionArgs(varargin) if length(varargin)>1 positionFilter = varargin{2}; %if (isjava(positionFilter) || iscom(positionFilter)) && ismethod(positionFilter,'getLocation') try % try-catch is faster... % Java/COM object passed - get its position positionFilter = positionFilter.getLocation; filterXY = [positionFilter.getX, positionFilter.getY]; catch if ~isscalar(positionFilter) % position vector passed if (length(positionFilter)>=2) && isnumeric(positionFilter) % Remember that java coordinates start at top-left corner, Matlab coords start at bottom left... %positionFilter = java.awt.Point(positionFilter(1), container.getHeight - positionFilter(2)); filterXY = [container.getX + positionFilter(1), container.getY + contentSize(2) - positionFilter(2)]; % Check for full Matlab position vector (x,y,w,h) %if (length(positionFilter)==4) % varargin{end+1} = 'size'; % varargin{end+1} = fix(positionFilter(3:4)); %end else error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); end elseif length(varargin)>2 % x,y passed as separate arg values if isnumeric(positionFilter) && isnumeric(varargin{3}) % Remember that java coordinates start at top-left corner, Matlab coords start at bottom left... %positionFilter = java.awt.Point(positionFilter, container.getHeight - varargin{3}); filterXY = [container.getX + positionFilter, container.getY + contentSize(2) - varargin{3}]; varargin(3) = []; else error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); end else error('YMA:findjobj:IllegalPositionFilter','Position filter must be a java UI component, or X,Y pair'); end end % Compute the required element positions in order to be eligible for a more detailed examination % Note: based on the following constraints: 0 <= abs(elementX-filterX) + abs(elementY+elementH-filterY) < 7 baseDeltas = [positions(:,1)-filterXY(1), positions(:,2)-filterXY(2)]; % faster than repmat()... %baseHeight = - baseDeltas(:,2);% -abs(baseDeltas(:,1)); %minHeight = baseHeight - 7; %maxHeight = baseHeight + 7; %foundIdx = ~arrayfun(@(b)(invoke(b,'contains',positionFilter)),handles); % ARGH! - disallowed by Matlab! %foundIdx = repmat(false,1,length(handles)); %foundIdx(length(handles)) = false; % faster than repmat()... foundIdx = (abs(baseDeltas(:,1)) < 7) & (abs(baseDeltas(:,2)) < 7); % & (minHeight >= 0); %fi = find(foundIdx); %for componentIdx = 1 : length(fi) %foundIdx(componentIdx) = handles(componentIdx).getBounds.contains(positionFilter); % Search for a point no farther than 7 pixels away (prevents rounding errors) %foundIdx(componentIdx) = handles(componentIdx).getLocationOnScreen.distanceSq(positionFilter) < 50; % fails for invisible components... %p = java.awt.Point(positions(componentIdx,1), positions(componentIdx,2) + handles(componentIdx).getHeight); %foundIdx(componentIdx) = p.distanceSq(positionFilter) < 50; %foundIdx(componentIdx) = sum(([baseDeltas(componentIdx,1),baseDeltas(componentIdx,2)+handles(componentIdx).getHeight]).^2) < 50; % Following is the fastest method found to date: only eligible elements are checked in detailed % elementHeight = handles(fi(componentIdx)).getHeight; % foundIdx(fi(componentIdx)) = elementHeight > minHeight(fi(componentIdx)) && ... % elementHeight < maxHeight(fi(componentIdx)); %disp([componentIdx,elementHeight,minHeight(fi(componentIdx)),maxHeight(fi(componentIdx)),foundIdx(fi(componentIdx))]) %end varargin(2) = []; else foundIdx = []; end end
Process 'size' option
function [varargin,foundIdx] = processSizeArgs(varargin) if length(varargin)>1 sizeFilter = lower(varargin{2}); %if (isjava(sizeFilter) || iscom(sizeFilter)) && ismethod(sizeFilter,'getSize') try % try-catch is faster... % Java/COM object passed - get its size sizeFilter = sizeFilter.getSize; filterWidth = sizeFilter.getWidth; filterHeight = sizeFilter.getHeight; catch if ~isscalar(sizeFilter) % size vector passed if (length(sizeFilter)>=2) && isnumeric(sizeFilter) %sizeFilter = java.awt.Dimension(sizeFilter(1),sizeFilter(2)); filterWidth = sizeFilter(1); filterHeight = sizeFilter(2); else error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); end elseif length(varargin)>2 % w,h passed as separate arg values if isnumeric(sizeFilter) && isnumeric(varargin{3}) %sizeFilter = java.awt.Dimension(sizeFilter,varargin{3}); filterWidth = sizeFilter; filterHeight = varargin{3}; varargin(3) = []; else error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); end else error('YMA:findjobj:IllegalSizeFilter','Size filter must be a java UI component, or W,H pair'); end end %foundIdx = ~arrayfun(@(b)(invoke(b,'contains',sizeFilter)),handles); % ARGH! - disallowed by Matlab! foundIdx(length(handles)) = false; % faster than repmat()... for componentIdx = 1 : length(handles) %foundIdx(componentIdx) = handles(componentIdx).getSize.equals(sizeFilter); % Allow a 2-pixel tollerance to account for non-integer pixel sizes foundIdx(componentIdx) = abs(handles(componentIdx).getWidth - filterWidth) <= 3 && ... % faster than getSize.equals() abs(handles(componentIdx).getHeight - filterHeight) <= 3; end varargin(2) = []; else foundIdx = []; end end
Process 'class' option
function [varargin,foundIdx] = processClassArgs(varargin) if length(varargin)>1 classFilter = varargin{2}; %if ismethod(classFilter,'getClass') try % try-catch is faster... classFilter = char(classFilter.getClass); catch if ~ischar(classFilter) error('YMA:findjobj:IllegalClassFilter','Class filter must be a java object, class or string'); end end % Now convert all java classes to java.lang.Strings and compare to the requested filter string try foundIdx(length(handles)) = false; % faster than repmat()... jClassFilter = java.lang.String(classFilter).toLowerCase; for componentIdx = 1 : length(handles) % Note: JVM 1.5's String.contains() appears slightly slower and is available only since Matlab 7.2 foundIdx(componentIdx) = handles(componentIdx).getClass.toString.toLowerCase.indexOf(jClassFilter) >= 0; end catch % Simple processing: slower since it does extra processing within opaque.char() for componentIdx = 1 : length(handles) % Note: using @toChar is faster but returns java String, not a Matlab char foundIdx(componentIdx) = ~isempty(regexpi(char(handles(componentIdx).getClass),classFilter)); end end varargin(2) = []; else foundIdx = []; end end
Process 'property' option
function [varargin,foundIdx] = processPropertyArgs(varargin) if length(varargin)>1 propertyName = varargin{2}; if iscell(propertyName) if length(propertyName) == 2 propertyVal = propertyName{2}; propertyName = propertyName{1}; elseif length(propertyName) == 1 propertyName = propertyName{1}; else error('YMA:findjobj:IllegalPropertyFilter','Property filter must be a string (case insensitive name of property) or cell array {propName,propValue}'); end end if ~ischar(propertyName) error('YMA:findjobj:IllegalPropertyFilter','Property filter must be a string (case insensitive name of property) or cell array {propName,propValue}'); end propertyName = lower(propertyName); %foundIdx = arrayfun(@(h)isprop(h,propertyName),handles); % ARGH! - disallowed by Matlab! foundIdx(length(handles)) = false; % faster than repmat()... % Split processing depending on whether a specific property value was requested (ugly but faster...) if exist('propertyVal','var') for componentIdx = 1 : length(handles) try % Find out whether this element has the specified property % Note: findprop() and its return value schema.prop are undocumented and unsupported! prop = findprop(handles(componentIdx),propertyName); % faster than isprop() & enables partial property names % If found, compare it to the actual element's property value foundIdx(componentIdx) = ~isempty(prop) && isequal(get(handles(componentIdx),prop.Name),propertyVal); catch % Some Java classes have a write-only property (like LabelPeer with 'Text'), so we end up here % In these cases, simply assume that the property value doesn't match and continue foundIdx(componentIdx) = false; end end else for componentIdx = 1 : length(handles) try % Find out whether this element has the specified property % Note: findprop() and its return value schema.prop are undocumented and unsupported! foundIdx(componentIdx) = ~isempty(findprop(handles(componentIdx),propertyName)); catch foundIdx(componentIdx) = false; end end end varargin(2) = []; else foundIdx = []; end end
Process 'depth' option
function [varargin,foundIdx] = processDepthArgs(varargin) if length(varargin)>1 level = varargin{2}; if ~isnumeric(level) error('YMA:findjobj:IllegalDepthFilter','Depth filter must be a number (=maximal element depth)'); end foundIdx = (levels <= level); varargin(2) = []; else foundIdx = []; end end
Convert property data into a string
function data = charizeData(data) if isa(data,'com.mathworks.hg.types.HGCallback') data = get(data,'Callback'); end if ~ischar(data) newData = strtrim(evalc('disp(data)')); try newData = regexprep(newData,' +',' '); newData = regexprep(newData,'Columns \d+ through \d+\s',''); newData = regexprep(newData,'Column \d+\s',''); catch %never mind... end if iscell(data) newData = ['{ ' newData ' }']; elseif isempty(data) newData = ''; elseif isnumeric(data) || islogical(data) || any(ishandle(data)) || numel(data) > 1 %&& ~isscalar(data) newData = ['[' newData ']']; end data = newData; elseif ~isempty(data) data = ['''' data '''']; end end % charizeData
Get callbacks table data
function [cbData, cbHeaders, cbTableEnabled] = getCbsData(obj, stripStdCbsFlag) classHdl = classhandle(handle(obj)); cbNames = get(classHdl.Events,'Name'); if ~isempty(cbNames) && ~iscom(obj) %only java-based please... cbNames = strcat(cbNames,'Callback'); end propNames = get(classHdl.Properties,'Name'); propCbIdx = []; if ~isempty(propNames) propCbIdx = find(~cellfun(@isempty,regexp(propNames,'(Fcn|Callback)$'))); cbNames = unique([cbNames; propNames(propCbIdx)]); %#ok logical is faster but less debuggable... end if ~isempty(cbNames) if stripStdCbsFlag cbNames = stripStdCbs(cbNames); end if iscell(cbNames) cbNames = sort(cbNames); end hgHandleFlag = 0; try hgHandleFlag = ishghandle(obj); catch, end %#ok try obj = handle(obj,'CallbackProperties'); catch hgHandleFlag = 1; end if hgHandleFlag % HG handles don't allow CallbackProperties - search only for *Fcn cbNames = propNames(propCbIdx); end if iscom(obj) cbs = obj.eventlisteners; if ~isempty(cbs) cbNamesRegistered = cbs(:,1); cbData = setdiff(cbNames,cbNamesRegistered); %cbData = charizeData(cbData); if size(cbData,2) > size(cbData(1)) cbData = cbData'; end cbData = [cbData, cellstr(repmat(' ',length(cbData),1))]; cbData = [cbData; cbs]; [sortedNames, sortedIdx] = sort(cbData(:,1)); sortedCbs = cellfun(@charizeData,cbData(sortedIdx,2),'un',0); cbData = [sortedNames, sortedCbs]; else cbData = [cbNames, cellstr(repmat(' ',length(cbNames),1))]; end elseif iscell(cbNames) cbNames = sort(cbNames); %cbData = [cbNames, get(obj,cbNames)']; cbData = cbNames; for idx = 1 : length(cbNames) try cbData{idx,2} = charizeData(get(obj,cbNames{idx})); catch cbData{idx,2} = '(callback value inaccessible)'; end end else % only one event callback %cbData = {cbNames, get(obj,cbNames)'}; %cbData{1,2} = charizeData(cbData{1,2}); try cbData = {cbNames, charizeData(get(obj,cbNames))}; catch cbData = {cbNames, '(callback value inaccessible)'}; end end cbHeaders = {'Callback name','Callback value'}; cbTableEnabled = true; else cbData = {'(no callbacks)'}; cbHeaders = {'Callback name'}; cbTableEnabled = false; end end % getCbsData
Get relative (0.0-1.0) divider location
function divLocation = getRalativeDivlocation(jDiv) divLocation = jDiv.getDividerLocation; if divLocation > 1 % i.e. [pixels] visibleRect = jDiv.getVisibleRect; if jDiv.getOrientation == 0 % vertical start = visibleRect.getY; extent = visibleRect.getHeight - start; else start = visibleRect.getX; extent = visibleRect.getWidth - start; end divLocation = (divLocation - start) / extent; end end % getRalativeDivlocation
Try to set a treenode icon based on a container's icon
function setTreeNodeIcon(treenode,container) try iconImage = []; iconImage = container.getIcon; if ~isempty(findprop(handle(iconImage),'Image')) % get(iconImage,'Image') is easier but leaks memory... iconImage = iconImage.getImage; else a=b; %#ok cause an error end catch try iconImage = container.getIconImage; catch try if ~isempty(iconImage) ge = java.awt.GraphicsEnvironment.getLocalGraphicsEnvironment; gd = ge.getDefaultScreenDevice; gc = gd.getDefaultConfiguration; image = gc.createCompatibleImage(iconImage.getIconWidth, iconImage.getIconHeight); % a BufferedImage object g = image.createGraphics; iconImage.paintIcon([], g, 0, 0); g.dispose; iconImage = image; end catch % never mind... end end end if ~isempty(iconImage) iconImage = setIconSize(iconImage); treenode.setIcon(iconImage); end end % setTreeNodeIcon
Present the object hierarchy tree
function presentObjectTree() import java.awt.* import javax.swing.* hTreeFig = findall(0,'tag','findjobjFig'); iconpath = [matlabroot, '/toolbox/matlab/icons/']; cbHideStd = 0; % Initial state of the cbHideStdCbs checkbox if isempty(hTreeFig) % Prepare the figure hTreeFig = figure('tag','findjobjFig','menuBar','none','toolBar','none','Name','FindJObj','NumberTitle','off','handleVisibility','off','IntegerHandle','off'); figIcon = ImageIcon([iconpath 'tool_legend.gif']); drawnow; try mde = com.mathworks.mde.desk.MLDesktop.getInstance; jTreeFig = mde.getClient('FindJObj').getTopLevelAncestor; jTreeFig.setIcon(figIcon); catch warning('off','MATLAB:HandleGraphics:ObsoletedProperty:JavaFrame'); % R2008b compatibility jTreeFig = get(hTreeFig,'JavaFrame'); jTreeFig.setFigureIcon(figIcon); end vsplitPaneLocation = 0.8; hsplitPaneLocation = 0.5; else % Remember cbHideStdCbs checkbox & dividers state for later userdata = get(hTreeFig, 'userdata'); try cbHideStd = userdata.cbHideStdCbs.isSelected; catch, end %#ok try vsplitPaneLocation = getRalativeDivlocation(userdata.vsplitPane); hsplitPaneLocation = getRalativeDivlocation(userdata.hsplitPane); catch vsplitPaneLocation = 0.8; hsplitPaneLocation = 0.5; end % Clear the figure and redraw clf(hTreeFig); figure(hTreeFig); % bring to front end % Traverse all HG children, if root container was a HG handle if ishghandle(origContainer) %&& ~isequal(origContainer,container) traverseHGContainer(origContainer,0,0); end % Prepare the tree pane warning('off','MATLAB:uitreenode:MigratingFunction'); % R2008b compatibility warning('off','MATLAB:uitreenode:DeprecatedFunction'); % R2008a compatibility tree_h = com.mathworks.hg.peer.UITreePeer; hasChildren = sum(allParents==1) > 1; icon = [iconpath 'upfolder.gif']; [rootName, rootTitle] = getNodeName(container); try root = uitreenode('v0', handle(container), rootName, icon, ~hasChildren); catch % old matlab version don't have the 'v0' option root = uitreenode(handle(container), rootName, icon, ~hasChildren); end setTreeNodeIcon(root,container); % constructor must accept a char icon unfortunately, so need to do this afterwards... if ~isempty(rootTitle) set(hTreeFig, 'Name',['FindJObj - ' char(rootTitle)]); end nodedata.idx = 1; nodedata.obj = container; set(root,'userdata',nodedata); root.setUserObject(container); setappdata(root,'childHandle',container); tree_h.setRoot(root); treePane = tree_h.getScrollPane; treePane.setMinimumSize(Dimension(50,50)); jTreeObj = treePane.getViewport.getComponent(0); jTreeObj.setShowsRootHandles(true) jTreeObj.getSelectionModel.setSelectionMode(javax.swing.tree.TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); %jTreeObj.setVisible(0); %jTreeObj.getCellRenderer.setLeafIcon([]); %jTreeObj.getCellRenderer.setOpenIcon(figIcon); %jTreeObj.getCellRenderer.setClosedIcon([]); treePanel = JPanel(BorderLayout); treePanel.add(treePane, BorderLayout.CENTER); progressBar = JProgressBar(0); progressBar.setMaximum(length(allHandles) + length(hg_handles)); % = # of all nodes treePanel.add(progressBar, BorderLayout.SOUTH); % Prepare the image pane %disable for now, until we get it working... %{ try hFig = ancestor(origContainer,'figure'); [cdata, cm] = getframe(hFig); %#ok cm unused tempfname = [tempname '.png']; imwrite(cdata,tempfname); % don't know how to pass directly to BufferedImage, so use disk... jImg = javax.imageio.ImageIO.read(java.io.File(tempfname)); try delete(tempfname); catch end imgPanel = JPanel(); leftPanel = JSplitPane(JSplitPane.VERTICAL_SPLIT, treePanel, imgPanel); leftPanel.setOneTouchExpandable(true); leftPanel.setContinuousLayout(true); leftPanel.setResizeWeight(0.8); catch leftPanel = treePanel; end %} leftPanel = treePanel; % Prepare the inspector pane classNameLabel = JLabel([' ' char(container.class)]); classNameLabel.setForeground(Color.blue); updateNodeTooltip(container, classNameLabel); inspectorPanel = JPanel(BorderLayout); inspectorPanel.add(classNameLabel, BorderLayout.NORTH); % TODO: Maybe uncomment the following when we add the HG tree - in the meantime it's unused (java properties are un-groupable) %objReg = com.mathworks.services.ObjectRegistry.getLayoutRegistry; %toolBar = awtinvoke('com.mathworks.mlwidgets.inspector.PropertyView$ToolBarStyle','valueOf(Ljava.lang.String;)','GROUPTOOLBAR'); %inspectorPane = com.mathworks.mlwidgets.inspector.PropertyView(objReg, toolBar); inspectorPane = com.mathworks.mlwidgets.inspector.PropertyView; identifiers = disableDbstopError; %#ok "dbstop if error" causes inspect.m to croak due to a bug - so workaround inspectorPane.setObject(container); inspectorPane.setAutoUpdate(true); % TODO: Add property listeners % TODO: Display additional props inspectorTable = inspectorPane; try while ~isa(inspectorTable,'javax.swing.JTable') inspectorTable = inspectorTable.getComponent(0); end catch % R2010a inspectorTable = inspectorPane.getComponent(0).getScrollPane.getViewport.getComponent(0); end toolTipText = 'hover mouse over the red classname above to see the full list of properties'; inspectorTable.setToolTipText(toolTipText); jideTableUtils = []; try % Try JIDE features - see http://www.jidesoft.com/products/JIDE_Grids_Developer_Guide.pdf com.mathworks.mwswing.MJUtilities.initJIDE; jideTableUtils = eval('com.jidesoft.grid.TableUtils;'); % prevent JIDE alert by run-time (not load-time) evaluation jideTableUtils.autoResizeAllColumns(inspectorTable); inspectorTable.setRowAutoResizes(true); inspectorTable.getModel.setShowExpert(1); catch % JIDE is probably unavailable - never mind... end inspectorPanel.add(inspectorPane, BorderLayout.CENTER); % TODO: Add data update listeners % Prepare the callbacks pane callbacksPanel = JPanel(BorderLayout); classHdl = classhandle(handle(container)); eventNames = get(classHdl.Events,'Name'); if ~isempty(eventNames) cbNames = sort(strcat(eventNames,'Callback')); try cbData = [cbNames, get(container,cbNames)']; catch % R2010a cbData = cbNames; if isempty(cbData) cbData = {}; elseif ~iscell(cbData) cbData = {cbData}; end for idx = 1 : length(cbNames) cbData{idx,2} = get(container,cbNames{idx}); end end cbTableEnabled = true; else cbData = {'(no callbacks)',''}; cbTableEnabled = false; end cbHeaders = {'Callback name','Callback value'}; try % Use JideTable if available on this system %callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); %#ok %callbacksTable = eval('com.jidesoft.grid.PropertyTable(callbacksTableModel);'); % prevent JIDE alert by run-time (not load-time) evaluation callbacksTable = eval('com.jidesoft.grid.TreeTable(cbData,cbHeaders);'); % prevent JIDE alert by run-time (not load-time) evaluation callbacksTable.setRowAutoResizes(true); callbacksTable.setColumnAutoResizable(true); callbacksTable.setColumnResizable(true); jideTableUtils.autoResizeAllColumns(callbacksTable); callbacksTable.setTableHeader([]); % hide the column headers since now we can resize columns with the gridline callbacksLabel = JLabel(' Callbacks:'); % The column headers are replaced with a header label %callbacksPanel.add(callbacksLabel, BorderLayout.NORTH); % Add checkbox to show/hide standard callbacks callbacksTopPanel = JPanel; callbacksTopPanel.setLayout(BoxLayout(callbacksTopPanel, BoxLayout.LINE_AXIS)); callbacksTopPanel.add(callbacksLabel); callbacksTopPanel.add(Box.createHorizontalGlue); jcb = JCheckBox('Hide standard callbacks', cbHideStd); set(handle(jcb,'CallbackProperties'), 'ActionPerformedCallback',{@cbHideStdCbs_Callback,callbacksTable}); try set(jcb, 'userdata',callbacksTable, 'tooltip','Hide standard Swing callbacks - only component-specific callbacks will be displayed'); catch jcb.setToolTipText('Hide standard Swing callbacks - only component-specific callbacks will be displayed'); %setappdata(jcb,'userdata',callbacksTable); end callbacksTopPanel.add(jcb); callbacksPanel.add(callbacksTopPanel, BorderLayout.NORTH); catch % Otherwise, use a standard Swing JTable (keep the headers to enable resizing) callbacksTable = JTable(cbData,cbHeaders); end cbToolTipText = 'Callbacks may be ''strings'', @funcHandle or {@funcHandle,arg1,...}'; callbacksTable.setToolTipText(cbToolTipText); callbacksTable.setGridColor(inspectorTable.getGridColor); cbNameTextField = JTextField; cbNameTextField.setEditable(false); % ensure that the callback names are not modified... cbNameCellEditor = DefaultCellEditor(cbNameTextField); cbNameCellEditor.setClickCountToStart(intmax); % i.e, never enter edit mode... callbacksTable.getColumnModel.getColumn(0).setCellEditor(cbNameCellEditor); if ~cbTableEnabled callbacksTable.getColumnModel.getColumn(1).setCellEditor(cbNameCellEditor); end hModel = callbacksTable.getModel; set(handle(hModel,'CallbackProperties'), 'TableChangedCallback',{@tbCallbacksChanged,container}); %set(hModel, 'UserData',container); cbScrollPane = JScrollPane(callbacksTable); cbScrollPane.setVerticalScrollBarPolicy(cbScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); callbacksPanel.add(cbScrollPane, BorderLayout.CENTER); callbacksPanel.setToolTipText(cbToolTipText); % Prepare the top-bottom JSplitPanes vsplitPane = JSplitPane(JSplitPane.VERTICAL_SPLIT, inspectorPanel, callbacksPanel); vsplitPane.setOneTouchExpandable(true); vsplitPane.setContinuousLayout(true); vsplitPane.setResizeWeight(0.8); % Prepare the left-right JSplitPane hsplitPane = JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, vsplitPane); hsplitPane.setOneTouchExpandable(true); hsplitPane.setContinuousLayout(true); hsplitPane.setResizeWeight(0.6); pos = getpixelposition(hTreeFig); % Prepare the bottom pane with all buttons lowerPanel = JPanel(FlowLayout); blogUrlLabel = '<a href="http://UndocumentedMatlab.com">Undocumented<br>Matlab.com</a>'; jWebsite = createJButton(blogUrlLabel, @btWebsite_Callback, 'Visit the UndocumentedMatlab.com blog'); jWebsite.setContentAreaFilled(0); lowerPanel.add(jWebsite); lowerPanel.add(createJButton('Refresh<br>tree', {@btRefresh_Callback, origContainer, hTreeFig}, 'Rescan the component tree, from the root down')); lowerPanel.add(createJButton('Export to<br>workspace', {@btExport_Callback, jTreeObj, classNameLabel}, 'Export the selected component handles to workspace variable findjobj_hdls')); lowerPanel.add(createJButton('Request<br>focus', {@btFocus_Callback, jTreeObj, root}, 'Set the focus on the first selected component')); lowerPanel.add(createJButton('Inspect<br>object', {@btInspect_Callback, jTreeObj, root}, 'View the signature of all methods supported by the first selected component')); lowerPanel.add(createJButton('Check for<br>updates', {@btCheckFex_Callback}, 'Check the MathWorks FileExchange for the latest version of FindJObj')); % Display everything on-screen globalPanel = JPanel(BorderLayout); globalPanel.add(hsplitPane, BorderLayout.CENTER); globalPanel.add(lowerPanel, BorderLayout.SOUTH); [obj, hcontainer] = javacomponent(globalPanel, [0,0,pos(3:4)], hTreeFig); set(hcontainer,'units','normalized'); drawnow; hsplitPane.setDividerLocation(hsplitPaneLocation); % this only works after the JSplitPane is displayed... vsplitPane.setDividerLocation(vsplitPaneLocation); % this only works after the JSplitPane is displayed... %restoreDbstopError(identifiers); % Refresh & resize the screenshot thumbnail %disable for now, until we get it working... %{ try hAx = axes('Parent',hTreeFig, 'units','pixels', 'position',[10,10,250,150], 'visible','off'); axis(hAx,'image'); image(cdata,'Parent',hAx); axis(hAx,'off'); set(hAx,'UserData',cdata); set(imgPanel, 'ComponentResizedCallback',{@resizeImg, hAx}, 'UserData',lowerPanel); imgPanel.getGraphics.drawImage(jImg, 0, 0, []); catch % Never mind... end %} % If all handles were selected (i.e., none were filtered) then only select the first if (length(selectedIdx) == length(allHandles)) && ~isempty(selectedIdx) selectedIdx = 1; end % Store handles for callback use userdata.handles = allHandles; userdata.levels = allLevels; userdata.parents = allParents; userdata.hg_handles = hg_handles; userdata.hg_levels = hg_levels; userdata.hg_parents = hg_parentIdx; userdata.initialIdx = selectedIdx; userdata.userSelected = false; % Indicates the user has modified the initial selections userdata.inInit = true; userdata.jTree = jTreeObj; userdata.jTreePeer = tree_h; userdata.vsplitPane = vsplitPane; userdata.hsplitPane = hsplitPane; userdata.classNameLabel = classNameLabel; userdata.inspectorPane = inspectorPane; userdata.callbacksTable = callbacksTable; userdata.jideTableUtils = jideTableUtils; try userdata.cbHideStdCbs = jcb; catch userdata.cbHideStdCbs = []; end % Update userdata for use in callbacks try set(tree_h,'userdata',userdata); catch setappdata(tree_h,'userdata',userdata); end try set(callbacksTable,'userdata',userdata); catch setappdata(callbacksTable,'userdata',userdata); end set(hTreeFig,'userdata',userdata); % Select the root node if requested % Note: we must do so here since all other nodes except the root are processed by expandNode if any(selectedIdx==1) tree_h.setSelectedNode(root); end % Set the initial cbHideStdCbs state try if jcb.isSelected drawnow; evd.getSource.isSelected = jcb.isSelected; cbHideStdCbs_Callback(jcb,evd,callbacksTable); end catch % never mind... end % Set the callback functions tree_hh = handle(tree_h,'CallbackProperties'); set(tree_hh, 'NodeExpandedCallback', {@nodeExpanded, tree_h}); set(tree_hh, 'NodeSelectedCallback', {@nodeSelected, tree_h}); % Set the tree mouse-click callback % Note: default actions (expand/collapse) will still be performed? % Note: MousePressedCallback is better than MouseClickedCallback % since it fires immediately when mouse button is pressed, % without waiting for its release, as MouseClickedCallback does handleTree = tree_h.getScrollPane; jTreeObj = handleTree.getViewport.getComponent(0); jTreeObjh = handle(jTreeObj,'CallbackProperties'); set(jTreeObjh, 'MousePressedCallback', {@treeMousePressedCallback,tree_h}); % context (right-click) menu set(jTreeObjh, 'MouseMovedCallback', @treeMouseMovedCallback); % mouse hover tooltips % Pre-expand all rows expandNode(progressBar, jTreeObj, tree_h, root, 0); %jTreeObj.setVisible(1); % Hide the progressbar now that we've finished expanding all rows try hsplitPane.getLeftComponent.setTopComponent(treePane); catch % Probably not a vSplitPane on the left... hsplitPane.setLeftComponent(treePane); end hsplitPane.setDividerLocation(hsplitPaneLocation); % need to do it again... % Update userdata userdata.inInit = false; try set(tree_h,'userdata',userdata); catch setappdata(tree_h,'userdata',userdata); end set(hTreeFig,'userdata',userdata); % Set keyboard focus on the tree jTreeObj.requestFocus; drawnow; % Check for a newer version checkVersion(); % Reset the last error lasterr(''); %#ok end
Rresize image pane
function resizeImg(varargin) %#ok - unused (TODO: waiting for img placement fix...) try hPanel = varargin{1}; hAx = varargin{3}; lowerPanel = get(hPanel,'UserData'); newJPos = cell2mat(get(hPanel,{'X','Y','Width','Height'})); newMPos = [1,get(lowerPanel,'Height'),newJPos(3:4)]; set(hAx, 'units','pixels', 'position',newMPos, 'Visible','on'); uistack(hAx,'top'); % no good... set(hPanel,'Opaque','off'); % also no good... catch % Never mind... dispError end return; end
"dbstop if error" causes inspect.m to croak due to a bug - so workaround by temporarily disabling this dbstop
function identifiers = disableDbstopError dbStat = dbstatus; idx = find(strcmp({dbStat.cond},'error')); identifiers = [dbStat(idx).identifier]; if ~isempty(idx) dbclear if error; msgbox('''dbstop if error'' had to be disabled due to a Matlab bug that would have caused Matlab to crash.', 'FindJObj', 'warn'); end end
Restore any previous "dbstop if error"
function restoreDbstopError(identifiers) %#ok for itemIdx = 1 : length(identifiers) eval(['dbstop if error ' identifiers{itemIdx}]); end end
Recursively expand all nodes (except toolbar/menubar) in startup
function expandNode(progressBar, tree, tree_h, parentNode, parentRow) try if nargin < 5 parentPath = javax.swing.tree.TreePath(parentNode.getPath); parentRow = tree.getRowForPath(parentPath); end tree.expandRow(parentRow); progressBar.setValue(progressBar.getValue+1); numChildren = parentNode.getChildCount; if (numChildren == 0) pause(0.0002); % as short as possible... drawnow; end nodesToUnExpand = {'FigureMenuBar','MLMenuBar','MJToolBar','Box','uimenu','uitoolbar','ScrollBar'}; numChildren = parentNode.getChildCount; for childIdx = 0 : numChildren-1 childNode = parentNode.getChildAt(childIdx); % Pre-select the node based upon the user's FINDJOBJ filters try nodedata = get(childNode, 'userdata'); try userdata = get(tree_h, 'userdata'); catch userdata = getappdata(tree_h, 'userdata'); end %fprintf('%d - %s\n',nodedata.idx,char(nodedata.obj)) if ~ishghandle(nodedata.obj) && ~userdata.userSelected && any(userdata.initialIdx == nodedata.idx) pause(0.0002); % as short as possible... drawnow; if isempty(tree_h.getSelectedNodes) tree_h.setSelectedNode(childNode); else newSelectedNodes = [tree_h.getSelectedNodes, childNode]; tree_h.setSelectedNodes(newSelectedNodes); end end catch % never mind... dispError end % Expand child node if not leaf & not toolbar/menubar if childNode.isLeafNode % This is a leaf node, so simply update the progress-bar progressBar.setValue(progressBar.getValue+1); else % Expand all non-leaves expandNode(progressBar, tree, tree_h, childNode); % Re-collapse toolbar/menubar etc., and also invisible containers % Note: if we simply did nothing, progressbar would not have been updated... try childHandle = getappdata(childNode,'childHandle'); %=childNode.getUserObject visible = childHandle.isVisible; catch visible = 1; end visible = visible && isempty(findstr(get(childNode,'Name'),'color="gray"')); %if any(strcmp(childNode.getName,nodesToUnExpand)) %name = char(childNode.getName); if any(cellfun(@(s)~isempty(strmatch(s,char(childNode.getName))),nodesToUnExpand)) || ~visible childPath = javax.swing.tree.TreePath(childNode.getPath); childRow = tree.getRowForPath(childPath); tree.collapseRow(childRow); end end end catch % never mind... dispError end end
Create utility buttons
function hButton = createJButton(nameStr, handler, toolTipText) try jButton = javax.swing.JButton(['<html><body><center>' nameStr]); jButton.setCursor(java.awt.Cursor.getPredefinedCursor(java.awt.Cursor.HAND_CURSOR)); jButton.setToolTipText(toolTipText); minSize = jButton.getMinimumSize; jButton.setMinimumSize(java.awt.Dimension(minSize.getWidth,35)); hButton = handle(jButton,'CallbackProperties'); set(hButton,'ActionPerformedCallback',handler); catch % Never mind... end end
Flash a component off/on for the specified duration
note: starts with 'on'; if numTimes is odd then ends with 'on', otherwise with 'off'
function flashComponent(jComps,delaySecs,numTimes) persistent redBorder redBorderPanels try % Handle callback data from right-click (context-menu) if iscell(numTimes) [jComps,delaySecs,numTimes] = deal(numTimes{:}); end if isempty(redBorder) % reuse if possible redBorder = javax.swing.border.LineBorder(java.awt.Color.red,2,0); end for compIdx = 1 : length(jComps) try oldBorder{compIdx} = jComps(compIdx).getBorder; %#ok grow catch oldBorder{compIdx} = []; %#ok grow end isSettable(compIdx) = ismethod(jComps(compIdx),'setBorder'); %#ok grow if isSettable(compIdx) try % some components prevent border modification: oldBorderFlag = jComps(compIdx).isBorderPainted; if ~oldBorderFlag jComps(compIdx).setBorderPainted(1); isSettable(compIdx) = jComps(compIdx).isBorderPainted; %#ok grow jComps(compIdx).setBorderPainted(oldBorderFlag); end catch % do nothing... end end if compIdx > length(redBorderPanels) redBorderPanels{compIdx} = javax.swing.JPanel; redBorderPanels{compIdx}.setBorder(redBorder); redBorderPanels{compIdx}.setOpaque(0); % transparent interior, red border end try redBorderPanels{compIdx}.setBounds(jComps(compIdx).getBounds); catch % never mind - might be an HG handle end end for idx = 1 : 2*numTimes if idx>1, pause(delaySecs); end % don't pause at start visible = mod(idx,2); for compIdx = 1 : length(jComps) try jComp = jComps(compIdx); % Prevent Matlab crash (java buffer overflow...) if jComp.isa('com.mathworks.mwswing.desk.DTSplitPane') || ... jComp.isa('com.mathworks.mwswing.MJSplitPane') continue; end % HG handles are highlighted by setting their 'Selected' property if isa(jComp,'uimenu') if visible oldColor = get(jComp,'ForegroundColor'); setappdata(jComp,'findjobj_oldColor',oldColor); set(jComp,'ForegroundColor','red'); else oldColor = getappdata(jComp,'findjobj_oldColor'); set(jComp,'ForegroundColor',oldColor); rmappdata(jComp,'ForegroundColor'); end elseif ishghandle(jComp) if visible set(jComp,'Selected','on'); else set(jComp,'Selected','off'); end else %if isjava(jComp) jParent = jComps(compIdx).getParent; % Most Java components allow modifying their borders if isSettable(compIdx) if visible jComp.setBorder(redBorder); try jComp.setBorderPainted(1); catch, end %#ok else %if ~isempty(oldBorder{compIdx}) jComp.setBorder(oldBorder{compIdx}); end jComp.repaint; % The other Java components are highlighted by a transparent red-border % panel that is placed on top of them in their parent's space elseif ~isempty(jParent) if visible jParent.add(redBorderPanels{compIdx}); jParent.setComponentZOrder(redBorderPanels{compIdx},0); else jParent.remove(redBorderPanels{compIdx}); end jParent.repaint end end catch % never mind - try the next component (if any) end end drawnow; end catch % never mind... dispError; end return; % debug point end % flashComponent
Select tree node
function nodeSelected(src, evd, tree) %#ok try if iscell(tree) [src,node] = deal(tree{:}); else node = evd.getCurrentNode; end %nodeHandle = node.getUserObject; nodedata = get(node,'userdata'); nodeHandle = nodedata.obj; try userdata = get(src,'userdata'); catch userdata = getappdata(src.java,'userdata'); end if ~isempty(nodeHandle) && ~isempty(userdata) numSelections = userdata.jTree.getSelectionCount; selectionPaths = userdata.jTree.getSelectionPaths; if (numSelections == 1) % Indicate that the user has modified the initial selection (except if this was an initial auto-selected node) if ~userdata.inInit userdata.userSelected = true; try set(src,'userdata',userdata); catch setappdata(src.java,'userdata',userdata); end end % Update the fully-qualified class name label numInitialIdx = length(userdata.initialIdx); thisHandle = nodeHandle; try if ~ishghandle(thisHandle) thisHandle = java(nodeHandle); end catch % never mind... end if ~userdata.inInit || (numInitialIdx == 1) userdata.classNameLabel.setText([' ' char(thisHandle.class)]); else userdata.classNameLabel.setText([' ' num2str(numInitialIdx) 'x handles (some handles hidden by unexpanded tree nodes)']); end if ishghandle(thisHandle) userdata.classNameLabel.setText(userdata.classNameLabel.getText.concat(' (HG handle)')); end userdata.inspectorPane.dispose; % remove props listeners - doesn't work... updateNodeTooltip(nodeHandle, userdata.classNameLabel); % Update the data properties inspector pane % Note: we can't simply use the evd nodeHandle, because this node might have been DE-selected with only one other node left selected... %nodeHandle = selectionPaths(1).getLastPathComponent.getUserObject; nodedata = get(selectionPaths(1).getLastPathComponent,'userdata'); nodeHandle = nodedata.obj; %identifiers = disableDbstopError; % "dbstop if error" causes inspect.m to croak due to a bug - so workaround userdata.inspectorPane.setObject(thisHandle); % Update the callbacks table try stripStdCbsFlag = getappdata(userdata.callbacksTable,'hideStdCbs'); [cbData, cbHeaders, cbTableEnabled] = getCbsData(nodeHandle, stripStdCbsFlag); %#ok cbTableEnabled unused callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); set(handle(callbacksTableModel,'CallbackProperties'), 'TableChangedCallback',{@tbCallbacksChanged,nodeHandle}); %set(callbacksTableModel, 'UserData',nodeHandle); userdata.callbacksTable.setModel(callbacksTableModel) userdata.callbacksTable.setRowAutoResizes(true); userdata.jideTableUtils.autoResizeAllColumns(userdata.callbacksTable); catch % never mind... %dispError end pause(0.005); drawnow; %restoreDbstopError(identifiers); % Highlight the selected object (if visible) flashComponent(nodeHandle,0.2,3); elseif (numSelections > 1) % Multiple selections % Get the list of all selected nodes jArray = javaArray('java.lang.Object', numSelections); toolTipStr = '<html>'; sameClassFlag = true; for idx = 1 : numSelections %jArray(idx) = selectionPaths(idx).getLastPathComponent.getUserObject; nodedata = get(selectionPaths(idx).getLastPathComponent,'userdata'); try jArray(idx) = java(nodedata.obj); catch jArray(idx) = nodedata.obj; end toolTipStr = [toolTipStr ' ' jArray(idx).class ' ']; %#ok grow if (idx < numSelections), toolTipStr = [toolTipStr '<br>']; end %#ok grow if (idx > 1) && sameClassFlag && ~isequal(jArray(idx).getClass,jArray(1).getClass) sameClassFlag = false; end end toolTipStr = [toolTipStr '</html>']; % Update the fully-qualified class name label if sameClassFlag classNameStr = jArray(1).class; else classNameStr = 'handle'; end if all(ishghandle(jArray)) if strcmp(classNameStr,'handle') classNameStr = 'HG handles'; else classNameStr = [classNameStr ' (HG handles)']; end end classNameStr = [' ' num2str(numSelections) 'x ' classNameStr]; userdata.classNameLabel.setText(classNameStr); userdata.classNameLabel.setToolTipText(toolTipStr); % Update the data properties inspector pane %identifiers = disableDbstopError; % "dbstop if error" causes inspect.m to croak due to a bug - so workaround userdata.inspectorPane.getRegistry.setSelected(jArray, true); % Update the callbacks table try % Get intersecting callback names & values stripStdCbsFlag = getappdata(userdata.callbacksTable,'hideStdCbs'); [cbData, cbHeaders, cbTableEnabled] = getCbsData(jArray(1), stripStdCbsFlag); %#ok cbHeaders & cbTableEnabled unused if ~isempty(cbData) cbNames = cbData(:,1); for idx = 2 : length(jArray) [cbData2, cbHeaders2] = getCbsData(jArray(idx), stripStdCbsFlag); %#ok cbHeaders2 unused if ~isempty(cbData2) newCbNames = cbData2(:,1); [cbNames, cbIdx, cb2Idx] = intersect(cbNames,newCbNames); %#ok cb2Idx unused cbData = cbData(cbIdx,:); for cbIdx = 1 : length(cbNames) newIdx = find(strcmp(cbNames{cbIdx},newCbNames)); if ~isequal(cbData2{newIdx,2}, cbData{cbIdx,2}) cbData{cbIdx,2} = '<different values>'; end end else cbData = cbData([],:); %=empty cell array end if isempty(cbData) break; end end end cbHeaders = {'Callback name','Callback value'}; callbacksTableModel = javax.swing.table.DefaultTableModel(cbData,cbHeaders); set(handle(callbacksTableModel,'CallbackProperties'), 'TableChangedCallback',{@tbCallbacksChanged,jArray}); %set(callbacksTableModel, 'UserData',jArray); userdata.callbacksTable.setModel(callbacksTableModel) userdata.callbacksTable.setRowAutoResizes(true); userdata.jideTableUtils.autoResizeAllColumns(userdata.callbacksTable); catch % never mind... dispError end pause(0.005); drawnow; %restoreDbstopError(identifiers); % Highlight the selected objects (if visible) flashComponent(jArray,0.2,3); end % TODO: Auto-highlight selected object (?) %nodeHandle.requestFocus; end catch dispError end end
IFF utility function for annonymous cellfun funcs
function result = iff(test,trueVal,falseVal) %#ok try if test result = trueVal; else result = falseVal; end catch result = false; end end
Get an HTML representation of the object's properties
function dataFieldsStr = getPropsHtml(nodeHandle, dataFields) try % Get a text representation of the fieldnames & values dataFieldsStr = ''; % just in case the following croaks... if isempty(dataFields) return; end dataFieldsStr = evalc('disp(dataFields)'); if dataFieldsStr(end)==char(10), dataFieldsStr=dataFieldsStr(1:end-1); end % Strip out callbacks dataFieldsStr = regexprep(dataFieldsStr,'^\s*\w*Callback(Data)?:[^\n]*$','','lineanchors'); dataFieldsStr = regexprep(dataFieldsStr,'\n\n','\n'); % HTMLize tooltip data % First, set the fields' font based on its read-write status nodeHandle = handle(nodeHandle); % ensure this is a Matlab handle, not a java object fieldNames = fieldnames(dataFields); undefinedStr = ''; for fieldIdx = 1 : length(fieldNames) thisFieldName = fieldNames{fieldIdx}; accessFlags = get(findprop(nodeHandle,thisFieldName),'AccessFlags'); if isfield(accessFlags,'PublicSet') && strcmp(accessFlags.PublicSet,'on') % Bolden read/write fields thisFieldFormat = ['<b>' thisFieldName '<b>:$2']; elseif ~isfield(accessFlags,'PublicSet') % Undefined - probably a Matlab-defined field of com.mathworks.hg.peer.FigureFrameProxy... thisFieldFormat = ['<font color="blue">' thisFieldName '</font>:$2']; undefinedStr = ', <font color="blue">undefined</font>'; else % PublicSet=='off' % Gray-out & italicize any read-only fields thisFieldFormat = ['<font color="#C0C0C0"><i>' thisFieldName '</i></font>:<font color="#C0C0C0"><i>$2<i></font>']; end dataFieldsStr = regexprep(dataFieldsStr, ['([\s\n])' thisFieldName ':([^\n]*)'], ['$1' thisFieldFormat]); end catch % never mind... - probably an ambiguous property name %dispError end % Method 1: simple <br> list %dataFieldsStr = strrep(dataFieldsStr,char(10),' <br> '); % Method 2: 2x2-column <table> dataFieldsStr = regexprep(dataFieldsStr, '^\s*([^:]+:)([^\n]*)\n^\s*([^:]+:)([^\n]*)$', '<tr><td> $1</td><td> $2</td><td> $3</td><td> $4 </td></tr>', 'lineanchors'); dataFieldsStr = regexprep(dataFieldsStr, '^[^<]\s*([^:]+:)([^\n]*)$', '<tr><td> $1</td><td> $2</td><td> </td><td> </td></tr>', 'lineanchors'); dataFieldsStr = ['(<b>modifiable</b>' undefinedStr ' & <font color="#C0C0C0"><i>read-only</i></font> fields)<p> <table cellpadding="0" cellspacing="0">' dataFieldsStr '</table>']; end
Update tooltip string with a node's data
function updateNodeTooltip(nodeHandle, uiObject) try toolTipStr = nodeHandle.class; dataFieldsStr = ''; % Add HG annotation if relevant if ishghandle(nodeHandle) hgStr = ' HG Handle'; else hgStr = ''; end % Note: don't bulk-get because (1) not all properties are returned & (2) some properties cause a Java exception % Note2: the classhandle approach does not enable access to user-defined schema.props ch = classhandle(handle(nodeHandle)); dataFields = []; [sortedNames, sortedIdx] = sort(get(ch.Properties,'Name')); for idx = 1 : length(sortedIdx) sp = ch.Properties(sortedIdx(idx)); % TODO: some fields (see EOL comment below) generate a Java Exception from: com.mathworks.mlwidgets.inspector.PropertyRootNode$PropertyListener$1$1.run if strcmp(sp.AccessFlags.PublicGet,'on') % && ~any(strcmp(sp.Name,{'FixedColors','ListboxTop','Extent'})) try dataFields.(sp.Name) = get(nodeHandle, sp.Name); catch dataFields.(sp.Name) = '<font color="red">Error!</font>'; end else dataFields.(sp.Name) = '(no public getter method)'; end end dataFieldsStr = getPropsHtml(nodeHandle, dataFields); catch % Probably a non-HG java object try % Note: the bulk-get approach enables access to user-defined schema-props, but not to some original classhandle Properties... dataFields = get(nodeHandle); dataFieldsStr = getPropsHtml(nodeHandle, dataFields); catch % Probably a missing property getter implementation try % Inform the user - bail out on error err = lasterror; %#ok dataFieldsStr = ['<p>' strrep(err.message, char(10), '<br>')]; catch % forget it... end end end % Set the object tooltip if ~isempty(dataFieldsStr) toolTipStr = ['<html> <b><font color="blue">' char(toolTipStr) '</font></b>' hgStr ': ' dataFieldsStr '</html>']; end uiObject.setToolTipText(toolTipStr); end
Expand tree node
function nodeExpanded(src, evd, tree) %#ok % tree = handle(src); % evdsrc = evd.getSource; evdnode = evd.getCurrentNode; if ~tree.isLoaded(evdnode) % Get the list of children TreeNodes childnodes = getChildrenNodes(tree, evdnode); % Add the HG sub-tree (unless already included in the first tree) childHandle = getappdata(evdnode.handle,'childHandle'); %=evdnode.getUserObject if evdnode.isRoot && ~isempty(hg_handles) && ~isequal(hg_handles(1).java, childHandle) childnodes = [childnodes, getChildrenNodes(tree, evdnode, true)]; end % If we have a single child handle, wrap it within a javaArray for tree.add() to "swallow" if (length(childnodes) == 1) chnodes = childnodes; childnodes = javaArray('com.mathworks.hg.peer.UITreeNode', 1); childnodes(1) = java(chnodes); end % Add child nodes to the current node tree.add(evdnode, childnodes); tree.setLoaded(evdnode, true); end end
Get an icon image no larger than 16x16 pixels
function iconImage = setIconSize(iconImage) try iconWidth = iconImage.getWidth; iconHeight = iconImage.getHeight; if iconWidth > 16 newHeight = fix(iconHeight * 16 / iconWidth); iconImage = iconImage.getScaledInstance(16,newHeight,iconImage.SCALE_SMOOTH); elseif iconHeight > 16 newWidth = fix(iconWidth * 16 / iconHeight); iconImage = iconImage.getScaledInstance(newWidth,16,iconImage.SCALE_SMOOTH); end catch % never mind... - return original icon end end % setIconSize
Get list of children nodes
function nodes = getChildrenNodes(tree, parentNode, isRootHGNode) try iconpath = [matlabroot, '/toolbox/matlab/icons/']; nodes = handle([]); try userdata = get(tree,'userdata'); catch userdata = getappdata(tree,'userdata'); end hdls = userdata.handles; nodedata = get(parentNode,'userdata'); if nargin < 3 %isJavaNode = ~ishghandle(parentNode.getUserObject); isJavaNode = ~ishghandle(nodedata.obj); isRootHGNode = false; else isJavaNode = ~isRootHGNode; end % Search for this parent node in the list of all nodes parents = userdata.parents; nodeIdx = nodedata.idx; if isJavaNode && isempty(nodeIdx) % Failback, in case userdata doesn't work for some reason... for hIdx = 1 : length(hdls) %if isequal(handle(parentNode.getUserObject), hdls(hIdx)) if isequal(handle(nodedata.obj), hdls(hIdx)) nodeIdx = hIdx; break; end end end if ~isJavaNode if isRootHGNode % =root HG node thisChildHandle = userdata.hg_handles(1); childName = getNodeName(thisChildHandle); hasGrandChildren = any(parents==1); icon = []; if hasGrandChildren && length(hg_handles)>1 childName = childName.concat(' - HG root container'); icon = [iconpath 'figureicon.gif']; end try nodes = uitreenode('v0', thisChildHandle, childName, icon, ~hasGrandChildren); catch % old matlab version don't have the 'v0' option try nodes = uitreenode(thisChildHandle, childName, icon, ~hasGrandChildren); catch % probably an invalid handle - ignore... end end % Add the handler to the node's internal data % Note: could also use 'userdata', but setUserObject() is recommended for TreeNodes % Note2: however, setUserObject() sets a java *BeenAdapter object for HG handles instead of the required original class, so use setappdata %nodes.setUserObject(thisChildHandle); setappdata(nodes,'childHandle',thisChildHandle); nodedata.idx = 1; nodedata.obj = thisChildHandle; set(nodes,'userdata',nodedata); return; else % non-root HG node parents = userdata.hg_parents; hdls = userdata.hg_handles; end % if isRootHGNode end % if ~isJavaNode % If this node was found, get the list of its children if ~isempty(nodeIdx) %childIdx = setdiff(find(parents==nodeIdx),nodeIdx); childIdx = find(parents==nodeIdx); childIdx(childIdx==nodeIdx) = []; % faster... numChildren = length(childIdx); for cIdx = 1 : numChildren thisChildIdx = childIdx(cIdx); thisChildHandle = hdls(thisChildIdx); childName = getNodeName(thisChildHandle); try visible = thisChildHandle.Visible; if visible try visible = thisChildHandle.Width > 0; catch, end %#ok end if ~visible childName = ['<HTML><i><font color="gray">' char(childName) '</font></i></html>']; %#ok grow end catch % never mind... end hasGrandChildren = any(parents==thisChildIdx); try isaLabel = isa(thisChildHandle.java,'javax.swing.JLabel'); catch isaLabel = 0; end if hasGrandChildren && ~any(strcmp(thisChildHandle.class,{'axes'})) icon = [iconpath 'foldericon.gif']; elseif isaLabel icon = [iconpath 'tool_text.gif']; else icon = []; end try nodes(cIdx) = uitreenode('v0', thisChildHandle, childName, icon, ~hasGrandChildren); catch % old matlab version don't have the 'v0' option try nodes(cIdx) = uitreenode(thisChildHandle, childName, icon, ~hasGrandChildren); catch % probably an invalid handle - ignore... end end % Use existing object icon, if available try setTreeNodeIcon(nodes(cIdx),thisChildHandle); catch % probably an invalid handle - ignore... end % Pre-select the node based upon the user's FINDJOBJ filters try if isJavaNode && ~userdata.userSelected && any(userdata.initialIdx == thisChildIdx) pause(0.0002); % as short as possible... drawnow; if isempty(tree.getSelectedNodes) tree.setSelectedNode(nodes(cIdx)); else newSelectedNodes = [tree.getSelectedNodes, nodes(cIdx).java]; tree.setSelectedNodes(newSelectedNodes); end end catch % never mind... end % Add the handler to the node's internal data % Note: could also use 'userdata', but setUserObject() is recommended for TreeNodes % Note2: however, setUserObject() sets a java *BeenAdapter object for HG handles instead of the required original class, so use setappdata % Note3: the following will error if invalid handle - ignore try if isJavaNode thisChildHandle = thisChildHandle.java; end %nodes(cIdx).setUserObject(thisChildHandle); setappdata(nodes(cIdx),'childHandle',thisChildHandle); nodedata.idx = thisChildIdx; nodedata.obj = thisChildHandle; set(nodes(cIdx),'userdata',nodedata); catch % never mind (probably an invalid handle) - leave unchanged (like a leaf) end end end catch % Never mind - leave unchanged (like a leaf) %error('YMA:findjobj:UnknownNodeType', 'Error expanding component tree node'); dispError end end
Get a node's name
function [nodeName, nodeTitle] = getNodeName(hndl,charsLimit) try % Initialize (just in case one of the succeding lines croaks) nodeName = ''; nodeTitle = ''; if ~ismethod(hndl,'getClass') try nodeName = hndl.class; catch nodeName = hndl.type; % last-ditch try... end else nodeName = hndl.getClass.getSimpleName; end % Strip away the package name, leaving only the regular classname if ~isempty(nodeName) && ischar(nodeName) nodeName = java.lang.String(nodeName); nodeName = nodeName.substring(nodeName.lastIndexOf('.')+1); end if (nodeName.length == 0) % fix case of anonymous internal classes, that do not have SimpleNames try nodeName = hndl.getClass.getName; nodeName = nodeName.substring(nodeName.lastIndexOf('.')+1); catch % never mind - leave unchanged... end end % Get any unique identifying string (if available in one of several fields) labelsToCheck = {'label','title','text','string','displayname','toolTipText','TooltipString','actionCommand','name','Tag','style'}; %,'UIClassID'}; nodeTitle = ''; strField = ''; %#ok - used for debugging while ((~isa(nodeTitle,'java.lang.String') && ~ischar(nodeTitle)) || isempty(nodeTitle)) && ~isempty(labelsToCheck) try nodeTitle = get(hndl,labelsToCheck{1}); strField = labelsToCheck{1}; %#ok - used for debugging catch % never mind - probably missing prop, so skip to next one end labelsToCheck(1) = []; end if length(nodeTitle) ~= numel(nodeTitle) % Multi-line - convert to a long single line nodeTitle = nodeTitle'; nodeTitle = nodeTitle(:)'; end if isempty(char(nodeTitle)) % No title - check whether this is an HG label whose text is gettable try location = hndl.getLocationOnScreen; pos = [location.getX, location.getY, hndl.getWidth, hndl.getHeight]; dist = sum((labelPositions-repmat(pos,size(labelPositions,1),[1,1,1,1])).^2, 2); [minVal,minIdx] = min(dist); % Allow max distance of 8 = 2^2+2^2 (i.e. X&Y off by up to 2 pixels, W&H exact) if minVal <= 8 % 8=2^2+2^2 nodeTitle = get(hg_labels(minIdx),'string'); % Preserve the label handles & position for the tooltip & context-menu %hg_labels(minIdx) = []; %labelPositions(minIdx,:) = []; end catch % never mind... end end if nargin<2, charsLimit = 25; end extraStr = regexprep(nodeTitle,{sprintf('(.{%d,%d}).*',charsLimit,min(charsLimit,length(nodeTitle)-1)),' +'},{'$1...',' '},'once'); if ~isempty(extraStr) if ischar(extraStr) nodeName = nodeName.concat(' (''').concat(extraStr).concat(''')'); else nodeName = nodeName.concat(' (').concat(num2str(extraStr)).concat(')'); end %nodeName = nodeName.concat(strField); end catch % Never mind - use whatever we have so far %dispError end end
Strip standard Swing callbacks from a list of events
function evNames = stripStdCbs(evNames) try stdEvents = {'AncestorAdded', 'AncestorMoved', 'AncestorRemoved', 'AncestorResized', ... 'CaretPositionChanged', 'ComponentAdded', 'ComponentRemoved', ... 'ComponentHidden', 'ComponentMoved', 'ComponentResized', 'ComponentShown', ... 'PropertyChange', 'FocusGained', 'FocusLost', ... 'HierarchyChanged', 'InputMethodTextChanged', ... 'KeyPressed', 'KeyReleased', 'KeyTyped', ... 'MouseClicked', 'MouseDragged', 'MouseEntered', 'MouseExited', ... 'MouseMoved', 'MousePressed', 'MouseReleased', 'MouseWheelMoved', ... 'VetoableChange'}; stdEvents = [stdEvents, strcat(stdEvents,'Callback'), strcat(stdEvents,'Fcn')]; evNames = setdiff(evNames,stdEvents)'; catch % Never mind... dispError end end
Callback function for standard callbacks checkbox
function cbHideStdCbs_Callback(src, evd, callbacksTable, varargin) %#ok try % Store the current checkbox value for later use if nargin < 3 try callbacksTable = get(src,'userdata'); catch callbacksTable = getappdata(src,'userdata'); end end if evd.getSource.isSelected setappdata(callbacksTable,'hideStdCbs',1); else setappdata(callbacksTable,'hideStdCbs',[]); end % Rescan the current node try userdata = get(callbacksTable,'userdata'); catch userdata = getappdata(callbacksTable,'userdata'); end nodepath = userdata.jTree.getSelectionModel.getSelectionPath; try ed.getCurrentNode = nodepath.getLastPathComponent; nodeSelected(handle(userdata.jTreePeer),ed,[]); catch % ignore - probably no node selected end catch % Never mind... dispError end end
Callback function for UndocumentedMatlab.com button
function btWebsite_Callback(src, evd, varargin) %#ok try web('http://UndocumentedMatlab.com/'); catch % Never mind... dispError end end
Callback function for data button
function btRefresh_Callback(src, evd, varargin) %#ok try % Set cursor shape to hourglass until we're done hTreeFig = varargin{2}; set(hTreeFig,'Pointer','watch'); drawnow; object = varargin{1}; % Re-invoke this utility to re-scan the container for all children findjobj(object); catch % Never mind... end % Restore default cursor shape set(hTreeFig,'Pointer','arrow'); end
Callback function for Export button
function btExport_Callback(src, evd, varargin) %#ok try % Get the list of all selected nodes if length(varargin) > 1 jTree = varargin{1}; numSelections = jTree.getSelectionCount; selectionPaths = jTree.getSelectionPaths; hdls = handle([]); for idx = 1 : numSelections %hdls(idx) = handle(selectionPaths(idx).getLastPathComponent.getUserObject); nodedata = get(selectionPaths(idx).getLastPathComponent,'userdata'); hdls(idx) = handle(nodedata.obj,'CallbackProperties'); end % Assign the handles in the base workspace & inform user assignin('base','findjobj_hdls',hdls); classNameLabel = varargin{2}; msg = ['Exported ' char(classNameLabel.getText.trim) ' to base workspace variable findjobj_hdls']; else % Right-click (context-menu) callback data = varargin{1}; obj = data{1}; varName = data{2}; if isempty(varName) varName = inputdlg('Enter workspace variable name','FindJObj'); if isempty(varName), return; end % bail out on <Cancel> varName = varName{1}; if isempty(varName) || ~ischar(varName), return; end % bail out on empty/null varName = genvarname(varName); end assignin('base',varName,handle(obj,'CallbackProperties')); msg = ['Exported object to base workspace variable ' varName]; end msgbox(msg,'FindJObj','help'); catch % Never mind... dispError end end
Callback function for focus button
function btFocus_Callback(src, evd, varargin) %#ok try % Request focus for the specified object object = getTopSelectedObject(varargin{:}); object.requestFocus; catch try object = object.java.getPeer.requestFocus; object.requestFocus; catch % Never mind... %dispError end end end
Callback function for Inspect button
function btInspect_Callback(src, evd, varargin) %#ok try % Inspect the specified object if length(varargin) == 1 object = varargin{1}; else object = getTopSelectedObject(varargin{:}); end if isempty(which('uiinspect')) % If the user has not indicated NOT to be informed about UIInspect if ~ispref('FindJObj','dontCheckUIInspect') % Ask the user whether to download UIINSPECT (YES, no, no & don't ask again) answer = questdlg({'The object inspector requires UIINSPECT from the MathWorks File Exchange. UIINSPECT was created by Yair Altman, like this FindJObj utility.','','Download & install UIINSPECT?'},'UIInspect','Yes','No','No & never ask again','Yes'); switch answer case 'Yes' % => Yes: download & install try % Download UIINSPECT baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/17935'; fileUrl = [baseUrl '?controller=file_infos&download=true']; %file = urlread(fileUrl); %file = regexprep(file,[char(13),char(10)],'\n'); %convert to OS-dependent EOL % Install... %newPath = fullfile(fileparts(which(mfilename)),'uiinspect.m'); %fid = fopen(newPath,'wt'); %fprintf(fid,'%s',file); %fclose(fid); [fpath,fname,fext] = fileparts(which(mfilename)); zipFileName = fullfile(fpath,'uiinspect.zip'); urlwrite(fileUrl,zipFileName); unzip(zipFileName,fpath); rehash; catch % Error downloading: inform the user msgbox(['Error in downloading: ' lasterr], 'UIInspect', 'warn'); %#ok web(baseUrl); end % ...and now run it... %pause(0.1); drawnow; dummy = which('uiinspect'); %#ok used only to load into memory uiinspect(object); return; case 'No & never ask again' % => No & don't ask again setpref('FindJObj','dontCheckUIInspect',1); otherwise % forget it... end end drawnow; % No UIINSPECT available - run the good-ol' METHODSVIEW()... methodsview(object); else uiinspect(object); end catch try if isjava(object) methodsview(object) else methodsview(object.java); end catch % Never mind... dispError end end end
Callback function for for updates button
function btCheckFex_Callback(src, evd, varargin) %#ok try % Check the FileExchange for the latest version web('http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14317'); catch % Never mind... dispError end end
Check for existence of a newer version
function checkVersion() try % If the user has not indicated NOT to be informed if ~ispref('FindJObj','dontCheckNewerVersion') % Get the latest version date from the File Exchange webpage baseUrl = 'http://www.mathworks.com/matlabcentral/fileexchange/'; webUrl = [baseUrl '14317']; % 'loadFile.do?objectId=14317']; webPage = urlread(webUrl); modIdx = strfind(webPage,'>Updates<'); if ~isempty(modIdx) webPage = webPage(modIdx:end); % Note: regexp hangs if substr not found, so use strfind instead... %latestWebVersion = regexprep(webPage,'.*?>(20[\d-]+)</td>.*','$1'); dateIdx = strfind(webPage,'class="date">'); if ~isempty(dateIdx) latestDate = webPage(dateIdx(end)+13 : dateIdx(end)+23); try startIdx = dateIdx(end)+27; descStartIdx = startIdx + strfind(webPage(startIdx:startIdx+999),'<td>'); descEndIdx = startIdx + strfind(webPage(startIdx:startIdx+999),'</td>'); descStr = webPage(descStartIdx(1)+3 : descEndIdx(1)-2); descStr = regexprep(descStr,'</?[pP]>',''); catch descStr = ''; end % Get this file's latest date thisFileName = which(mfilename); %#ok try thisFileData = dir(thisFileName); try thisFileDatenum = thisFileData.datenum; catch % old ML versions... thisFileDatenum = datenum(thisFileData.date); end catch thisFileText = evalc('type(thisFileName)'); thisFileLatestDate = regexprep(thisFileText,'.*Change log:[\s%]+([\d-]+).*','$1'); thisFileDatenum = datenum(thisFileLatestDate,'yyyy-mm-dd'); end % If there's a newer version on the File Exchange webpage (allow 2 days grace period) if (thisFileDatenum < datenum(latestDate,'dd mmm yyyy')-2) % Ask the user whether to download the newer version (YES, no, no & don't ask again) msg = {['A newer version (' latestDate ') of FindJObj is available on the MathWorks File Exchange:'], '', ... ['\color{blue}' descStr '\color{black}'], '', ... 'Download & install the new version?'}; createStruct.Interpreter = 'tex'; createStruct.Default = 'Yes'; answer = questdlg(msg,'FindJObj','Yes','No','No & never ask again',createStruct); switch answer case 'Yes' % => Yes: download & install newer file try %fileUrl = [baseUrl '/download.do?objectId=14317&fn=findjobj&fe=.m']; fileUrl = [baseUrl '/14317?controller=file_infos&download=true']; %file = urlread(fileUrl); %file = regexprep(file,[char(13),char(10)],'\n'); %convert to OS-dependent EOL %fid = fopen(thisFileName,'wt'); %fprintf(fid,'%s',file); %fclose(fid); [fpath,fname,fext] = fileparts(thisFileName); zipFileName = fullfile(fpath,[fname '.zip']); urlwrite(fileUrl,zipFileName); unzip(zipFileName,fpath); rehash; catch % Error downloading: inform the user msgbox(['Error in downloading: ' lasterr], 'FindJObj', 'warn'); %#ok web(webUrl); end case 'No & never ask again' % => No & don't ask again setpref('FindJObj','dontCheckNewerVersion',1); otherwise % forget it... end end end else % Maybe webpage not fully loaded or changed format - bail out... end end catch % Never mind... end end
Get the first selected object (might not be the top one - depends on selection order)
function object = getTopSelectedObject(jTree, root) try object = []; numSelections = jTree.getSelectionCount; if numSelections > 0 % Get the first object specified %object = jTree.getSelectionPath.getLastPathComponent.getUserObject; nodedata = get(jTree.getSelectionPath.getLastPathComponent,'userdata'); else % Get the root object (container) %object = root.getUserObject; nodedata = get(root,'userdata'); end object = nodedata.obj; catch % Never mind... dispError end end
Update component callback upon callbacksTable data change
function tbCallbacksChanged(src, evd, object) persistent hash try % exit if invalid handle or already in Callback %if ~ishandle(src) || ~isempty(getappdata(src,'inCallback')) % || length(dbstack)>1 %exit also if not called from user action if isempty(hash), hash = java.util.Hashtable; end if ~ishandle(src) || ~isempty(hash.get(src)) % || length(dbstack)>1 %exit also if not called from user action return; end %setappdata(src,'inCallback',1); % used to prevent endless recursion % can't use getappdata(src,...) because it fails on R2010b!!! hash.put(src,1); % Update the object's callback with the modified value modifiedColIdx = evd.getColumn; modifiedRowIdx = evd.getFirstRow; if modifiedColIdx==1 && modifiedRowIdx>=0 %sanity check - should always be true table = evd.getSource; %object = get(src,'userdata'); cbName = strtrim(table.getValueAt(modifiedRowIdx,0)); try cbValue = strtrim(char(table.getValueAt(modifiedRowIdx,1))); if ~isempty(cbValue) && ismember(cbValue(1),'{[@''') cbValue = eval(cbValue); end if (~ischar(cbValue) && ~isa(cbValue, 'function_handle') && (iscom(object(1)) || iscell(cbValue))) revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, ''); else for objIdx = 1 : length(object) if ~iscom(object(objIdx)) set(object(objIdx), cbName, cbValue); else cbs = object(objIdx).eventlisteners; if ~isempty(cbs) cbs = cbs(strcmpi(cbs(:,1),cbName),:); object(objIdx).unregisterevent(cbs); end if ~isempty(cbValue) object(objIdx).registerevent({cbName, cbValue}); end end end end catch revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, lasterr) %#ok end end catch % never mind... end %setappdata(src,'inCallback',[]); % used to prevent endless recursion % can't use setappdata(src,...) because it fails on R2010b!!! hash.remove(src); end
Revert Callback table modification
function revertCbTableModification(table, modifiedRowIdx, modifiedColIdx, cbName, object, errMsg) %#ok try % Display a notification MsgBox msg = 'Callbacks must be a ''string'', or a @function handle'; if ~iscom(object(1)), msg = [msg ' or a {@func,args...} construct']; end if ~isempty(errMsg), msg = {errMsg, '', msg}; end msgbox(msg, ['Error setting ' cbName ' value'], 'warn'); % Revert to the current value curValue = ''; try if ~iscom(object(1)) curValue = charizeData(get(object(1),cbName)); else cbs = object(1).eventlisteners; if ~isempty(cbs) cbs = cbs(strcmpi(cbs(:,1),cbName),:); curValue = charizeData(cbs(1,2)); end end catch % never mind... - clear the current value end table.setValueAt(curValue, modifiedRowIdx, modifiedColIdx); catch % never mind... end end % revertCbTableModification
Get the Java positions of all HG text labels
function labelPositions = getLabelsJavaPos(container) try labelPositions = []; % Ensure we have a figure handle try h = hFig; %#ok unused catch hFig = getCurrentFigure; end % Get the figure's margins from the Matlab properties hg_labels = findall(hFig, 'Style','text'); units = get(hFig,'units'); set(hFig,'units','pixels'); outerPos = get(hFig,'OuterPosition'); innerPos = get(hFig,'Position'); set(hFig,'units',units); margins = abs(innerPos-outerPos); figX = container.getX; % =outerPos(1) figY = container.getY; % =outerPos(2) %figW = container.getWidth; % =outerPos(3) figH = container.getHeight; % =outerPos(4) % Get the relevant screen pixel size %monitorPositions = get(0,'MonitorPositions'); %for monitorIdx = size(monitorPositions,1) : -1 : 1 % screenSize = monitorPositions(monitorIdx,:); % if (outerPos(1) >= screenSize(1)) % && (outerPos(1)+outerPos(3) <= screenSize(3)) % break; % end %end %monitorBaseY = screenSize(4) - monitorPositions(1,4); % Compute the labels' screen pixel position in Java units ([0,0] = top left) for idx = 1 : length(hg_labels) matlabPos = getpixelposition(hg_labels(idx),1); javaX = figX + matlabPos(1) + margins(1); javaY = figY + figH - matlabPos(2) - matlabPos(4) - margins(2); labelPositions(idx,:) = [javaX, javaY, matlabPos(3:4)]; %#ok grow end catch % never mind... err = lasterror; %#ok debug point end end
Traverse an HG container hierarchy and extract the HG elements within
function traverseHGContainer(hcontainer,level,parent) try % Record the data for this node thisIdx = length(hg_levels) + 1; hg_levels(thisIdx) = level; hg_parentIdx(thisIdx) = parent; hg_handles(thisIdx) = handle(hcontainer); parentId = length(hg_parentIdx); % Now recursively process all this node's children (if any) %if ishghandle(hcontainer) try % try-catch is faster than checking ishghandle(hcontainer)... allChildren = allchild(handle(hcontainer)); for childIdx = 1 : length(allChildren) traverseHGContainer(allChildren(childIdx),level+1,parentId); end catch % do nothing - probably not a container %dispError end % TODO: Add axis (plot) component handles catch % forget it... end end
Debuggable "quiet" error-handling
function dispError err = lasterror; %#ok msg = err.message; for idx = 1 : length(err.stack) filename = err.stack(idx).file; if ~isempty(regexpi(filename,mfilename)) funcname = err.stack(idx).name; line = num2str(err.stack(idx).line); msg = [msg ' at <a href="matlab:opentoline(''' filename ''',' line ');">' funcname ' line #' line '</a>']; %#ok grow break; end end disp(msg); return; % debug point end
ML 7.0 - compatible ischar() function
function flag = ischar(data) try flag = builtin('ischar',data); catch flag = isa(data,'char'); end end
Set up the uitree context (right-click) menu
function jmenu = setTreeContextMenu(obj,node,tree_h) % Prepare the context menu (note the use of HTML labels) import javax.swing.* titleStr = getNodeTitleStr(obj,node); titleStr = regexprep(titleStr,'<hr>.*',''); menuItem0 = JMenuItem(titleStr); menuItem0.setEnabled(false); menuItem0.setArmed(false); menuItem1 = JMenuItem('Export handle to findjobj_hdls'); menuItem2 = JMenuItem('Export handle to...'); menuItem3 = JMenuItem('Request focus (bring to front)'); menuItem4 = JMenuItem('Flash component borders'); menuItem5 = JMenuItem('Display properties & callbacks'); menuItem6 = JMenuItem('Inspect object'); % Set the menu items' callbacks set(handle(menuItem1,'CallbackProperties'), 'ActionPerformedCallback', {@btExport_Callback,{obj,'findjobj_hdls'}}); set(handle(menuItem2,'CallbackProperties'), 'ActionPerformedCallback', {@btExport_Callback,{obj,[]}}); set(handle(menuItem3,'CallbackProperties'), 'ActionPerformedCallback', {@requestFocus,obj}); set(handle(menuItem4,'CallbackProperties'), 'ActionPerformedCallback', {@flashComponent,{obj,0.2,3}}); set(handle(menuItem5,'CallbackProperties'), 'ActionPerformedCallback', {@nodeSelected,{tree_h,node}}); set(handle(menuItem6,'CallbackProperties'), 'ActionPerformedCallback', {@btInspect_Callback,obj}); % Add all menu items to the context menu (with internal separator) jmenu = JPopupMenu; jmenu.add(menuItem0); jmenu.addSeparator; handleValue=[]; try handleValue = double(obj); catch, end; %#ok if ~isempty(handleValue) % For valid HG handles only menuItem0a = JMenuItem('Copy handle value to clipboard'); set(handle(menuItem0a,'CallbackProperties'), 'ActionPerformedCallback', sprintf('clipboard(''copy'',%.99g)',handleValue)); jmenu.add(menuItem0a); end jmenu.add(menuItem1); jmenu.add(menuItem2); jmenu.addSeparator; jmenu.add(menuItem3); jmenu.add(menuItem4); jmenu.add(menuItem5); jmenu.add(menuItem6); end % setTreeContextMenu
Set the mouse-press callback
function treeMousePressedCallback(hTree, eventData, tree_h) %#ok hTree is unused if eventData.isMetaDown % right-click is like a Meta-button % Get the clicked node clickX = eventData.getX; clickY = eventData.getY; jtree = eventData.getSource; treePath = jtree.getPathForLocation(clickX, clickY); try % Modify the context menu based on the clicked node node = treePath.getLastPathComponent; userdata = get(node,'userdata'); obj = userdata.obj; jmenu = setTreeContextMenu(obj,node,tree_h); % TODO: remember to call jmenu.remove(item) in item callback % or use the timer hack shown here to remove the item: % timerFcn = {@menuRemoveItem,jmenu,item}; % start(timer('TimerFcn',timerFcn,'StartDelay',0.2)); % Display the (possibly-modified) context menu jmenu.show(jtree, clickX, clickY); jmenu.repaint; % This is for debugging: userdata.tree = jtree; setappdata(gcf,'findjobj_hgtree',userdata) catch % clicked location is NOT on top of any node % Note: can also be tested by isempty(treePath) end end end % treeMousePressedCallback
Remove the extra context menu item after display
function menuRemoveItem(hObj,eventData,jmenu,item) %#ok unused jmenu.remove(item); end % menuRemoveItem
Get the title for the tooltip and context (right-click) menu
function nodeTitleStr = getNodeTitleStr(obj,node) try % Display the full classname and object name in the tooltip %nodeName = char(node.getName); %nodeName = strrep(nodeName, '<HTML><i><font color="gray">',''); %nodeName = strrep(nodeName, '</font></i></html>',''); nodeName = char(getNodeName(obj,99)); [objClass,objName] = strtok(nodeName); objName = objName(3:end-1); % strip leading ( and trailing ) if isempty(objName), objName = '(none found)'; end nodeName = char(node.getName); objClass = char(obj.getClass.getName); nodeTitleStr = sprintf('<html>Class name: <font color="blue">%s</font><br>Text/title: %s',objClass,objName); % If the component is invisible, state this in the tooltip if ~isempty(strfind(nodeName,'color="gray"')) nodeTitleStr = [nodeTitleStr '<br><font color="gray"><i><b>*** Invisible ***</b></i></font>']; end nodeTitleStr = [nodeTitleStr '<hr>Right-click for context-menu']; catch % Possible not a Java object - try treating as an HG handle try handleValueStr = sprintf('#: <font color="blue"><b>%.99g<b></font>',double(obj)); try type = ''; type = get(obj,'type'); type(1) = upper(type(1)); catch if ~ishandle(obj) type = ['<font color="red"><b>Invalid <i>' char(node.getName) '</i>']; handleValueStr = '!!!</b></font><br>Perhaps this handle was deleted after this UIInspect tree was<br>already drawn. Try to refresh by selecting any valid node handle'; end end nodeTitleStr = sprintf('<html>%s handle %s',type,handleValueStr); try % If the component is invisible, state this in the tooltip if strcmp(get(obj,'Visible'),'off') nodeTitleStr = [nodeTitleStr '<br><center><font color="gray"><i>Invisible</i></font>']; end catch % never mind... end catch % never mind... - ignore end end end % getNodeTitleStr
Handle tree mouse movement callback - used to set the tooltip & context-menu
function treeMouseMovedCallback(hTree, eventData) try x = eventData.getX; y = eventData.getY; jtree = eventData.getSource; treePath = jtree.getPathForLocation(x, y); try % Set the tooltip string based on the hovered node node = treePath.getLastPathComponent; userdata = get(node,'userdata'); obj = userdata.obj; tooltipStr = getNodeTitleStr(obj,node); set(hTree,'ToolTipText',tooltipStr) catch % clicked location is NOT on top of any node % Note: can also be tested by isempty(treePath) end catch dispError; end return; % denug breakpoint end % treeMouseMovedCallback
Request focus for a specific object handle
function requestFocus(hTree, eventData, obj) %#ok hTree & eventData are unused % Ensure the object handle is valid if isjava(obj) obj.requestFocus; return; elseif ~ishandle(obj) msgbox('The selected object does not appear to be a valid handle as defined by the ishandle() function. Perhaps this object was deleted after this hierarchy tree was already drawn. Refresh this tree by selecting a valid node handle and then retry.','FindJObj','warn'); beep; return; end try foundFlag = 0; while ~foundFlag if isempty(obj), return; end % sanity check type = get(obj,'type'); obj = double(obj); foundFlag = any(strcmp(type,{'figure','axes','uicontrol'})); if ~foundFlag obj = get(obj,'Parent'); end end feval(type,obj); catch % never mind... dispError; end end % requestFocus
end % FINDJOBJ
TODO TODO TODO
%{ - Enh: Improve performance - esp. expandNode() (performance solved in non-nteractive mode) - Enh: Add property listeners - same problem in MathWork's inspect.m - Enh: Display additional properties - same problem in MathWork's inspect.m - Enh: Add axis (plot, Graphics) component handles - Enh: Group callbacks according to the first word (up to 2nd cap letter) - Enh: Add figure thumbnail image below the java tree (& indicate corresponding jObject when selected) - Enh: scroll initially-selected node into view (problem because treenode has no pixel location) - Fix: java exceptions when getting some fields (com.mathworks.mlwidgets.inspector.PropertyRootNode$PropertyListener$1$1.run) - Fix: use EDT if available (especially in flashComponent) %}