Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / resizer / SplitterTracker.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.resizer.SplitterTracker
17  * @extends Ext.dd.DragTracker
18  * Private utility class for Ext.Splitter.
19  * @private
20  */
21 Ext.define('Ext.resizer.SplitterTracker', {
22     extend: 'Ext.dd.DragTracker',
23     requires: ['Ext.util.Region'],
24     enabled: true,
25     
26     overlayCls: Ext.baseCSSPrefix + 'resizable-overlay',
27
28     getPrevCmp: function() {
29         var splitter = this.getSplitter();
30         return splitter.previousSibling();
31     },
32
33     getNextCmp: function() {
34         var splitter = this.getSplitter();
35         return splitter.nextSibling();
36     },
37
38     // ensure the tracker is enabled, store boxes of previous and next
39     // components and calculate the constrain region
40     onBeforeStart: function(e) {
41         var me = this,
42             prevCmp = me.getPrevCmp(),
43             nextCmp = me.getNextCmp(),
44             collapseEl = me.getSplitter().collapseEl,
45             overlay;
46             
47         if (collapseEl && (e.getTarget() === me.getSplitter().collapseEl.dom)) {
48             return false;
49         }
50
51         // SplitterTracker is disabled if any of its adjacents are collapsed.
52         if (nextCmp.collapsed || prevCmp.collapsed) {
53             return false;
54         }
55         
56         overlay = me.overlay =  Ext.getBody().createChild({
57             cls: me.overlayCls, 
58             html: ' '
59         });
60         overlay.unselectable();
61         overlay.setSize(Ext.Element.getViewWidth(true), Ext.Element.getViewHeight(true));
62         overlay.show();
63         
64         // store boxes of previous and next
65         me.prevBox  = prevCmp.getEl().getBox();
66         me.nextBox  = nextCmp.getEl().getBox();
67         me.constrainTo = me.calculateConstrainRegion();
68     },
69
70     // We move the splitter el. Add the proxy class.
71     onStart: function(e) {
72         var splitter = this.getSplitter();
73         splitter.addCls(splitter.baseCls + '-active');
74     },
75
76     // calculate the constrain Region in which the splitter el may be moved.
77     calculateConstrainRegion: function() {
78         var me         = this,
79             splitter   = me.getSplitter(),
80             splitWidth = splitter.getWidth(),
81             defaultMin = splitter.defaultSplitMin,
82             orient     = splitter.orientation,
83             prevBox    = me.prevBox,
84             prevCmp    = me.getPrevCmp(),
85             nextBox    = me.nextBox,
86             nextCmp    = me.getNextCmp(),
87             // prev and nextConstrainRegions are the maximumBoxes minus the
88             // minimumBoxes. The result is always the intersection
89             // of these two boxes.
90             prevConstrainRegion, nextConstrainRegion;
91
92         // vertical splitters, so resizing left to right
93         if (orient === 'vertical') {
94
95             // Region constructor accepts (top, right, bottom, left)
96             // anchored/calculated from the left
97             prevConstrainRegion = Ext.create('Ext.util.Region',
98                 prevBox.y,
99                 // Right boundary is x + maxWidth if there IS a maxWidth.
100                 // Otherwise it is calculated based upon the minWidth of the next Component
101                 (prevCmp.maxWidth ? prevBox.x + prevCmp.maxWidth : nextBox.right - (nextCmp.minWidth || defaultMin)) + splitWidth,
102                 prevBox.bottom,
103                 prevBox.x + (prevCmp.minWidth || defaultMin)
104             );
105             // anchored/calculated from the right
106             nextConstrainRegion = Ext.create('Ext.util.Region',
107                 nextBox.y,
108                 nextBox.right - (nextCmp.minWidth || defaultMin),
109                 nextBox.bottom,
110                 // Left boundary is right - maxWidth if there IS a maxWidth.
111                 // Otherwise it is calculated based upon the minWidth of the previous Component
112                 (nextCmp.maxWidth ? nextBox.right - nextCmp.maxWidth : prevBox.x + (prevBox.minWidth || defaultMin)) - splitWidth
113             );
114         } else {
115             // anchored/calculated from the top
116             prevConstrainRegion = Ext.create('Ext.util.Region',
117                 prevBox.y + (prevCmp.minHeight || defaultMin),
118                 prevBox.right,
119                 // Bottom boundary is y + maxHeight if there IS a maxHeight.
120                 // Otherwise it is calculated based upon the minWidth of the next Component
121                 (prevCmp.maxHeight ? prevBox.y + prevCmp.maxHeight : nextBox.bottom - (nextCmp.minHeight || defaultMin)) + splitWidth,
122                 prevBox.x
123             );
124             // anchored/calculated from the bottom
125             nextConstrainRegion = Ext.create('Ext.util.Region',
126                 // Top boundary is bottom - maxHeight if there IS a maxHeight.
127                 // Otherwise it is calculated based upon the minHeight of the previous Component
128                 (nextCmp.maxHeight ? nextBox.bottom - nextCmp.maxHeight : prevBox.y + (prevCmp.minHeight || defaultMin)) - splitWidth,
129                 nextBox.right,
130                 nextBox.bottom - (nextCmp.minHeight || defaultMin),
131                 nextBox.x
132             );
133         }
134
135         // intersection of the two regions to provide region draggable
136         return prevConstrainRegion.intersect(nextConstrainRegion);
137     },
138
139     // Performs the actual resizing of the previous and next components
140     performResize: function(e) {
141         var me       = this,
142             offset   = me.getOffset('dragTarget'),
143             splitter = me.getSplitter(),
144             orient   = splitter.orientation,
145             prevCmp  = me.getPrevCmp(),
146             nextCmp  = me.getNextCmp(),
147             owner    = splitter.ownerCt,
148             layout   = owner.getLayout();
149
150         // Inhibit automatic container layout caused by setSize calls below.
151         owner.suspendLayout = true;
152
153         if (orient === 'vertical') {
154             if (prevCmp) {
155                 if (!prevCmp.maintainFlex) {
156                     delete prevCmp.flex;
157                     prevCmp.setSize(me.prevBox.width + offset[0], prevCmp.getHeight());
158                 }
159             }
160             if (nextCmp) {
161                 if (!nextCmp.maintainFlex) {
162                     delete nextCmp.flex;
163                     nextCmp.setSize(me.nextBox.width - offset[0], nextCmp.getHeight());
164                 }
165             }
166         // verticals
167         } else {
168             if (prevCmp) {
169                 if (!prevCmp.maintainFlex) {
170                     delete prevCmp.flex;
171                     prevCmp.setSize(prevCmp.getWidth(), me.prevBox.height + offset[1]);
172                 }
173             }
174             if (nextCmp) {
175                 if (!nextCmp.maintainFlex) {
176                     delete nextCmp.flex;
177                     nextCmp.setSize(prevCmp.getWidth(), me.nextBox.height - offset[1]);
178                 }
179             }
180         }
181         delete owner.suspendLayout;
182         layout.onLayout();
183     },
184
185     // Cleans up the overlay (if we have one) and calls the base. This cannot be done in
186     // onEnd, because onEnd is only called if a drag is detected but the overlay is created
187     // regardless (by onBeforeStart).
188     endDrag: function () {
189         var me = this;
190
191         if (me.overlay) {
192              me.overlay.remove();
193              delete me.overlay;
194         }
195
196         me.callParent(arguments); // this calls onEnd
197     },
198
199     // perform the resize and remove the proxy class from the splitter el
200     onEnd: function(e) {
201         var me = this,
202             splitter = me.getSplitter();
203             
204         splitter.removeCls(splitter.baseCls + '-active');
205         me.performResize();
206     },
207
208     // Track the proxy and set the proper XY coordinates
209     // while constraining the drag
210     onDrag: function(e) {
211         var me        = this,
212             offset    = me.getOffset('dragTarget'),
213             splitter  = me.getSplitter(),
214             splitEl   = splitter.getEl(),
215             orient    = splitter.orientation;
216
217         if (orient === "vertical") {
218             splitEl.setX(me.startRegion.left + offset[0]);
219         } else {
220             splitEl.setY(me.startRegion.top + offset[1]);
221         }
222     },
223
224     getSplitter: function() {
225         return Ext.getCmp(this.getDragCt().id);
226     }
227 });