1/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/**
30 * @constructor
31 * @extends {WebInspector.View}
32 * @param {boolean} isVertical
33 * @param {string=} sidebarSizeSettingName
34 * @param {number=} defaultSidebarWidth
35 * @param {number=} defaultSidebarHeight
36 */
37WebInspector.SplitView = function(isVertical, sidebarSizeSettingName, defaultSidebarWidth, defaultSidebarHeight)
38{
39    WebInspector.View.call(this);
40
41    this.registerRequiredCSS("splitView.css");
42
43    this.element.className = "split-view";
44
45    this._firstElement = this.element.createChild("div", "split-view-contents scroll-target split-view-contents-first");
46    this._secondElement = this.element.createChild("div", "split-view-contents scroll-target split-view-contents-second");
47
48    this._resizerElement = this.element.createChild("div", "split-view-resizer");
49    this.installResizer(this._resizerElement);
50    this._resizable = true;
51
52    this._savedSidebarWidth = defaultSidebarWidth || 200;
53    this._savedSidebarHeight = defaultSidebarHeight || this._savedSidebarWidth;
54
55    if (0 < this._savedSidebarWidth && this._savedSidebarWidth < 1 &&
56        0 < this._savedSidebarHeight && this._savedSidebarHeight < 1)
57        this._useFraction = true;
58
59    this._sidebarSizeSettingName = sidebarSizeSettingName;
60
61    this.setSecondIsSidebar(true);
62
63    this._innerSetVertical(isVertical);
64}
65
66WebInspector.SplitView.prototype = {
67    /**
68     * @return {boolean}
69     */
70    isVertical: function()
71    {
72        return this._isVertical;
73    },
74
75    /**
76     * @param {boolean} isVertical
77     */
78    setVertical: function(isVertical)
79    {
80        if (this._isVertical === isVertical)
81            return;
82
83        this._innerSetVertical(isVertical);
84
85        if (this.isShowing())
86            this._updateLayout();
87    },
88
89    /**
90     * @param {boolean} isVertical
91     */
92    _innerSetVertical: function(isVertical)
93    {
94        this.element.removeStyleClass(this._isVertical ? "split-view-vertical" : "split-view-horizontal");
95        this._isVertical = isVertical;
96        this.element.addStyleClass(this._isVertical ? "split-view-vertical" : "split-view-horizontal");
97    },
98
99    _updateLayout: function()
100    {
101        this._updateTotalSize();
102
103        delete this._sidebarSize;  // Force update.
104        this.setSidebarSize(this._lastSidebarSize());
105    },
106
107    /**
108     * @return {Element}
109     */
110    firstElement: function()
111    {
112        return this._firstElement;
113    },
114
115    /**
116     * @return {Element}
117     */
118    secondElement: function()
119    {
120        return this._secondElement;
121    },
122
123    /**
124     * @return {Element}
125     */
126    get mainElement()
127    {
128        return this.isSidebarSecond() ? this.firstElement() : this.secondElement();
129    },
130
131    /**
132     * @return {Element}
133     */
134    get sidebarElement()
135    {
136        return this.isSidebarSecond() ? this.secondElement() : this.firstElement();
137    },
138
139    /**
140     * @return {boolean}
141     */
142    isSidebarSecond: function()
143    {
144        return this._secondIsSidebar;
145    },
146
147    /**
148     * @param {boolean} secondIsSidebar
149     */
150    setSecondIsSidebar: function(secondIsSidebar)
151    {
152        this.sidebarElement.removeStyleClass("split-view-sidebar");
153        this._secondIsSidebar = secondIsSidebar;
154        this.sidebarElement.addStyleClass("split-view-sidebar");
155    },
156
157    /**
158     * @return {Element}
159     */
160    resizerElement: function()
161    {
162        return this._resizerElement;
163    },
164
165    showOnlyFirst: function()
166    {
167        this._showOnly(this._firstElement, this._secondElement);
168    },
169
170    showOnlySecond: function()
171    {
172        this._showOnly(this._secondElement, this._firstElement);
173    },
174
175    /**
176     * @param {Element} sideA
177     * @param {Element} sideB
178     */
179    _showOnly: function(sideA, sideB)
180    {
181        sideA.removeStyleClass("hidden");
182        sideA.addStyleClass("maximized");
183        sideB.addStyleClass("hidden");
184        sideB.removeStyleClass("maximized");
185        this._removeAllLayoutProperties();
186
187        this._isShowingOne = true;
188        this.setResizable(false);
189        this.doResize();
190    },
191
192    _removeAllLayoutProperties: function()
193    {
194        this._firstElement.style.removeProperty("right");
195        this._firstElement.style.removeProperty("bottom");
196        this._firstElement.style.removeProperty("width");
197        this._firstElement.style.removeProperty("height");
198
199        this._secondElement.style.removeProperty("left");
200        this._secondElement.style.removeProperty("top");
201        this._secondElement.style.removeProperty("width");
202        this._secondElement.style.removeProperty("height");
203
204        this._resizerElement.style.removeProperty("left");
205        this._resizerElement.style.removeProperty("right");
206        this._resizerElement.style.removeProperty("top");
207        this._resizerElement.style.removeProperty("bottom");
208
209        this._resizerElement.style.removeProperty("margin-left");
210        this._resizerElement.style.removeProperty("margin-right");
211        this._resizerElement.style.removeProperty("margin-top");
212        this._resizerElement.style.removeProperty("margin-bottom");
213    },
214
215    showBoth: function()
216    {
217        this._isShowingOne = false;
218        this._firstElement.removeStyleClass("hidden");
219        this._firstElement.removeStyleClass("maximized");
220        this._secondElement.removeStyleClass("hidden");
221        this._secondElement.removeStyleClass("maximized");
222
223        this._updateLayout();
224
225        this.setResizable(true);
226        this.doResize();
227    },
228
229    /**
230     * @param {boolean} resizable
231     */
232    setResizable: function(resizable)
233    {
234        if (this._resizable === resizable)
235            return;
236        this._resizable = resizable;
237        if (resizable)
238            this._resizerElement.removeStyleClass("hidden");
239        else
240            this._resizerElement.addStyleClass("hidden");
241    },
242
243    /**
244     * @param {number} size
245     */
246    setSidebarSize: function(size)
247    {
248        if (this._sidebarSize === size)
249            return;
250
251        size = this.applyConstraints(size);
252        this._innerSetSidebarSize(size);
253        this._saveSidebarSize(size);
254    },
255
256    /**
257     * @return {number}
258     */
259    sidebarSize: function()
260    {
261        return this._sidebarSize;
262    },
263
264    /**
265     * @return {number}
266     */
267    totalSize: function()
268    {
269        return this._totalSize;
270    },
271
272    _updateTotalSize: function()
273    {
274        this._totalSize = this._isVertical ? this.element.offsetWidth : this.element.offsetHeight;
275        if (this._useFraction)
276            this._sidebarSize = this._lastSidebarSize();
277    },
278
279    /**
280     * @param {number} size
281     */
282    _innerSetSidebarSize: function(size)
283    {
284        if (this._isShowingOne)
285            return;
286
287        this._removeAllLayoutProperties();
288
289        var sizeValue;
290        if (this._useFraction)
291            sizeValue = (size / this._totalSize) * 100 + "%";
292        else
293            sizeValue = size + "px";
294
295        if (this._isVertical) {
296            var resizerWidth = this._resizerElement.offsetWidth;
297            if (this._secondIsSidebar) {
298                this._firstElement.style.right = sizeValue;
299                this._secondElement.style.width = sizeValue;
300                this._resizerElement.style.right = sizeValue;
301                this._resizerElement.style.marginRight = -resizerWidth / 2 + "px";
302            } else {
303                this._firstElement.style.width = sizeValue;
304                this._secondElement.style.left = sizeValue;
305                this._resizerElement.style.left = sizeValue;
306                this._resizerElement.style.marginLeft = -resizerWidth / 2 + "px";
307            }
308        } else {
309            var resizerHeight = this._resizerElement.offsetHeight;
310
311            if (this._secondIsSidebar) {
312                this._firstElement.style.bottom = sizeValue;
313                this._secondElement.style.height = sizeValue;
314                this._resizerElement.style.bottom = sizeValue;
315                this._resizerElement.style.marginBottom = -resizerHeight / 2 + "px";
316            } else {
317                this._firstElement.style.height = sizeValue;
318                this._secondElement.style.top = sizeValue;
319                this._resizerElement.style.top = sizeValue;
320                this._resizerElement.style.marginTop = -resizerHeight / 2 + "px";
321            }
322        }
323
324        this._sidebarSize = size;
325        this.doResize();
326    },
327
328    /**
329     * @param {number} size
330     * @return {number}
331     */
332    applyConstraints: function(size)
333    {
334        const minSize = 20;
335        size = Math.max(size, minSize);
336        if (this._totalSize - size < minSize)
337            size = this._totalSize - minSize;
338        return size;
339    },
340
341    wasShown: function()
342    {
343        this._updateLayout();
344    },
345
346    onResize: function()
347    {
348        this._updateTotalSize();
349    },
350
351    /**
352     * @param {Event} event
353     * @return {boolean}
354     */
355    _startResizerDragging: function(event)
356    {
357        if (!this._resizable)
358            return false;
359
360        this._dragOffset = (this._secondIsSidebar ? this._totalSize - this._sidebarSize : this._sidebarSize) - (this._isVertical ? event.pageX : event.pageY);
361        return true;
362    },
363
364    /**
365     * @param {Event} event
366     */
367    _resizerDragging: function(event)
368    {
369        var newOffset = (this._isVertical ? event.pageX : event.pageY) + this._dragOffset;
370        var newSize = (this._secondIsSidebar ? this._totalSize - newOffset : newOffset);
371        this.setSidebarSize(newSize);
372        event.preventDefault();
373    },
374
375    /**
376     * @param {Event} event
377     */
378    _endResizerDragging: function(event)
379    {
380        delete this._dragOffset;
381    },
382
383    /**
384     * @param {Element} resizerElement
385     */
386    installResizer: function(resizerElement)
387    {
388        resizerElement.addEventListener("mousedown", this._onDragStart.bind(this), false);
389    },
390
391    /**
392     *
393     * @param {Event} event
394     */
395    _onDragStart: function(event)
396    {
397        WebInspector._elementDragStart(this._startResizerDragging.bind(this), this._resizerDragging.bind(this), this._endResizerDragging.bind(this), this._isVertical ? "ew-resize" : "ns-resize", event);
398    },
399
400    /**
401     * @return {WebInspector.Setting}
402     */
403    _sizeSetting: function()
404    {
405        if (!this._sidebarSizeSettingName)
406            return null;
407
408        var settingName = this._sidebarSizeSettingName + (this._isVertical ? "" : "H");
409        if (!WebInspector.settings[settingName])
410            WebInspector.settings[settingName] = WebInspector.settings.createSetting(settingName, undefined);
411
412        return WebInspector.settings[settingName];
413    },
414
415    /**
416     * @return {number}
417     */
418    _lastSidebarSize: function()
419    {
420        var sizeSetting = this._sizeSetting();
421        var size = sizeSetting ? sizeSetting.get() : 0;
422        if (!size)
423             size = this._isVertical ? this._savedSidebarWidth : this._savedSidebarHeight;
424        if (this._useFraction)
425            size *= this._totalSize;
426        return size;
427    },
428
429    /**
430     * @param {number} size
431     */
432    _saveSidebarSize: function(size)
433    {
434        if (this._useFraction)
435            size /= this._totalSize;
436
437        if (this._isVertical)
438            this._savedSidebarWidth = size;
439        else
440            this._savedSidebarHeight = size;
441
442        var sizeSetting = this._sizeSetting();
443        if (sizeSetting)
444            sizeSetting.set(size);
445    },
446
447    __proto__: WebInspector.View.prototype
448}
449