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 {boolean} secondIsSidebar
34 * @param {string=} settingName
35 * @param {number=} defaultSidebarWidth
36 * @param {number=} defaultSidebarHeight
37 * @param {boolean=} constraintsInDip
38 */
39WebInspector.SplitView = function(isVertical, secondIsSidebar, settingName, defaultSidebarWidth, defaultSidebarHeight, constraintsInDip)
40{
41    WebInspector.View.call(this);
42
43    this.element.classList.add("split-view");
44
45    this._mainView = new WebInspector.VBox();
46    this._mainElement = this._mainView.element;
47    this._mainElement.className = "split-view-contents split-view-main vbox"; // Override
48
49    this._sidebarView = new WebInspector.VBox();
50    this._sidebarElement = this._sidebarView.element;
51    this._sidebarElement.className = "split-view-contents split-view-sidebar vbox"; // Override
52
53    this._resizerElement = this.element.createChild("div", "split-view-resizer");
54    this._resizerElement.createChild("div", "split-view-resizer-border");
55    if (secondIsSidebar) {
56        this._mainView.show(this.element);
57        this._sidebarView.show(this.element);
58    } else {
59        this._sidebarView.show(this.element);
60        this._mainView.show(this.element);
61    }
62
63    this._resizerWidget = new WebInspector.ResizerWidget();
64    this._resizerWidget.setEnabled(true);
65    this._resizerWidget.addEventListener(WebInspector.ResizerWidget.Events.ResizeStart, this._onResizeStart, this);
66    this._resizerWidget.addEventListener(WebInspector.ResizerWidget.Events.ResizeUpdate, this._onResizeUpdate, this);
67    this._resizerWidget.addEventListener(WebInspector.ResizerWidget.Events.ResizeEnd, this._onResizeEnd, this);
68
69    this._defaultSidebarWidth = defaultSidebarWidth || 200;
70    this._defaultSidebarHeight = defaultSidebarHeight || this._defaultSidebarWidth;
71    this._constraintsInDip = !!constraintsInDip;
72    this._settingName = settingName;
73
74    this.setSecondIsSidebar(secondIsSidebar);
75
76    this._innerSetVertical(isVertical);
77    this._showMode = WebInspector.SplitView.ShowMode.Both;
78
79    // Should be called after isVertical has the right value.
80    this.installResizer(this._resizerElement);
81}
82
83/** @typedef {{showMode: string, size: number}} */
84WebInspector.SplitView.SettingForOrientation;
85
86WebInspector.SplitView.ShowMode = {
87    Both: "Both",
88    OnlyMain: "OnlyMain",
89    OnlySidebar: "OnlySidebar"
90}
91
92WebInspector.SplitView.Events = {
93    SidebarSizeChanged: "SidebarSizeChanged",
94    ShowModeChanged: "ShowModeChanged"
95}
96
97WebInspector.SplitView.MinPadding = 20;
98
99WebInspector.SplitView.prototype = {
100    /**
101     * @return {boolean}
102     */
103    isVertical: function()
104    {
105        return this._isVertical;
106    },
107
108    /**
109     * @param {boolean} isVertical
110     */
111    setVertical: function(isVertical)
112    {
113        if (this._isVertical === isVertical)
114            return;
115
116        this._innerSetVertical(isVertical);
117
118        if (this.isShowing())
119            this._updateLayout();
120    },
121
122    /**
123     * @param {boolean} isVertical
124     */
125    _innerSetVertical: function(isVertical)
126    {
127        this.element.classList.remove(this._isVertical ? "hbox" : "vbox");
128        this._isVertical = isVertical;
129        this.element.classList.add(this._isVertical ? "hbox" : "vbox");
130        delete this._resizerElementSize;
131        this._sidebarSize = -1;
132        this._restoreSidebarSizeFromSettings();
133        if (this._shouldSaveShowMode)
134            this._restoreAndApplyShowModeFromSettings();
135        this._updateShowHideSidebarButton();
136        // FIXME: reverse SplitView.isVertical meaning.
137        this._resizerWidget.setVertical(!isVertical);
138        this.invalidateConstraints();
139    },
140
141    /**
142     * @param {boolean=} animate
143     */
144    _updateLayout: function(animate)
145    {
146        delete this._totalSize; // Lazy update.
147        delete this._totalSizeOtherDimension;
148
149        // Remove properties that might affect total size calculation.
150        this._mainElement.style.removeProperty("width");
151        this._mainElement.style.removeProperty("height");
152        this._sidebarElement.style.removeProperty("width");
153        this._sidebarElement.style.removeProperty("height");
154
155        this._innerSetSidebarSize(this._preferredSidebarSize(), !!animate);
156    },
157
158    /**
159     * @return {!Element}
160     */
161    mainElement: function()
162    {
163        return this._mainElement;
164    },
165
166    /**
167     * @return {!Element}
168     */
169    sidebarElement: function()
170    {
171        return this._sidebarElement;
172    },
173
174    /**
175     * @return {boolean}
176     */
177    isSidebarSecond: function()
178    {
179        return this._secondIsSidebar;
180    },
181
182    enableShowModeSaving: function()
183    {
184        this._shouldSaveShowMode = true;
185        this._restoreAndApplyShowModeFromSettings();
186    },
187
188    /**
189     * @return {string}
190     */
191    showMode: function()
192    {
193        return this._showMode;
194    },
195
196    /**
197     * @param {boolean} secondIsSidebar
198     */
199    setSecondIsSidebar: function(secondIsSidebar)
200    {
201        this._mainElement.classList.toggle("split-view-contents-first", secondIsSidebar);
202        this._mainElement.classList.toggle("split-view-contents-second", !secondIsSidebar);
203        this._sidebarElement.classList.toggle("split-view-contents-first", !secondIsSidebar);
204        this._sidebarElement.classList.toggle("split-view-contents-second", secondIsSidebar);
205
206        // Make sure second is last in the children array.
207        if (secondIsSidebar) {
208            if (this._sidebarElement.parentElement && this._sidebarElement.nextSibling)
209                this.element.appendChild(this._sidebarElement);
210        } else {
211            if (this._mainElement.parentElement && this._mainElement.nextSibling)
212                this.element.appendChild(this._mainElement);
213        }
214
215        this._secondIsSidebar = secondIsSidebar;
216    },
217
218    /**
219     * @return {?string}
220     */
221    sidebarSide: function()
222    {
223        if (this._showMode !== WebInspector.SplitView.ShowMode.Both)
224            return null;
225        return this._isVertical ?
226            (this._secondIsSidebar ? "right" : "left") :
227            (this._secondIsSidebar ? "bottom" : "top");
228    },
229
230    /**
231     * @return {number}
232     */
233    preferredSidebarSize: function()
234    {
235        return this._preferredSidebarSize();
236    },
237
238    /**
239     * @return {!Element}
240     */
241    resizerElement: function()
242    {
243        return this._resizerElement;
244    },
245
246    /**
247     * @param {boolean=} animate
248     */
249    hideMain: function(animate)
250    {
251        this._showOnly(this._sidebarView, this._mainView, animate);
252        this._updateShowMode(WebInspector.SplitView.ShowMode.OnlySidebar);
253    },
254
255    /**
256     * @param {boolean=} animate
257     */
258    hideSidebar: function(animate)
259    {
260        this._showOnly(this._mainView, this._sidebarView, animate);
261        this._updateShowMode(WebInspector.SplitView.ShowMode.OnlyMain);
262    },
263
264    /**
265     * @override
266     */
267    detachChildViews: function()
268    {
269        this._mainView.detachChildViews();
270        this._sidebarView.detachChildViews();
271    },
272
273    /**
274     * @param {!WebInspector.View} sideToShow
275     * @param {!WebInspector.View} sideToHide
276     * @param {boolean=} animate
277     */
278    _showOnly: function(sideToShow, sideToHide, animate)
279    {
280        this._cancelAnimation();
281
282        /**
283         * @this {WebInspector.SplitView}
284         */
285        function callback()
286        {
287            sideToShow.show(this.element);
288            sideToHide.detach();
289            sideToShow.element.classList.add("maximized");
290            sideToHide.element.classList.remove("maximized");
291            this._resizerElement.classList.add("hidden");
292            this._removeAllLayoutProperties();
293        }
294
295        if (animate) {
296            this._animate(true, callback.bind(this));
297        } else {
298            callback.call(this);
299            this.doResize();
300        }
301
302        this._sidebarSize = -1;
303        this.setResizable(false);
304    },
305
306    _removeAllLayoutProperties: function()
307    {
308        this._sidebarElement.style.removeProperty("flexBasis");
309
310        this._mainElement.style.removeProperty("width");
311        this._mainElement.style.removeProperty("height");
312        this._sidebarElement.style.removeProperty("width");
313        this._sidebarElement.style.removeProperty("height");
314
315        this._resizerElement.style.removeProperty("left");
316        this._resizerElement.style.removeProperty("right");
317        this._resizerElement.style.removeProperty("top");
318        this._resizerElement.style.removeProperty("bottom");
319
320        this._resizerElement.style.removeProperty("margin-left");
321        this._resizerElement.style.removeProperty("margin-right");
322        this._resizerElement.style.removeProperty("margin-top");
323        this._resizerElement.style.removeProperty("margin-bottom");
324    },
325
326    /**
327     * @param {boolean=} animate
328     */
329    showBoth: function(animate)
330    {
331       if (this._showMode === WebInspector.SplitView.ShowMode.Both)
332            animate = false;
333
334        this._cancelAnimation();
335        this._mainElement.classList.remove("maximized");
336        this._sidebarElement.classList.remove("maximized");
337        this._resizerElement.classList.remove("hidden");
338
339        this._mainView.show(this.element);
340        this._sidebarView.show(this.element);
341        // Order views in DOM properly.
342        this.setSecondIsSidebar(this._secondIsSidebar);
343
344        this._sidebarSize = -1;
345        this.setResizable(true);
346        this._updateShowMode(WebInspector.SplitView.ShowMode.Both);
347        this._updateLayout(animate);
348    },
349
350    /**
351     * @param {boolean} resizable
352     */
353    setResizable: function(resizable)
354    {
355        this._resizerWidget.setEnabled(resizable);
356    },
357
358    /**
359     * @return {boolean}
360     */
361    isResizable: function()
362    {
363        return this._resizerWidget.isEnabled();
364    },
365
366    /**
367     * @param {number} size
368     */
369    setSidebarSize: function(size)
370    {
371        size *= WebInspector.zoomManager.zoomFactor();
372        this._savedSidebarSize = size;
373        this._saveSetting();
374        this._innerSetSidebarSize(size, false, true);
375    },
376
377    /**
378     * @return {number}
379     */
380    sidebarSize: function()
381    {
382        var size = Math.max(0, this._sidebarSize);
383        return size / WebInspector.zoomManager.zoomFactor();
384    },
385
386    /**
387     * Returns total size in DIP.
388     * @return {number}
389     */
390    _totalSizeDIP: function()
391    {
392        if (!this._totalSize) {
393            this._totalSize = this._isVertical ? this.element.offsetWidth : this.element.offsetHeight;
394            this._totalSizeOtherDimension = this._isVertical ? this.element.offsetHeight : this.element.offsetWidth;
395        }
396        return this._totalSize * WebInspector.zoomManager.zoomFactor();
397    },
398
399    /**
400     * @param {string} showMode
401     */
402    _updateShowMode: function(showMode)
403    {
404        this._showMode = showMode;
405        this._saveShowModeToSettings();
406        this._updateShowHideSidebarButton();
407        this.dispatchEventToListeners(WebInspector.SplitView.Events.ShowModeChanged, showMode);
408        this.invalidateConstraints();
409    },
410
411    /**
412     * @param {number} size
413     * @param {boolean} animate
414     * @param {boolean=} userAction
415     */
416    _innerSetSidebarSize: function(size, animate, userAction)
417    {
418        if (this._showMode !== WebInspector.SplitView.ShowMode.Both || !this.isShowing())
419            return;
420
421        size = this._applyConstraints(size, userAction);
422        if (this._sidebarSize === size)
423            return;
424
425        if (!this._resizerElementSize)
426            this._resizerElementSize = this._isVertical ? this._resizerElement.offsetWidth : this._resizerElement.offsetHeight;
427
428        // Invalidate layout below.
429
430        this._removeAllLayoutProperties();
431
432        // this._totalSize is available below since we successfully applied constraints.
433        var sidebarSizeValue = (size / WebInspector.zoomManager.zoomFactor()) + "px";
434        var mainSizeValue = (this._totalSize - size / WebInspector.zoomManager.zoomFactor()) + "px";
435        this.sidebarElement().style.flexBasis = sidebarSizeValue;
436
437        // Make both sides relayout boundaries.
438        if (this._isVertical) {
439            this._sidebarElement.style.width = sidebarSizeValue;
440            this._mainElement.style.width = mainSizeValue;
441            this._sidebarElement.style.height = this._totalSizeOtherDimension + "px";
442            this._mainElement.style.height = this._totalSizeOtherDimension + "px";
443        } else {
444            this._sidebarElement.style.height = sidebarSizeValue;
445            this._mainElement.style.height = mainSizeValue;
446            this._sidebarElement.style.width = this._totalSizeOtherDimension + "px";
447            this._mainElement.style.width = this._totalSizeOtherDimension + "px";
448        }
449
450        // Position resizer.
451        if (this._isVertical) {
452            if (this._secondIsSidebar) {
453                this._resizerElement.style.right = sidebarSizeValue;
454                this._resizerElement.style.marginRight = -this._resizerElementSize / 2 + "px";
455            } else {
456                this._resizerElement.style.left = sidebarSizeValue;
457                this._resizerElement.style.marginLeft = -this._resizerElementSize / 2 + "px";
458            }
459        } else {
460            if (this._secondIsSidebar) {
461                this._resizerElement.style.bottom = sidebarSizeValue;
462                this._resizerElement.style.marginBottom = -this._resizerElementSize / 2 + "px";
463            } else {
464                this._resizerElement.style.top = sidebarSizeValue;
465                this._resizerElement.style.marginTop = -this._resizerElementSize / 2 + "px";
466            }
467        }
468
469        this._sidebarSize = size;
470
471        // Force layout.
472
473        if (animate) {
474            this._animate(false);
475        } else {
476            // No need to recalculate this._sidebarSize and this._totalSize again.
477            this.doResize();
478            this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize());
479        }
480    },
481
482    /**
483     * @param {boolean} reverse
484     * @param {function()=} callback
485     */
486    _animate: function(reverse, callback)
487    {
488        var animationTime = 50;
489        this._animationCallback = callback;
490
491        var animatedMarginPropertyName;
492        if (this._isVertical)
493            animatedMarginPropertyName = this._secondIsSidebar ? "margin-right" : "margin-left";
494        else
495            animatedMarginPropertyName = this._secondIsSidebar ? "margin-bottom" : "margin-top";
496
497        var zoomFactor = WebInspector.zoomManager.zoomFactor();
498        var marginFrom = reverse ? "0" : "-" + (this._sidebarSize / zoomFactor) + "px";
499        var marginTo = reverse ? "-" + (this._sidebarSize / zoomFactor) + "px" : "0";
500
501        // This order of things is important.
502        // 1. Resize main element early and force layout.
503        this.element.style.setProperty(animatedMarginPropertyName, marginFrom);
504        if (!reverse) {
505            suppressUnused(this._mainElement.offsetWidth);
506            suppressUnused(this._sidebarElement.offsetWidth);
507        }
508
509        // 2. Issue onresize to the sidebar element, its size won't change.
510        if (!reverse)
511            this._sidebarView.doResize();
512
513        // 3. Configure and run animation
514        this.element.style.setProperty("transition", animatedMarginPropertyName + " " + animationTime + "ms linear");
515
516        var boundAnimationFrame;
517        var startTime;
518        /**
519         * @this {WebInspector.SplitView}
520         */
521        function animationFrame()
522        {
523            delete this._animationFrameHandle;
524
525            if (!startTime) {
526                // Kick animation on first frame.
527                this.element.style.setProperty(animatedMarginPropertyName, marginTo);
528                startTime = window.performance.now();
529            } else if (window.performance.now() < startTime + animationTime) {
530                // Process regular animation frame.
531                this._mainView.doResize();
532            } else {
533                // Complete animation.
534                this._cancelAnimation();
535                this._mainView.doResize();
536                this.dispatchEventToListeners(WebInspector.SplitView.Events.SidebarSizeChanged, this.sidebarSize());
537                return;
538            }
539            this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame);
540        }
541        boundAnimationFrame = animationFrame.bind(this);
542        this._animationFrameHandle = window.requestAnimationFrame(boundAnimationFrame);
543    },
544
545    _cancelAnimation: function()
546    {
547        this.element.style.removeProperty("margin-top");
548        this.element.style.removeProperty("margin-right");
549        this.element.style.removeProperty("margin-bottom");
550        this.element.style.removeProperty("margin-left");
551        this.element.style.removeProperty("transition");
552
553        if (this._animationFrameHandle) {
554            window.cancelAnimationFrame(this._animationFrameHandle);
555            delete this._animationFrameHandle;
556        }
557        if (this._animationCallback) {
558            this._animationCallback();
559            delete this._animationCallback;
560        }
561    },
562
563    /**
564     * @param {number} sidebarSize
565     * @param {boolean=} userAction
566     * @return {number}
567     */
568    _applyConstraints: function(sidebarSize, userAction)
569    {
570        var totalSize = this._totalSizeDIP();
571        var zoomFactor = this._constraintsInDip ? 1 : WebInspector.zoomManager.zoomFactor();
572
573        var constraints = this._sidebarView.constraints();
574        var minSidebarSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height;
575        if (!minSidebarSize)
576            minSidebarSize = WebInspector.SplitView.MinPadding;
577        minSidebarSize *= zoomFactor;
578
579        var preferredSidebarSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height;
580        if (!preferredSidebarSize)
581            preferredSidebarSize = WebInspector.SplitView.MinPadding;
582        preferredSidebarSize *= zoomFactor;
583        // Allow sidebar to be less than preferred by explicit user action.
584        if (sidebarSize < preferredSidebarSize)
585            preferredSidebarSize = Math.max(sidebarSize, minSidebarSize);
586
587        constraints = this._mainView.constraints();
588        var minMainSize = this.isVertical() ? constraints.minimum.width : constraints.minimum.height;
589        if (!minMainSize)
590            minMainSize = WebInspector.SplitView.MinPadding;
591        minMainSize *= zoomFactor;
592
593        var preferredMainSize = this.isVertical() ? constraints.preferred.width : constraints.preferred.height;
594        if (!preferredMainSize)
595            preferredMainSize = WebInspector.SplitView.MinPadding;
596        preferredMainSize *= zoomFactor;
597        var savedMainSize = this.isVertical() ? this._savedVerticalMainSize : this._savedHorizontalMainSize;
598        if (typeof savedMainSize !== "undefined")
599            preferredMainSize = Math.min(preferredMainSize, savedMainSize * zoomFactor);
600        if (userAction)
601            preferredMainSize = minMainSize;
602
603        // Enough space for preferred.
604        var totalPreferred = preferredMainSize + preferredSidebarSize;
605        if (totalPreferred <= totalSize)
606            return Number.constrain(sidebarSize, preferredSidebarSize, totalSize - preferredMainSize);
607
608        // Enough space for minimum.
609        if (minMainSize + minSidebarSize <= totalSize) {
610            var delta = totalPreferred - totalSize;
611            var sidebarDelta = delta * preferredSidebarSize / totalPreferred;
612            sidebarSize = preferredSidebarSize - sidebarDelta;
613            return Number.constrain(sidebarSize, minSidebarSize, totalSize - minMainSize);
614        }
615
616        // Not enough space even for minimum sizes.
617        return Math.max(0, totalSize - minMainSize);
618    },
619
620    wasShown: function()
621    {
622        this._forceUpdateLayout();
623        WebInspector.zoomManager.addEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this);
624    },
625
626    willHide: function()
627    {
628        WebInspector.zoomManager.removeEventListener(WebInspector.ZoomManager.Events.ZoomChanged, this._onZoomChanged, this);
629    },
630
631    onResize: function()
632    {
633        this._updateLayout();
634    },
635
636    onLayout: function()
637    {
638        this._updateLayout();
639    },
640
641    /**
642     * @return {!Constraints}
643     */
644    calculateConstraints: function()
645    {
646        if (this._showMode === WebInspector.SplitView.ShowMode.OnlyMain)
647            return this._mainView.constraints();
648        if (this._showMode === WebInspector.SplitView.ShowMode.OnlySidebar)
649            return this._sidebarView.constraints();
650
651        var mainConstraints = this._mainView.constraints();
652        var sidebarConstraints = this._sidebarView.constraints();
653        var min = WebInspector.SplitView.MinPadding;
654        if (this._isVertical) {
655            mainConstraints = mainConstraints.widthToMax(min);
656            sidebarConstraints = sidebarConstraints.widthToMax(min);
657            return mainConstraints.addWidth(sidebarConstraints).heightToMax(sidebarConstraints);
658        } else {
659            mainConstraints = mainConstraints.heightToMax(min);
660            sidebarConstraints = sidebarConstraints.heightToMax(min);
661            return mainConstraints.widthToMax(sidebarConstraints).addHeight(sidebarConstraints);
662        }
663    },
664
665    /**
666     * @param {!WebInspector.Event} event
667     */
668    _onResizeStart: function(event)
669    {
670        this._resizeStartSize = this._sidebarSize;
671    },
672
673    /**
674     * @param {!WebInspector.Event} event
675     */
676    _onResizeUpdate: function(event)
677    {
678        var cssOffset = event.data.currentPosition - event.data.startPosition;
679        var dipOffset = cssOffset * WebInspector.zoomManager.zoomFactor();
680        var newSize = this._secondIsSidebar ? this._resizeStartSize - dipOffset : this._resizeStartSize + dipOffset;
681        var constrainedSize = this._applyConstraints(newSize, true);
682        this._savedSidebarSize = constrainedSize;
683        this._saveSetting();
684        this._innerSetSidebarSize(constrainedSize, false, true);
685        if (this.isVertical())
686            this._savedVerticalMainSize = this._totalSizeDIP() - this._sidebarSize;
687        else
688            this._savedHorizontalMainSize = this._totalSizeDIP() - this._sidebarSize;
689    },
690
691    /**
692     * @param {!WebInspector.Event} event
693     */
694    _onResizeEnd: function(event)
695    {
696        delete this._resizeStartSize;
697    },
698
699    hideDefaultResizer: function()
700    {
701        this.uninstallResizer(this._resizerElement);
702    },
703
704    /**
705     * @param {!Element} resizerElement
706     */
707    installResizer: function(resizerElement)
708    {
709        this._resizerWidget.addElement(resizerElement);
710    },
711
712    /**
713     * @param {!Element} resizerElement
714     */
715    uninstallResizer: function(resizerElement)
716    {
717        this._resizerWidget.removeElement(resizerElement);
718    },
719
720    /**
721     * @return {boolean}
722     */
723    hasCustomResizer: function()
724    {
725        var elements = this._resizerWidget.elements();
726        return elements.length > 1 || (elements.length == 1 && elements[0] !== this._resizerElement);
727    },
728
729    /**
730     * @param {!Element} resizer
731     * @param {boolean} on
732     */
733    toggleResizer: function(resizer, on)
734    {
735        if (on)
736            this.installResizer(resizer);
737        else
738            this.uninstallResizer(resizer);
739    },
740
741    /**
742     * @return {?WebInspector.Setting}
743     */
744    _setting: function()
745    {
746        if (!this._settingName)
747            return null;
748
749        if (!WebInspector.settings[this._settingName])
750            WebInspector.settings[this._settingName] = WebInspector.settings.createSetting(this._settingName, {});
751
752        return WebInspector.settings[this._settingName];
753    },
754
755    /**
756     * @return {?WebInspector.SplitView.SettingForOrientation}
757     */
758    _settingForOrientation: function()
759    {
760        var state = this._setting() ? this._setting().get() : {};
761        return this._isVertical ? state.vertical : state.horizontal;
762    },
763
764    /**
765     * @return {number}
766     */
767    _preferredSidebarSize: function()
768    {
769        var size = this._savedSidebarSize;
770        if (!size) {
771            size = this._isVertical ? this._defaultSidebarWidth : this._defaultSidebarHeight;
772            // If we have default value in percents, calculate it on first use.
773            if (0 < size && size < 1)
774                size *= this._totalSizeDIP();
775        }
776        return size;
777    },
778
779    _restoreSidebarSizeFromSettings: function()
780    {
781        var settingForOrientation = this._settingForOrientation();
782        this._savedSidebarSize = settingForOrientation ? settingForOrientation.size : 0;
783    },
784
785    _restoreAndApplyShowModeFromSettings: function()
786    {
787        var orientationState = this._settingForOrientation();
788        this._savedShowMode = orientationState ? orientationState.showMode : WebInspector.SplitView.ShowMode.Both;
789        this._showMode = this._savedShowMode;
790
791        switch (this._savedShowMode) {
792        case WebInspector.SplitView.ShowMode.Both:
793            this.showBoth();
794            break;
795        case WebInspector.SplitView.ShowMode.OnlyMain:
796            this.hideSidebar();
797            break;
798        case WebInspector.SplitView.ShowMode.OnlySidebar:
799            this.hideMain();
800            break;
801        }
802    },
803
804    _saveShowModeToSettings: function()
805    {
806        this._savedShowMode = this._showMode;
807        this._saveSetting();
808    },
809
810    _saveSetting: function()
811    {
812        var setting = this._setting();
813        if (!setting)
814            return;
815        var state = setting.get();
816        var orientationState = (this._isVertical ? state.vertical : state.horizontal) || {};
817
818        orientationState.size = this._savedSidebarSize;
819        if (this._shouldSaveShowMode)
820            orientationState.showMode = this._savedShowMode;
821
822        if (this._isVertical)
823            state.vertical = orientationState;
824        else
825            state.horizontal = orientationState;
826        setting.set(state);
827    },
828
829    _forceUpdateLayout: function()
830    {
831        // Force layout even if sidebar size does not change.
832        this._sidebarSize = -1;
833        this._updateLayout();
834    },
835
836    /**
837     * @param {!WebInspector.Event} event
838     */
839    _onZoomChanged: function(event)
840    {
841        this._forceUpdateLayout();
842    },
843
844    /**
845     * @param {string} title
846     * @param {string} className
847     * @return {!WebInspector.StatusBarButton}
848     */
849    createShowHideSidebarButton: function(title, className)
850    {
851        console.assert(this.isVertical(), "Buttons for split view with horizontal split are not supported yet.");
852
853        this._showHideSidebarButtonTitle = WebInspector.UIString(title);
854        this._showHideSidebarButton = new WebInspector.StatusBarButton("", "sidebar-show-hide-button " + className, 3);
855        this._showHideSidebarButton.addEventListener("click", buttonClicked.bind(this));
856        this._updateShowHideSidebarButton();
857
858        /**
859         * @this {WebInspector.SplitView}
860         * @param {!WebInspector.Event} event
861         */
862        function buttonClicked(event)
863        {
864            if (this._showMode !== WebInspector.SplitView.ShowMode.Both)
865                this.showBoth(true);
866            else
867                this.hideSidebar(true);
868        }
869
870        return this._showHideSidebarButton;
871    },
872
873    _updateShowHideSidebarButton: function()
874    {
875        if (!this._showHideSidebarButton)
876            return;
877        var sidebarHidden = this._showMode === WebInspector.SplitView.ShowMode.OnlyMain;
878        this._showHideSidebarButton.state = sidebarHidden ? "show" : "hide";
879        this._showHideSidebarButton.element.classList.toggle("top-sidebar-show-hide-button", !this.isVertical() && !this.isSidebarSecond());
880        this._showHideSidebarButton.element.classList.toggle("right-sidebar-show-hide-button", this.isVertical() && this.isSidebarSecond());
881        this._showHideSidebarButton.element.classList.toggle("bottom-sidebar-show-hide-button", !this.isVertical() && this.isSidebarSecond());
882        this._showHideSidebarButton.element.classList.toggle("left-sidebar-show-hide-button", this.isVertical() && !this.isSidebarSecond());
883        this._showHideSidebarButton.title = sidebarHidden ? WebInspector.UIString("Show %s", this._showHideSidebarButtonTitle) : WebInspector.UIString("Hide %s", this._showHideSidebarButtonTitle);
884    },
885
886    __proto__: WebInspector.View.prototype
887}
888