/* * Copyright 2015 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.uberfire.client.util; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.Position; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.HasWidgets; import com.google.gwt.user.client.ui.ProvidesResize; import com.google.gwt.user.client.ui.RequiresResize; import com.google.gwt.user.client.ui.ScrollPanel; import com.google.gwt.user.client.ui.Widget; import org.uberfire.client.workbench.panels.SplitPanel; import org.uberfire.debug.Debug; import org.uberfire.workbench.model.CompassPosition; import org.uberfire.workbench.model.PanelDefinition; import static org.uberfire.plugin.PluginUtil.toInteger; public class Layouts { public static final int DEFAULT_CHILD_SIZE = 100; /** * Sets the CSS on the given widget so it automatically fills the available space, rather than being sized based on * the amount of space required by its contents. This tends to be useful when building a UI that always fills the * available space on the screen, as most desktop application windows do. * <p> * To achieve this, the element is given relative positioning with top and left set to 0px and width and height set * to 100%. This makes the widget fill its nearest ancestor which has relative or absolute positioning. This * technique is compatible with GWT's LayoutPanel system. Note that, like LayoutPanels, this only works if the host * page is in standards mode (has a {@code <!DOCTYPE html>} header). * @param w the widget that should always fill its available space, rather than being sized to fit its contents. */ public static void setToFillParent(Widget w) { Element e = w.getElement(); Style s = e.getStyle(); s.setPosition(Position.RELATIVE); s.setTop(0.0, Unit.PX); s.setLeft(0.0, Unit.PX); s.setWidth(100.0, Unit.PCT); s.setHeight(100.0, Unit.PCT); } /** * Returns a multi-line string detailing layout information about the given widget and each of its ancestors in the * widget tree. * @param w the widget to start at. Null is permitted, and results in this method returning an empty string. * @return information about w and its ancestors, one widget per line. */ public static String getContainmentHierarchy(Widget w) { return getContainmentHierarchy(w, false); } /** * Returns a multi-line string detailing layout information about the given widget and each of its ancestors in the * widget tree, optionally setting debug IDs on each widget to assist in locating them in browser DOM explorer * tools. * @param w the widget to start at. Null is permitted, and results in this method returning an empty string. * @param setDebugIds if true, the element and each of its ancestors will have its ID set to * <code>"containment-parent-<i>depth</i>"</code>, where depth is 0 for the given widget, 1 for * its parent, 2 for its grandparent, and so on. This ID will replace any ID that was previously set on * the element, so it may break some CSS and even javascript functionality. Use with caution. * @return information about w and its ancestors, one widget per line. */ public static String getContainmentHierarchy(Widget w, boolean setDebugIds) { StringBuilder sb = new StringBuilder(); int depth = 0; while (w != null) { if (setDebugIds) { w.getElement().setId("containment-parent-" + depth); } sb.append(" " + depth + " - " + widgetInfo(w)); w = w.getParent(); depth++; } return sb.toString(); } private static String widgetInfo(Widget w) { String widgetInfo; try { String id = w.getElement().getId(); widgetInfo = w.getOffsetWidth() + "x" + w.getOffsetHeight() + " - " + Debug.objectId(w) + (id != null && id.length() > 0 ? " id=" + id : "") + (w instanceof SplitPanel ? " divider at " + ((SplitPanel) w).getFixedWidgetSize() : "") + (w instanceof RequiresResize ? " RequiresResize" : "") + (w instanceof ProvidesResize ? " ProvidesResize" : "") + " position: " + w.getElement().getStyle().getPosition() + "\n"; } catch (Throwable t) { widgetInfo = "?x? - " + Debug.objectId(w) + ": " + t.toString() + "\n"; } return widgetInfo; } /** * Returns a multi-line string detailing layout information about the given widget and each of its descendants in * the widget tree. * @param startAt the widget to start at. Null is permitted. * @return information about w and its descendants, one widget per line. Each line is indented with leading spaces * to illustrate the containment hierarchy. */ public static String getContainedHierarchy(final Widget startAt) { IndentedLineAccumulator result = new IndentedLineAccumulator(); getContainedHierarchyRecursively(startAt, 0, result); return result.toString(); } private static void getContainedHierarchyRecursively(final Widget startAt, int depth, IndentedLineAccumulator result) { if (startAt == null) { result.append(depth, "(null)"); return; } result.append(depth, widgetInfo(startAt)); if (startAt instanceof HasWidgets) { for (Widget child : ((HasWidgets) startAt)) { getContainedHierarchyRecursively(child, depth + 1, result); } } else if (startAt instanceof Composite) { getContainedHierarchyRecursively(extractWidget(((Composite) startAt)), depth + 1, result); } } private static native Widget extractWidget(Composite composite) /*-{ return composite.@com.google.gwt.user.client.ui.Composite::widget; }-*/; /** * Returns the current width or height of the given panel definition. * @param position determines which dimension (width or height) to return. * @param definition the definition to get the size information from. * @return the with if position is EAST or WEST; the height if position is NORTH or SOUTH. If no size is provided by the PanelDefinition the DEFAULT_CHILD_SIZE is used. */ public static int widthOrHeight(CompassPosition position, PanelDefinition definition) { switch (position) { case NORTH: case SOUTH: return heightOrDefault(definition); case EAST: case WEST: return widthOrDefault(definition); default: throw new IllegalArgumentException("Position " + position + " has no horizontal or vertial aspect."); } } public static int heightOrDefault(PanelDefinition def) { Integer height = toInteger(def.getHeightAsInt()); return height == null ? DEFAULT_CHILD_SIZE : height; } public static int widthOrDefault(PanelDefinition def) { Integer width = toInteger(def.getWidthAsInt()); return width == null ? DEFAULT_CHILD_SIZE : width; } /** * Disables the scrolling behaviour of the nearest scrollpanel found in the given widget's containment hierarchy. * <p> * FIXME this is a really horrible workaround! should instead modify UF API to allow PanelDefinition to opt out of having a scroll panel. * The better fix would require changes to: * <ul> * <li>WorkbenchPartPresenter.View * <li>WorkbenchPartView and its mock * <li>The @WorkbenchPanel annotation * <li>The annotation processor code generators and their tests * </ul> * @return true if a scroll panel was found and disabled; false if no scroll panel was found. */ public static boolean disableNearestScrollPanel(Widget w) { while (w != null) { if (w instanceof ScrollPanel) { w.getElement().getStyle().clearOverflow(); w.getElement().getParentElement().getStyle().clearOverflow(); return true; } w = w.getParent(); } return false; } private static class IndentedLineAccumulator { final StringBuilder sb = new StringBuilder(); private void append(int depth, String s) { for (int i = 0; i < depth; i++) { sb.append(" "); } sb.append(s); } @Override public String toString() { return sb.toString(); } } }