UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

667 lines (561 loc) 19.8 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) * Andreas Ecker (ecker) * Martin Wittemann (martinwittemann) * Jonathan Weiß (jonathan_rass) ************************************************************************ */ /** * The Toolbar class is the main part of the toolbar widget. * * It can handle added {@link Button}s, {@link CheckBox}es, {@link RadioButton}s * and {@link Separator}s in its {@link #add} method. The {@link #addSpacer} method * adds a spacer at the current toolbar position. This means that the widgets * added after the method call of {@link #addSpacer} are aligned to the right of * the toolbar. * * For more details on the documentation of the toolbar widget, take a look at the * documentation of the {@link qx.ui.toolbar}-Package. */ qx.Class.define("qx.ui.toolbar.ToolBar", { extend : qx.ui.core.Widget, include : qx.ui.core.MChildrenHandling, /* ***************************************************************************** CONSTRUCTOR ***************************************************************************** */ construct : function() { this.base(arguments); // add needed layout this._setLayout(new qx.ui.layout.HBox()); // initialize the overflow handling this.__removedItems = []; this.__removePriority = []; }, /* ***************************************************************************** PROPERTIES ***************************************************************************** */ properties : { /** Appearance of the widget */ appearance : { refine : true, init : "toolbar" }, /** Holds the currently open menu (when the toolbar is used for menus) */ openMenu : { check : "qx.ui.menu.Menu", event : "changeOpenMenu", nullable : true }, /** Whether icons, labels, both or none should be shown. */ show : { init : "both", check : [ "both", "label", "icon" ], inheritable : true, apply : "_applyShow", event : "changeShow" }, /** The spacing between every child of the toolbar */ spacing : { nullable : true, check : "Integer", themeable : true, apply : "_applySpacing" }, /** * Widget which will be shown if at least one toolbar item is hidden. * Keep in mind to add this widget to the toolbar before you set it as * indicator! */ overflowIndicator : { check : "qx.ui.core.Widget", nullable : true, apply : "_applyOverflowIndicator" }, /** Enables the overflow handling which automatically removes items.*/ overflowHandling : { init : false, check : "Boolean", apply : "_applyOverflowHandling" } }, /* ***************************************************************************** EVENTS ***************************************************************************** */ events : { /** Fired if an item will be hidden by the {@link #overflowHandling}.*/ "hideItem" : "qx.event.type.Data", /** Fired if an item will be shown by the {@link #overflowHandling}.*/ "showItem" : "qx.event.type.Data" }, /* ***************************************************************************** MEMBERS ***************************************************************************** */ members : { /* --------------------------------------------------------------------------- OVERFLOW HANDLING --------------------------------------------------------------------------- */ __removedItems : null, __removePriority : null, // overridden _computeSizeHint : function() { // get the original hint var hint = this.base(arguments); if (true && this.getOverflowHandling()) { var minWidth = 0; // if an overflow widget is given, use its width + spacing as min width var overflowWidget = this.getOverflowIndicator(); if (overflowWidget) { minWidth = overflowWidget.getSizeHint().width + this.getSpacing(); } // reset the minWidth because we reduce the count of elements hint.minWidth = minWidth; } return hint; }, /** * Resize event handler. * * @param e {qx.event.type.Data} The resize event. */ _onResize : function(e) { this._recalculateOverflow(e.getData().width); }, /** * Responsible for calculation the overflow based on the available width. * * @param width {Integer?null} The available width. * @param requiredWidth {Integer?null} The required width for the widget * if available. */ _recalculateOverflow : function(width, requiredWidth) { // do nothing if overflow handling is not enabled if (!this.getOverflowHandling()) { return; } // get all required sizes requiredWidth = requiredWidth || this.getSizeHint().width; var overflowWidget = this.getOverflowIndicator(); var overflowWidgetWidth = 0; if (overflowWidget) { overflowWidgetWidth = overflowWidget.getSizeHint().width; } if (width == undefined && this.getBounds() != null) { width = this.getBounds().width; } // if we still don't have a width, than we are not added to a parent if (width == undefined) { // we should ignore it in that case return; } // if we have not enough space if (width < requiredWidth) { do { // get the next child var childToHide = this._getNextToHide(); // if there is no child to hide, just do nothing if (!childToHide) { return; } // get margins or spacing var margins = childToHide.getMarginLeft() + childToHide.getMarginRight(); margins = Math.max(margins, this.getSpacing()); var childWidth = childToHide.getSizeHint().width + margins; this.__hideChild(childToHide); // new width is the requiredWidth - the removed childs width requiredWidth -= childWidth; // show the overflowWidgetWidth if (overflowWidget && overflowWidget.getVisibility() != "visible") { overflowWidget.setVisibility("visible"); // if we need to add the overflow indicator, we need to add its width requiredWidth += overflowWidgetWidth; // add spacing or margins var overflowWidgetMargins = overflowWidget.getMarginLeft() + overflowWidget.getMarginRight(); requiredWidth += Math.max(overflowWidgetMargins, this.getSpacing()); } } while (requiredWidth > width); // if we can possibly show something } else if (this.__removedItems.length > 0) { do { var removedChild = this.__removedItems[0]; // if we have something we can show if (removedChild) { // get the margins or spacing var margins = removedChild.getMarginLeft() + removedChild.getMarginRight(); margins = Math.max(margins, this.getSpacing()); // check if the element has been rendered before [BUG #4542] if (removedChild.getContentElement().getDomElement() == null) { // if not, apply the decorator element because it can change the // width of the child with padding e.g. removedChild.syncAppearance(); // also invalidate the layout cache to trigger size hint // recalculation removedChild.invalidateLayoutCache(); } var removedChildWidth = removedChild.getSizeHint().width; // check if it fits in in case its the last child to replace var fits = false; // if we can remove the overflow widget if its available if (this.__removedItems.length == 1 && overflowWidgetWidth > 0) { var addedMargin = margins - this.getSpacing(); var wouldRequiredWidth = requiredWidth - overflowWidgetWidth + removedChildWidth + addedMargin; fits = width > wouldRequiredWidth; } // if it just fits in || it fits in when we remove the overflow widget if (width > requiredWidth + removedChildWidth + margins || fits) { this.__showChild(removedChild); requiredWidth += removedChildWidth; // check if we need to remove the overflow widget if (overflowWidget && this.__removedItems.length == 0) { overflowWidget.setVisibility("excluded"); } } else { return; } } } while (width >= requiredWidth && this.__removedItems.length > 0); } }, /** * Helper to show a toolbar item. * * @param child {qx.ui.core.Widget} The widget to show. */ __showChild : function(child) { child.setVisibility("visible"); this.__removedItems.shift(); this.fireDataEvent("showItem", child); }, /** * Helper to exclude a toolbar item. * * @param child {qx.ui.core.Widget} The widget to exclude. */ __hideChild : function(child) { // ignore the call if no child is given if (!child) { return; } this.__removedItems.unshift(child); child.setVisibility("excluded"); this.fireDataEvent("hideItem", child); }, /** * Responsible for returning the next item to remove. In It checks the * priorities added by {@link #setRemovePriority}. If all priorized widgets * already excluded, it takes the widget added at last. * * @return {qx.ui.core.Widget|null} The widget which should be removed next. * If null is returned, no widget is available to remove. */ _getNextToHide : function() { // get the elements by priority for (var i = this.__removePriority.length - 1; i >= 0; i--) { var item = this.__removePriority[i]; // maybe a priority is left out and spacers don't have the visibility if (item && item.getVisibility && item.getVisibility() == "visible") { return item; } }; // if there is non found by priority, check all available widgets var children = this._getChildren(); for (var i = children.length -1; i >= 0; i--) { var child = children[i]; // ignore the overflow widget if (child == this.getOverflowIndicator()) { continue; } // spacer don't have the visibility if (child.getVisibility && child.getVisibility() == "visible") { return child; } }; }, /** * The removal of the toolbar items is priority based. You can change these * priorities with this method. The higher a priority, the earlier it will * be excluded. Remember to use every priority only once! If you want * override an already set priority, use the override parameter. * Keep in mind to only use already added items. * * @param item {qx.ui.core.Widget} The item to give the priority. * @param priority {Integer} The priority, higher means removed earlier. * @param override {Boolean} true, if the priority should be overridden. */ setRemovePriority : function(item, priority, override) { // security check for overriding priorities if (!override && this.__removePriority[priority] != undefined) { throw new Error("Priority already in use!"); } this.__removePriority[priority] = item; }, // property apply _applyOverflowHandling : function(value, old) { // invalidate the own and the parents layout cache because the size hint changes this.invalidateLayoutCache(); var parent = this.getLayoutParent(); if (parent) { parent.invalidateLayoutCache(); } // recalculate if possible var bounds = this.getBounds(); if (bounds && bounds.width) { this._recalculateOverflow(bounds.width); } // if the handling has been enabled if (value) { // add the resize listener this.addListener("resize", this._onResize, this); // if the handles has been disabled } else { this.removeListener("resize", this._onResize, this); // set the overflow indicator to excluded var overflowIndicator = this.getOverflowIndicator(); if (overflowIndicator) { overflowIndicator.setVisibility("excluded"); } // set all buttons back to visible for (var i = 0; i < this.__removedItems.length; i++) { this.__removedItems[i].setVisibility("visible"); }; // reset the removed items this.__removedItems = []; } }, // property apply _applyOverflowIndicator : function(value, old) { if (old) { this._remove(old); } if (value) { // check if its a child of the toolbar if (this._indexOf(value) == -1) { throw new Error("Widget must be child of the toolbar."); } // hide the widget value.setVisibility("excluded"); } }, /* --------------------------------------------------------------------------- MENU OPEN --------------------------------------------------------------------------- */ __allowMenuOpenHover : false, /** * Indicate if a menu could be opened on hover or not. * * @internal * @param value {Boolean} <code>true</code> if a menu could be opened, * <code>false</code> otherwise. */ _setAllowMenuOpenHover : function(value) { this.__allowMenuOpenHover = value; }, /** * Return if a menu could be opened on hover or not. * * @internal * @return {Boolean} <code>true</code> if a menu could be opened, * <code>false</code> otherwise. */ _isAllowMenuOpenHover : function () { return this.__allowMenuOpenHover; }, /* --------------------------------------------------------------------------- PROPERTY APPLY ROUTINES --------------------------------------------------------------------------- */ // property apply _applySpacing : function(value, old) { var layout = this._getLayout(); value == null ? layout.resetSpacing() : layout.setSpacing(value); }, // property apply _applyShow : function(value) { var children = this._getChildren(); for (var i=0; i < children.length; i++) { if (children[i].setShow) { children[i].setShow(value); } }; }, /* --------------------------------------------------------------------------- CHILD HANDLING --------------------------------------------------------------------------- */ // overridden _add : function(child, options) { this.base(arguments, child, options); // sync the show property (bug #6743) - but only if show wasn't explicitly set for the child (bug #6823) if (child.setShow && !qx.util.PropertyUtil.getUserValue(child, "show")) { child.setShow(this.getShow()); } var newWidth = this.getSizeHint().width + child.getSizeHint().width + 2 * this.getSpacing(); this._recalculateOverflow(null, newWidth); }, // overridden _addAt : function(child, index, options) { this.base(arguments, child, index, options); // sync the show property (bug #6743) - but only if show wasn't explicitly set for the child (bug #6823) if (child.setShow && !qx.util.PropertyUtil.getUserValue(child, "show")) { child.setShow(this.getShow()); } var newWidth = this.getSizeHint().width + child.getSizeHint().width + 2 * this.getSpacing(); this._recalculateOverflow(null, newWidth); }, // overridden _addBefore : function(child, before, options) { this.base(arguments, child, before, options); // sync the show property (bug #6743) - but only if show wasn't explicitly set for the child (bug #6823) if (child.setShow && !qx.util.PropertyUtil.getUserValue(child, "show")) { child.setShow(this.getShow()); } var newWidth = this.getSizeHint().width + child.getSizeHint().width + 2 * this.getSpacing(); this._recalculateOverflow(null, newWidth); }, // overridden _addAfter : function(child, after, options) { this.base(arguments, child, after, options); // sync the show property (bug #6743) - but only if show wasn't explicitly set for the child (bug #6823) if (child.setShow && !qx.util.PropertyUtil.getUserValue(child, "show")) { child.setShow(this.getShow()); } var newWidth = this.getSizeHint().width + child.getSizeHint().width + 2 * this.getSpacing(); this._recalculateOverflow(null, newWidth); }, // overridden _remove : function(child) { this.base(arguments, child); var newWidth = this.getSizeHint().width - child.getSizeHint().width - 2 * this.getSpacing(); this._recalculateOverflow(null, newWidth); }, // overridden _removeAt : function(index) { var child = this._getChildren()[index]; this.base(arguments, index); var newWidth = this.getSizeHint().width - child.getSizeHint().width - 2 * this.getSpacing(); this._recalculateOverflow(null, newWidth); return child; }, // overridden _removeAll : function() { var children = this.base(arguments); this._recalculateOverflow(null, 0); return children; }, /* --------------------------------------------------------------------------- UTILITIES --------------------------------------------------------------------------- */ /** * Add a spacer to the toolbar. The spacer has a flex * value of one and will stretch to the available space. * * @return {qx.ui.core.Spacer} The newly added spacer object. A reference * to the spacer is needed to remove this spacer from the layout. */ addSpacer : function() { var spacer = new qx.ui.core.Spacer; this._add(spacer, {flex:1}); return spacer; }, /** * Adds a separator to the toolbar. */ addSeparator : function() { this.add(new qx.ui.toolbar.Separator); }, /** * Returns all nested buttons which contains a menu to show. This is mainly * used for keyboard support. * * @return {Array} List of all menu buttons */ getMenuButtons : function() { var children = this.getChildren(); var buttons = []; var child; for (var i=0, l=children.length; i<l; i++) { child = children[i]; if (child instanceof qx.ui.menubar.Button) { buttons.push(child); } else if (child instanceof qx.ui.toolbar.Part) { buttons.push.apply(buttons, child.getMenuButtons()); } } return buttons; } }, destruct : function() { if (this.hasListener("resize")) { this.removeListener("resize", this._onResize, this); } } });