Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2009 Joseph Pecoraro
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 /**
     31  * @constructor
     32  * @implements {WebInspector.ViewFactory}
     33  * @param {!WebInspector.InspectorView} inspectorView
     34  */
     35 WebInspector.Drawer = function(inspectorView)
     36 {
     37     this._inspectorView = inspectorView;
     38 
     39     this.element = this._inspectorView.devtoolsElement().createChild("div", "drawer");
     40     this.element.style.flexBasis = 0;
     41 
     42     this._savedHeight = 200; // Default.
     43 
     44     this._drawerContentsElement = this.element.createChild("div");
     45     this._drawerContentsElement.id = "drawer-contents";
     46 
     47     this._toggleDrawerButton = new WebInspector.StatusBarButton(WebInspector.UIString("Show drawer."), "console-status-bar-item");
     48     this._toggleDrawerButton.addEventListener("click", this.toggle, this);
     49 
     50     this._viewFactories = [];
     51     this._tabbedPane = new WebInspector.TabbedPane();
     52     this._tabbedPane.closeableTabs = false;
     53     this._tabbedPane.markAsRoot();
     54 
     55     // Register console early for it to be the first in the list.
     56     this.registerView("console", WebInspector.UIString("Console"), this);
     57 
     58     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._updateTabStrip, this);
     59     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
     60     WebInspector.installDragHandle(this._tabbedPane.headerElement(), this._startStatusBarDragging.bind(this), this._statusBarDragging.bind(this), this._endStatusBarDragging.bind(this), "row-resize");
     61     this._tabbedPane.element.createChild("div", "drawer-resizer");
     62     this._showDrawerOnLoadSetting = WebInspector.settings.createSetting("WebInspector.Drawer.showOnLoad", false);
     63     this._lastSelectedViewSetting = WebInspector.settings.createSetting("WebInspector.Drawer.lastSelectedView", "console");
     64 }
     65 
     66 WebInspector.Drawer.prototype = {
     67     /**
     68      * @return {!Element}
     69      */
     70     toggleButtonElement: function()
     71     {
     72         return this._toggleDrawerButton.element;
     73     },
     74 
     75     _constrainHeight: function(height)
     76     {
     77         return Number.constrain(height, Preferences.minConsoleHeight, this._inspectorView.devtoolsElement().offsetHeight - Preferences.minConsoleHeight);
     78     },
     79 
     80     isHiding: function()
     81     {
     82         return this._isHiding;
     83     },
     84 
     85     /**
     86      * @param {string} tabId
     87      * @param {string} title
     88      * @param {!WebInspector.View} view
     89      */
     90     _addView: function(tabId, title, view)
     91     {
     92         if (!this._tabbedPane.hasTab(tabId)) {
     93             this._tabbedPane.appendTab(tabId, title, view,  undefined, false);
     94         } else {
     95             this._tabbedPane.changeTabTitle(tabId, title);
     96             this._tabbedPane.changeTabView(tabId, view);
     97         }
     98     },
     99 
    100     /**
    101      * @param {string} id
    102      * @param {string} title
    103      * @param {!WebInspector.ViewFactory} factory
    104      */
    105     registerView: function(id, title, factory)
    106     {
    107         if (this._tabbedPane.hasTab(id))
    108             this._tabbedPane.closeTab(id);
    109         this._viewFactories[id] = factory;
    110         this._tabbedPane.appendTab(id, title, new WebInspector.View());
    111     },
    112 
    113     /**
    114      * @param {string} id
    115      */
    116     unregisterView: function(id)
    117     {
    118         if (this._tabbedPane.hasTab(id))
    119             this._tabbedPane.closeTab(id);
    120         delete this._viewFactories[id];
    121     },
    122 
    123     /**
    124      * @param {string=} id
    125      * @return {?WebInspector.View}
    126      */
    127     createView: function(id)
    128     {
    129         return WebInspector.panel("console").createView(id);
    130     },
    131 
    132     /**
    133      * @param {string} id
    134      */
    135     closeView: function(id)
    136     {
    137         this._tabbedPane.closeTab(id);
    138     },
    139 
    140     /**
    141      * @param {string} id
    142      * @param {boolean=} immediately
    143      */
    144     showView: function(id, immediately)
    145     {
    146         if (!this._toggleDrawerButton.enabled())
    147             return;
    148         if (this._viewFactories[id])
    149             this._tabbedPane.changeTabView(id, this._viewFactories[id].createView(id));
    150         this._innerShow(immediately);
    151         this._tabbedPane.selectTab(id, true);
    152         this._updateTabStrip();
    153     },
    154 
    155     /**
    156      * @param {string} id
    157      * @param {string} title
    158      * @param {!WebInspector.View} view
    159      */
    160     showCloseableView: function(id, title, view)
    161     {
    162         if (!this._toggleDrawerButton.enabled())
    163             return;
    164         if (!this._tabbedPane.hasTab(id)) {
    165             this._tabbedPane.appendTab(id, title, view, undefined, false, true);
    166         } else {
    167             this._tabbedPane.changeTabView(id, view);
    168             this._tabbedPane.changeTabTitle(id, title);
    169         }
    170         this._innerShow();
    171         this._tabbedPane.selectTab(id, true);
    172         this._updateTabStrip();
    173     },
    174 
    175     /**
    176      * @param {boolean=} immediately
    177      */
    178     show: function(immediately)
    179     {
    180         this.showView(this._tabbedPane.selectedTabId, immediately);
    181     },
    182 
    183     showOnLoadIfNecessary: function()
    184     {
    185         if (this._showDrawerOnLoadSetting.get())
    186             this.showView(this._lastSelectedViewSetting.get(), true);
    187     },
    188 
    189     /**
    190      * @param {boolean=} immediately
    191      */
    192     _innerShow: function(immediately)
    193     {
    194         this._immediatelyFinishAnimation();
    195 
    196         if (this._toggleDrawerButton.toggled)
    197             return;
    198         this._showDrawerOnLoadSetting.set(true);
    199         this._toggleDrawerButton.toggled = true;
    200         this._toggleDrawerButton.title = WebInspector.UIString("Hide drawer.");
    201 
    202         document.body.classList.add("drawer-visible");
    203         this._tabbedPane.show(this._drawerContentsElement);
    204 
    205         var height = this._constrainHeight(this._savedHeight);
    206         var animations = [
    207             {element: this.element, start: {"flex-basis": 23}, end: {"flex-basis": height}},
    208         ];
    209 
    210         /**
    211          * @param {boolean} finished
    212          * @this {WebInspector.Drawer}
    213          */
    214         function animationCallback(finished)
    215         {
    216             if (this._inspectorView.currentPanel())
    217                 this._inspectorView.currentPanel().doResize();
    218             if (!finished)
    219                 return;
    220             this._updateTabStrip();
    221             if (this._visibleView()) {
    222                 // Get console content back
    223                 this._tabbedPane.changeTabView(this._tabbedPane.selectedTabId, this._visibleView());
    224                 if (this._visibleView().afterShow)
    225                     this._visibleView().afterShow();
    226             }
    227             delete this._currentAnimation;
    228         }
    229 
    230         this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(immediately), animationCallback.bind(this));
    231 
    232         if (immediately)
    233             this._currentAnimation.forceComplete();
    234     },
    235 
    236     /**
    237      * @param {boolean=} immediately
    238      */
    239     hide: function(immediately)
    240     {
    241         this._immediatelyFinishAnimation();
    242 
    243         if (!this._toggleDrawerButton.toggled)
    244             return;
    245         this._showDrawerOnLoadSetting.set(false);
    246         this._toggleDrawerButton.toggled = false;
    247         this._toggleDrawerButton.title = WebInspector.UIString("Show console.");
    248 
    249         this._isHiding = true;
    250         this._savedHeight = this.element.offsetHeight;
    251 
    252         WebInspector.restoreFocusFromElement(this.element);
    253 
    254         // Temporarily set properties and classes to mimic the post-animation values so panels
    255         // like Elements in their updateStatusBarItems call will size things to fit the final location.
    256         document.body.classList.remove("drawer-visible");
    257         this._inspectorView.currentPanel().statusBarResized();
    258         document.body.classList.add("drawer-visible");
    259 
    260         var animations = [
    261             {element: this.element, start: {"flex-basis": this.element.offsetHeight }, end: {"flex-basis": 23}},
    262         ];
    263 
    264         /**
    265          * @param {boolean} finished
    266          * @this {WebInspector.Drawer}
    267          */
    268         function animationCallback(finished)
    269         {
    270             var panel = this._inspectorView.currentPanel();
    271             if (!finished) {
    272                 panel.doResize();
    273                 return;
    274             }
    275             this._tabbedPane.detach();
    276             this._drawerContentsElement.removeChildren();
    277             document.body.classList.remove("drawer-visible");
    278             panel.doResize();
    279             delete this._currentAnimation;
    280             delete this._isHiding;
    281         }
    282 
    283         this._currentAnimation = WebInspector.animateStyle(animations, this._animationDuration(immediately), animationCallback.bind(this));
    284 
    285         if (immediately)
    286             this._currentAnimation.forceComplete();
    287     },
    288 
    289     resize: function()
    290     {
    291         if (!this._toggleDrawerButton.toggled)
    292             return;
    293 
    294         this._visibleView().storeScrollPositions();
    295         var height = this._constrainHeight(this.element.offsetHeight);
    296         this.element.style.flexBasis = height + "px";
    297         this._tabbedPane.doResize();
    298     },
    299 
    300     _immediatelyFinishAnimation: function()
    301     {
    302         if (this._currentAnimation)
    303             this._currentAnimation.forceComplete();
    304     },
    305 
    306     /**
    307      * @param {boolean=} immediately
    308      * @return {number}
    309      */
    310     _animationDuration: function(immediately)
    311     {
    312         return immediately ? 0 : 50;
    313     },
    314 
    315     /**
    316      * @return {boolean}
    317      */
    318     _startStatusBarDragging: function(event)
    319     {
    320         if (!this._toggleDrawerButton.toggled || event.target !== this._tabbedPane.headerElement())
    321             return false;
    322 
    323         this._visibleView().storeScrollPositions();
    324         this._statusBarDragOffset = event.pageY - this.element.totalOffsetTop();
    325         return true;
    326     },
    327 
    328     _statusBarDragging: function(event)
    329     {
    330         var height = window.innerHeight - event.pageY + this._statusBarDragOffset;
    331         height = Number.constrain(height, Preferences.minConsoleHeight, this._inspectorView.devtoolsElement().offsetHeight - Preferences.minConsoleHeight);
    332 
    333         this.element.style.flexBasis = height + "px";
    334         if (this._inspectorView.currentPanel())
    335             this._inspectorView.currentPanel().doResize();
    336         this._tabbedPane.doResize();
    337 
    338         event.consume(true);
    339     },
    340 
    341     _endStatusBarDragging: function(event)
    342     {
    343         this._savedHeight = this.element.offsetHeight;
    344         delete this._statusBarDragOffset;
    345 
    346         event.consume();
    347     },
    348 
    349     /**
    350      * @return {!WebInspector.View} view
    351      */
    352     _visibleView: function()
    353     {
    354         return this._tabbedPane.visibleView;
    355     },
    356 
    357     _updateTabStrip: function()
    358     {
    359         this._tabbedPane.onResize();
    360         this._tabbedPane.doResize();
    361     },
    362 
    363     _tabSelected: function()
    364     {
    365         var tabId = this._tabbedPane.selectedTabId;
    366         if (!this._tabbedPane.isTabCloseable(tabId))
    367             this._lastSelectedViewSetting.set(tabId);
    368         if (this._viewFactories[tabId])
    369             this._tabbedPane.changeTabView(tabId, this._viewFactories[tabId].createView(tabId));
    370     },
    371 
    372     toggle: function()
    373     {
    374         if (this._toggleDrawerButton.toggled)
    375             this.hide();
    376         else
    377             this.show();
    378     },
    379 
    380     /**
    381      * @return {boolean}
    382      */
    383     visible: function()
    384     {
    385         return this._toggleDrawerButton.toggled;
    386     },
    387 
    388     /**
    389      * @return {string}
    390      */
    391     selectedViewId: function()
    392     {
    393         return this._tabbedPane.selectedTabId;
    394     }
    395 }
    396