source: MML/trunk/machine/SOLEIL/common/toolbox/GUILayout/+uiextras/VBoxFlex.m @ 4

Last change on this file since 4 was 4, checked in by zhangj, 10 years ago

Initial import--MML version from SOLEIL@2013

File size: 12.8 KB
Line 
1classdef VBoxFlex < uiextras.VBox
2    %VBoxFlex  A dynamically resizable vertical layout
3    %
4    %   obj = uiextras.VBoxFlex() creates a new dynamically resizable
5    %   vertical box layout with all parameters set to defaults. The output
6    %   is a new layout object that can be used as the parent for other
7    %   user-interface components.
8    %
9    %   obj = uiextras.VBoxFlex(param,value,...) also sets one or more
10    %   parameter values.
11    %
12    %   See the <a href="matlab:doc uiextras.VBoxFlex">documentation</a> for more detail and the list of properties.
13    %
14    %   Examples:
15    %   >> f = figure( 'Name', 'uiextras.VBoxFlex example' );
16    %   >> b = uiextras.VBoxFlex( 'Parent', f );
17    %   >> uicontrol( 'Parent', b, 'Background', 'r' )
18    %   >> uicontrol( 'Parent', b, 'Background', 'b' )
19    %   >> uicontrol( 'Parent', b, 'Background', 'g' )
20    %   >> uicontrol( 'Parent', b, 'Background', 'y' )
21    %   >> set( b, 'Sizes', [-1 100 -2 -1], 'Spacing', 5 );
22    %
23    %   See also: uiextras.HBoxFlex
24    %             uiextras.VBox
25    %             uiextras.Grid
26   
27    %   Copyright 2009 The MathWorks, Inc.
28    %   1.1
29    %   2012/05/08 08:02:58
30   
31    properties
32        ShowMarkings = 'on'  % Show markings on the draggable dividers [on|off]
33    end % public methods
34   
35    properties( SetAccess = private, GetAccess = private )
36        Dividers = []
37        SelectedDivider = -1
38    end % private properties
39   
40    methods
41       
42        function obj = VBoxFlex( varargin )
43            %VBoxFlex  Create a VBoxFlex layout
44           
45            % First step is to create the parent class. We pass the
46            % arguments (if any) just incase the parent needs setting
47            obj@uiextras.VBox( varargin{:} );
48
49            % Set some defaults
50            obj.setPropertyFromDefault( 'ShowMarkings' );
51           
52            % Set user-supplied property values
53            if nargin > 0
54                set( obj, varargin{:} );
55            end
56        end % constructor
57       
58    end % public methods
59   
60    methods
61       
62        function set.ShowMarkings( obj, value )
63            % Check
64            if ~ischar( value ) || ~ismember( lower( value ), {'on','off'} )
65                error( 'GUILayout:VBoxFlex:InvalidArgument', ...
66                    'Property ''ShowMarkings'' may only be set to ''on'' or ''off''.' )
67            end
68            % Apply
69            obj.ShowMarkings = lower( value );
70            obj.redraw();
71        end % set.ShowMarkings
72       
73    end % accessor methods
74       
75    methods( Access = protected )
76       
77        function redraw( obj )
78            %redraw  Redraw container contents.
79           
80            % First simply call the grid redraw
81            [widths,heights] = redraw@uiextras.VBox(obj);
82            sizes = obj.Sizes;
83            nChildren = numel( obj.getValidChildren() );
84            padding = obj.Padding;
85            spacing = obj.Spacing;
86           
87            % Get container width and height
88            totalPosition = ceil( getpixelposition( obj.UIContainer ) );
89            totalHeight = totalPosition(4);
90           
91            % Now also add some dividers
92            mph = uiextras.MousePointerHandler( obj.Parent );
93            numDynamic = 0;
94            for ii = 1:nChildren-1
95                if any(sizes(1:ii)<0) && any(sizes(ii+1:end)<0)
96                    numDynamic = numDynamic + 1;
97                    % Both dynamic, so add a divider
98                    position = [padding + 1, ...
99                        totalHeight - sum( heights(1:ii) ) - padding - spacing*ii + 1, ...
100                        widths(ii), ...
101                        max(1,spacing)];
102                    % Create the divider widget
103                    if numDynamic > numel( obj.Dividers )
104                        obj.Dividers(numDynamic) = uiextras.makeFlexDivider( ...
105                            obj.UIContainer, ...
106                            position, ...
107                            get( obj.UIContainer, 'BackgroundColor' ), ...
108                            'Horizontal', ...
109                            obj.ShowMarkings );
110                        set( obj.Dividers(numDynamic), 'ButtonDownFcn', @obj.onButtonDown, ...
111                            'Tag', 'UIExtras:VBoxFlex:Divider' );
112                        % Add it to the mouse-over handler
113                        mph.register( obj.Dividers(numDynamic), 'top' );
114                    else
115                        % Just update an existing divider
116                        uiextras.makeFlexDivider( ...
117                            obj.Dividers(numDynamic), ...
118                            position, ...
119                            get( obj.UIContainer, 'BackgroundColor' ), ...
120                            'Horizontal', ...
121                            obj.ShowMarkings );
122                    end
123                    setappdata( obj.Dividers(numDynamic), 'WhichDivider', ii );
124                end
125            end
126            % Remove any excess dividers
127            if numel( obj.Dividers ) > numDynamic
128                delete( obj.Dividers(numDynamic+1:end) );
129                obj.Dividers(numDynamic+1:end) = [];
130            end
131        end % redraw
132       
133        function onButtonDown( obj, source, eventData ) %#ok<INUSD>
134            %onButtonDown  user has clicked on a divider
135           
136            figh = ancestor( source, 'figure' );
137            % We need to store any existing motion callbacks so that we can
138            % restore them later.
139            oldProps = struct();
140            oldProps.WindowButtonMotionFcn = get( figh, 'WindowButtonMotionFcn' );
141            oldProps.WindowButtonUpFcn = get( figh, 'WindowButtonUpFcn' );
142            oldProps.Pointer = get( figh, 'Pointer' );
143            oldProps.Units = get( figh, 'Units' );
144
145            % Make sure all interaction modes are off to prevent our
146            % callbacks being clobbered
147            zoomh = zoom( figh );
148            r3dh = rotate3d( figh );
149            panh = pan( figh );
150            oldState = '';
151            if isequal( zoomh.Enable, 'on' )
152                zoomh.Enable = 'off';
153                oldState = 'zoom';
154            end
155            if isequal( r3dh.Enable, 'on' )
156                r3dh.Enable = 'off';
157                oldState = 'rotate3d';
158            end
159            if isequal( panh.Enable, 'on' )
160                panh.Enable = 'off';
161                oldState = 'pan';
162            end
163           
164            % Set our new callbacks
165            set( figh, ...
166                'WindowButtonMotionFcn', @obj.onButtonMotion, ...
167                'WindowButtonUpFcn', {@obj.onButtonUp, oldProps, oldState}, ...
168                'Pointer', 'top', ...
169                'Units', 'Pixels' );
170           
171            % Make the divider visible
172            cdata = get( source, 'CData' );
173            if mean( cdata(:) ) < 0.5
174                % Make it brighter
175                cdata = 1-0.5*(1-cdata);
176                newCol = 1-0.5*(1-get( obj.UIContainer, 'BackgroundColor' ));
177            else
178                % Make it darker
179                cdata = 0.5*cdata;
180                newCol = 0.5*get( obj.UIContainer, 'BackgroundColor' );
181            end
182           
183            set( source, ...
184                'BackgroundColor', newCol, ...
185                'ForegroundColor', newCol, ...
186                'CData', cdata );
187           
188            obj.SelectedDivider = source;
189        end % onButtonDown
190       
191        function onButtonMotion( obj, source, eventData ) %#ok<INUSD>
192            %onButtonMotion  user is dragging a divider
193            figh = ancestor( source, 'figure' );
194            cursorpos = get( figh, 'CurrentPoint' );
195            pos0 = getpixelposition( obj.UIContainer, true );
196           
197            % We need to gaurd against the focus having been lost. In this
198            % case we should have received a button-up event, but sometimes
199            % don't (at least on Windows).
200            if ishandle( obj.SelectedDivider )
201                dividerpos = get( obj.SelectedDivider, 'Position' );
202                dividerpos(2) = cursorpos(2) - pos0(2) - round(obj.Spacing/2) + 1;
203                % Make sure that the position doesn't cause an element to
204                % shrink too much
205                minSizes = obj.MinimumSizes(:);
206                pixSizes = uiextras.calculatePixelSizes( pos0(4), ...
207                    obj.Sizes, minSizes, obj.Padding, obj.Spacing );
208                N = numel( minSizes );
209                % Sometimes the actual width is smaller than the minimum!
210                minSizes = min( minSizes, pixSizes );
211                whichDivider = getappdata( obj.SelectedDivider, 'WhichDivider' );
212                minPos = pos0(4) - ceil( obj.Padding ...
213                    + sum( pixSizes(1:whichDivider-1) ) ...
214                    + minSizes(whichDivider) ...
215                    + obj.Spacing*(whichDivider-0.5) );
216                dividerpos(2) = min( dividerpos(2), minPos );
217                if whichDivider<(N-1)
218                    maxPos = floor( obj.Padding ...
219                        + sum( pixSizes(whichDivider+2:end) ) ...
220                        + minSizes(whichDivider+1) ...
221                        + obj.Spacing*(N-whichDivider-0.5) );
222                else
223                    % Final divider
224                    maxPos = floor( obj.Padding ...
225                        + minSizes(whichDivider+1) ...
226                        + obj.Spacing*0.5 );
227                end
228                dividerpos(2) = max( dividerpos(2), maxPos );
229                set( obj.SelectedDivider, 'Position', dividerpos );
230            else
231                % Divider has been lost, so we are in a bad state. The
232                % best we can do is kill the callbacks and attempt to put
233                % the figure back in a decent state.
234                set( figh, 'Pointer', 'arrow', ...
235                    'WindowButtonMotionFcn', [], ...
236                    'WindowButtonUpFcn', [] );
237            end
238        end % onButtonMotion
239       
240        function onButtonUp( obj, source, eventData, oldFigProps, oldState )
241            %onButtonUp  user has finished dragging a divider
242           
243            % Deliberately call the motion function to ensure any last
244            % movement is captured
245            obj.onButtonMotion( source, eventData );
246           
247             % Restore figure properties
248            figh = ancestor( source, 'figure' );
249            flds = fieldnames( oldFigProps );
250            for ii=1:numel(flds)
251                set( figh, flds{ii}, oldFigProps.(flds{ii}) );
252            end
253           
254            % If the figure has an interaction mode set, re-set it now
255            if ~isempty( oldState )
256                switch upper( oldState )
257                    case 'ZOOM'
258                        zoom( figh, 'on' );
259                    case 'PAN'
260                        pan( figh, 'on' );
261                    case 'ROTATE3D'
262                        rotate3d( figh, 'on' );
263                    otherwise
264                        error( 'GUILayout:FlexLayout:BadInteractionMode', 'Interaction mode ''%s'' not recognised', oldState );
265                end
266            end
267           
268            % Work out which divider was moved and which are the resizable
269            % elements either side of it
270            newPos = get( obj.SelectedDivider, 'Position' );
271            origPos = getappdata( obj.SelectedDivider, 'OriginalPosition' );
272            whichDivider = getappdata( obj.SelectedDivider, 'WhichDivider' );
273            obj.SelectedDivider = -1;
274            delta = newPos(2) - origPos(2);
275            sizes = obj.Sizes;
276            % Convert all flexible sizes into pixel units
277            totalPosition = ceil( getpixelposition( obj.UIContainer ) );
278            totalHeight = totalPosition(4);
279            heights = uiextras.calculatePixelSizes( totalHeight, ...
280                    obj.Sizes, obj.MinimumSizes, obj.Padding, obj.Spacing );
281
282           
283            bottomelement = find( sizes(1:whichDivider)<0, 1, 'last' );
284            topelement = find( sizes(whichDivider+1:end)<0, 1, 'first' )+whichDivider;
285           
286            % Now work out the new sizes. Note that we must ensure the size
287            % stays negative otherwise it'll stop being resizable
288            change = sum(sizes(sizes<0)) * delta / sum( heights(sizes<0) );
289            sizes(topelement) = min( -0.000001, sizes(topelement) + change );
290            sizes(bottomelement) = min( -0.000001, sizes(bottomelement) - change );
291           
292            % Setting the sizes will cause a redraw
293            obj.Sizes = sizes;
294        end % onButtonUp
295       
296        function onBackgroundColorChanged( obj, source, eventData ) %#ok<INUSD>
297            %onBackgroundColorChanged  Callback that fires when the container background color is changed
298            %
299            % We need to make the dividers match the background, so redarw
300            % them
301            obj.redraw();
302        end % onChildRemoved
303       
304    end % protected methods
305   
306end % classdef
Note: See TracBrowser for help on using the repository browser.