-
Notifications
You must be signed in to change notification settings - Fork 0
/
findjobj.m
3210 lines (2984 loc) · 155 KB
/
findjobj.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
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;
%
% Technical explanation & details:
% http://undocumentedmatlab.com/blog/findjobj/
% http://undocumentedmatlab.com/blog/findjobj-gui-display-container-hierarchy/
%
% 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:
% 2013-06-30: Additional fixes for the upcoming HG2
% 2013-05-15: Fix for the upcoming HG2
% 2013-02-21: Fixed HG-Java warnings
% 2013-01-23: Fixed callbacks table grouping & editing bugs; added hidden properties to the properties tooltip; updated help section
% 2013-01-13: Improved callbacks table; fixed tree refresh failure; fixed: tree node-selection didn't update the props pane nor flash the selected component
% 2012-07-25: Fixes for R2012a 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
% $Revision: 1.39 $ $Date: 2013/06/30 22:34:52 $
% 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{:});
% 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(class(java(handles(end))),'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(class(jcontainer),'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(class(childComponent))])
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) && ~isa(data,'java.lang.String')
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
%% Prepare a hierarchical callbacks table data
function setProp(list,name,value,category)
prop = eval('com.jidesoft.grid.DefaultProperty();'); % prevent JIDE alert by run-time (not load-time) evaluation
prop.setName(name);
prop.setType(java.lang.String('').getClass);
prop.setValue(value);
prop.setEditable(true);
prop.setExpert(true);
%prop.setCategory(['<html><b><u><font color="blue">' category ' callbacks']);
prop.setCategory([category ' callbacks']);
list.add(prop);
end % getTreeData
%% Prepare a hierarchical callbacks table data
function list = getTreeData(data)
list = java.util.ArrayList();
names = regexprep(data,'([A-Z][a-z]+).*','$1');
%hash = java.util.Hashtable;
others = {};
for propIdx = 1 : length(data)
if (propIdx < length(data) && strcmp(names{propIdx},names{propIdx+1})) || ...
(propIdx > 1 && strcmp(names{propIdx},names{propIdx-1}))
% Child callback property
setProp(list,data{propIdx,1},data{propIdx,2},names{propIdx});
else
% Singular callback property => Add to 'Other' category at bottom of the list
others(end+1,:) = data(propIdx,:); %#ok
end
end
for propIdx = 1 : size(others,1)
setProp(list,others{propIdx,1},others{propIdx,2},'Other');
end