UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

640 lines (510 loc) 17.9 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Fabian Jakobs (fjakobs) ************************************************************************ */ /** * The ScrollArea provides a container widget with on demand scroll bars * if the content size exceeds the size of the container. * * @childControl pane {qx.ui.core.scroll.ScrollPane} pane which holds the content to scroll * @childControl scrollbar-x {qx.ui.core.scroll.ScrollBar?qx.ui.core.scroll.NativeScrollBar} horizontal scrollbar * @childControl scrollbar-y {qx.ui.core.scroll.ScrollBar?qx.ui.core.scroll.NativeScrollBar} vertical scrollbar * @childControl corner {qx.ui.core.Widget} corner where no scrollbar is shown */ qx.Class.define("qx.ui.core.scroll.AbstractScrollArea", { extend : qx.ui.core.Widget, include : [ qx.ui.core.scroll.MScrollBarFactory, qx.ui.core.scroll.MRoll, qx.ui.core.MDragDropScrolling ], type : "abstract", /* ***************************************************************************** STATICS ***************************************************************************** */ statics : { /** * The default width which is used for the width of the scroll bar if * overlaid. */ DEFAULT_SCROLLBAR_WIDTH : 14 }, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { this.base(arguments); if (qx.core.Environment.get("os.scrollBarOverlayed")) { // use a plain canvas to overlay the scroll bars this._setLayout(new qx.ui.layout.Canvas()); } else { // Create 'fixed' grid layout var grid = new qx.ui.layout.Grid(); grid.setColumnFlex(0, 1); grid.setRowFlex(0, 1); this._setLayout(grid); } // since the scroll container disregards the min size of the scrollbars // we have to set the min size of the scroll area to ensure that the // scrollbars always have an usable size. var size = qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH * 2 + 14; this.set({minHeight: size, minWidth: size}); // Roll listener for scrolling this._addRollHandling(); }, events : { /** Fired as soon as the scroll animation in X direction ends. */ scrollAnimationXEnd: 'qx.event.type.Event', /** Fired as soon as the scroll animation in Y direction ends. */ scrollAnimationYEnd: 'qx.event.type.Event' }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { // overridden appearance : { refine : true, init : "scrollarea" }, /** * The policy, when the horizontal scrollbar should be shown. * <ul> * <li><b>auto</b>: Show scrollbar on demand</li> * <li><b>on</b>: Always show the scrollbar</li> * <li><b>off</b>: Never show the scrollbar</li> * </ul> */ scrollbarX : { check : ["auto", "on", "off"], init : "auto", themeable : true, apply : "_computeScrollbars" }, /** * The policy, when the horizontal scrollbar should be shown. * <ul> * <li><b>auto</b>: Show scrollbar on demand</li> * <li><b>on</b>: Always show the scrollbar</li> * <li><b>off</b>: Never show the scrollbar</li> * </ul> */ scrollbarY : { check : ["auto", "on", "off"], init : "auto", themeable : true, apply : "_computeScrollbars" }, /** * Group property, to set the overflow of both scroll bars. */ scrollbar : { group : [ "scrollbarX", "scrollbarY" ] } }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { /* --------------------------------------------------------------------------- CHILD CONTROL SUPPORT --------------------------------------------------------------------------- */ // overridden _createChildControlImpl : function(id, hash) { var control; switch(id) { case "pane": control = new qx.ui.core.scroll.ScrollPane(); control.addListener("update", this._computeScrollbars, this); control.addListener("scrollX", this._onScrollPaneX, this); control.addListener("scrollY", this._onScrollPaneY, this); if (qx.core.Environment.get("os.scrollBarOverlayed")) { this._add(control, {edge: 0}); } else { this._add(control, {row: 0, column: 0}); } break; case "scrollbar-x": control = this._createScrollBar("horizontal"); control.setMinWidth(0); control.exclude(); control.addListener("scroll", this._onScrollBarX, this); control.addListener("changeVisibility", this._onChangeScrollbarXVisibility, this); control.addListener("scrollAnimationEnd", this._onScrollAnimationEnd.bind(this, "X")); if (qx.core.Environment.get("os.scrollBarOverlayed")) { control.setMinHeight(qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH); this._add(control, {bottom: 0, right: 0, left: 0}); } else { this._add(control, {row: 1, column: 0}); } break; case "scrollbar-y": control = this._createScrollBar("vertical"); control.setMinHeight(0); control.exclude(); control.addListener("scroll", this._onScrollBarY, this); control.addListener("changeVisibility", this._onChangeScrollbarYVisibility, this); control.addListener("scrollAnimationEnd", this._onScrollAnimationEnd.bind(this, "Y")); if (qx.core.Environment.get("os.scrollBarOverlayed")) { control.setMinWidth(qx.ui.core.scroll.AbstractScrollArea.DEFAULT_SCROLLBAR_WIDTH); this._add(control, {right: 0, bottom: 0, top: 0}); } else { this._add(control, {row: 0, column: 1}); } break; case "corner": control = new qx.ui.core.Widget(); control.setWidth(0); control.setHeight(0); control.exclude(); if (!qx.core.Environment.get("os.scrollBarOverlayed")) { // only add for non overlayed scroll bars this._add(control, {row: 1, column: 1}); } break; } return control || this.base(arguments, id); }, /* --------------------------------------------------------------------------- PANE SIZE --------------------------------------------------------------------------- */ /** * Returns the dimensions of the pane. * * @return {Map|null} The pane dimension in pixel. Contains * the keys <code>width</code> and <code>height</code>. */ getPaneSize : function() { return this.getChildControl("pane").getInnerSize(); }, /* --------------------------------------------------------------------------- ITEM LOCATION SUPPORT --------------------------------------------------------------------------- */ /** * Returns the top offset of the given item in relation to the * inner height of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Top offset */ getItemTop : function(item) { return this.getChildControl("pane").getItemTop(item); }, /** * Returns the top offset of the end of the given item in relation to the * inner height of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Top offset */ getItemBottom : function(item) { return this.getChildControl("pane").getItemBottom(item); }, /** * Returns the left offset of the given item in relation to the * inner width of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Top offset */ getItemLeft : function(item) { return this.getChildControl("pane").getItemLeft(item); }, /** * Returns the left offset of the end of the given item in relation to the * inner width of this widget. * * @param item {qx.ui.core.Widget} Item to query * @return {Integer} Right offset */ getItemRight : function(item) { return this.getChildControl("pane").getItemRight(item); }, /* --------------------------------------------------------------------------- SCROLL SUPPORT --------------------------------------------------------------------------- */ /** * Scrolls the element's content to the given left coordinate * * @param value {Integer} The vertical position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollToX : function(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-x").scrollTo(value, duration); }, /** * Scrolls the element's content by the given left offset * * @param value {Integer} The vertical position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollByX : function(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-x").scrollBy(value, duration); }, /** * Returns the scroll left position of the content * * @return {Integer} Horizontal scroll position */ getScrollX : function() { var scrollbar = this.getChildControl("scrollbar-x", true); return scrollbar ? scrollbar.getPosition() : 0; }, /** * Scrolls the element's content to the given top coordinate * * @param value {Integer} The horizontal position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollToY : function(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-y").scrollTo(value, duration); }, /** * Scrolls the element's content by the given top offset * * @param value {Integer} The horizontal position to scroll to. * @param duration {Number?} The time in milliseconds the scroll to should take. */ scrollByY : function(value, duration) { // First flush queue before scroll qx.ui.core.queue.Manager.flush(); this.getChildControl("scrollbar-y").scrollBy(value, duration); }, /** * Returns the scroll top position of the content * * @return {Integer} Vertical scroll position */ getScrollY : function() { var scrollbar = this.getChildControl("scrollbar-y", true); return scrollbar ? scrollbar.getPosition() : 0; }, /** * In case a scroll animation is currently running in X direction, * it will be stopped. If not, the method does nothing. */ stopScrollAnimationX : function() { var scrollbar = this.getChildControl("scrollbar-x", true); if (scrollbar) { scrollbar.stopScrollAnimation(); } }, /** * In case a scroll animation is currently running in X direction, * it will be stopped. If not, the method does nothing. */ stopScrollAnimationY : function() { var scrollbar = this.getChildControl("scrollbar-y", true); if (scrollbar) { scrollbar.stopScrollAnimation(); } }, /* --------------------------------------------------------------------------- EVENT LISTENERS --------------------------------------------------------------------------- */ /** * Event handler for the scroll animation end event for both scroll bars. * * @param direction {String} Either "X" or "Y". */ _onScrollAnimationEnd : function(direction) { this.fireEvent("scrollAnimation" + direction + "End"); }, /** * Event handler for the scroll event of the horizontal scrollbar * * @param e {qx.event.type.Data} The scroll event object */ _onScrollBarX : function(e) { this.getChildControl("pane").scrollToX(e.getData()); }, /** * Event handler for the scroll event of the vertical scrollbar * * @param e {qx.event.type.Data} The scroll event object */ _onScrollBarY : function(e) { this.getChildControl("pane").scrollToY(e.getData()); }, /** * Event handler for the horizontal scroll event of the pane * * @param e {qx.event.type.Data} The scroll event object */ _onScrollPaneX : function(e) { var scrollbar = this.getChildControl("scrollbar-x"); if (scrollbar) { scrollbar.updatePosition(e.getData()); } }, /** * Event handler for the vertical scroll event of the pane * * @param e {qx.event.type.Data} The scroll event object */ _onScrollPaneY : function(e) { var scrollbar = this.getChildControl("scrollbar-y"); if (scrollbar) { scrollbar.updatePosition(e.getData()); } }, /** * Event handler for visibility changes of horizontal scrollbar. * * @param e {qx.event.type.Event} Property change event */ _onChangeScrollbarXVisibility : function(e) { var showX = this._isChildControlVisible("scrollbar-x"); var showY = this._isChildControlVisible("scrollbar-y"); if (!showX) { this.scrollToX(0); } showX && showY ? this._showChildControl("corner") : this._excludeChildControl("corner"); }, /** * Event handler for visibility changes of horizontal scrollbar. * * @param e {qx.event.type.Event} Property change event */ _onChangeScrollbarYVisibility : function(e) { var showX = this._isChildControlVisible("scrollbar-x"); var showY = this._isChildControlVisible("scrollbar-y"); if (!showY) { this.scrollToY(0); } showX && showY ? this._showChildControl("corner") : this._excludeChildControl("corner"); }, /* --------------------------------------------------------------------------- HELPER METHODS --------------------------------------------------------------------------- */ /** * Computes the visibility state for scrollbars. * */ _computeScrollbars : function() { var pane = this.getChildControl("pane"); var content = pane.getChildren()[0]; if (!content) { this._excludeChildControl("scrollbar-x"); this._excludeChildControl("scrollbar-y"); return; } var innerSize = this.getInnerSize(); var paneSize = pane.getInnerSize(); var scrollSize = pane.getScrollSize(); // if the widget has not yet been rendered, return and try again in the // resize event if (!paneSize || !scrollSize) { return; } var scrollbarX = this.getScrollbarX(); var scrollbarY = this.getScrollbarY(); if (scrollbarX === "auto" && scrollbarY === "auto") { // Check if the container is big enough to show // the full content. var showX = scrollSize.width > innerSize.width; var showY = scrollSize.height > innerSize.height; // Dependency check // We need a special intelligence here when only one // of the autosized axis requires a scrollbar // This scrollbar may then influence the need // for the other one as well. if ((showX || showY) && !(showX && showY)) { if (showX) { showY = scrollSize.height > paneSize.height; } else if (showY) { showX = scrollSize.width > paneSize.width; } } } else { var showX = scrollbarX === "on"; var showY = scrollbarY === "on"; // Check auto values afterwards with already // corrected client dimensions if (scrollSize.width > (showX ? paneSize.width : innerSize.width) && scrollbarX === "auto") { showX = true; } if (scrollSize.height > (showX ? paneSize.height : innerSize.height) && scrollbarY === "auto") { showY = true; } } // Update scrollbars if (showX) { var barX = this.getChildControl("scrollbar-x"); barX.show(); barX.setMaximum(Math.max(0, scrollSize.width - paneSize.width)); barX.setKnobFactor((scrollSize.width === 0) ? 0 : paneSize.width / scrollSize.width); } else { this._excludeChildControl("scrollbar-x"); } if (showY) { var barY = this.getChildControl("scrollbar-y"); barY.show(); barY.setMaximum(Math.max(0, scrollSize.height - paneSize.height)); barY.setKnobFactor((scrollSize.height === 0) ? 0 : paneSize.height / scrollSize.height); } else { this._excludeChildControl("scrollbar-y"); } } } });