package org.ovirt.engine.ui.webadmin.widget.main; import org.ovirt.engine.ui.common.system.ClientStorage; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.CssResource; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.LayoutPanel; import com.google.gwt.user.client.ui.PushButton; import com.google.gwt.user.client.ui.SplitLayoutPanel; import com.google.gwt.user.client.ui.Widget; public class TabbedSplitLayoutPanel extends SplitLayoutPanel { /** * Style sheet interface. */ public interface TabbedSplitLayoutCss extends CssResource { String sliderButton(); } /** * Tabbed Split Layout panel resources interface. */ public interface TabbedSplitLayoutResources extends ClientBundle { @Source("org/ovirt/engine/ui/common/css/TabbedSplitLayout.css") TabbedSplitLayoutCss taggedSplitLayoutCss(); @Source("org/ovirt/engine/ui/webadmin/images/collapse_splitter.png") ImageResource collapeSplitterButton(); @Source("org/ovirt/engine/ui/webadmin/images/expand_splitter.png") ImageResource expandSplitterButton(); } private static final String WEST_SPLITTER_KEY = "MAIN_WEST_SPLITTER_WIDTH"; //$NON-NLS-1$ private static final String WEST_SPLITTER_OLD_KEY = "MAIN_WEST_SPLITTER_OLD_WIDTH"; //$NON-NLS-1$ private static final Double DEFAULT_STACK_PANEL_WIDTH = 235.0; private static final int STOP_WAIT_TIME = 250; /** * Tabbed Split Layout panel resources. */ private static final TabbedSplitLayoutResources SPLIT_LAYOUT_RESOURCES = GWT.create(TabbedSplitLayoutResources.class); /** * The style. */ private final TabbedSplitLayoutCss style; /** * Collapse button. */ private final PushButton collapseLeft; /** * Expand button. */ private final PushButton expandLeft; /** * {@code ScheduleCommand} that forces a layout when resizing the panel. */ private ScheduledCommand layoutCommand; /** * Inserted WEST panel, that will contain the collapse button. */ private LayoutPanel westPanel = null; /** * Inserted CENTER panel, that will contain the expand button. */ private LayoutPanel centerPanel = null; /** * Client storage to store the current west panel width in. */ final ClientStorage clientStorage; /** * Constructor * @param splitterSize Size width of the splitter bar. */ public TabbedSplitLayoutPanel(int splitterSize, ClientStorage storage) { super(splitterSize); clientStorage = storage; style = SPLIT_LAYOUT_RESOURCES.taggedSplitLayoutCss(); style.ensureInjected(); collapseLeft = createButton(SPLIT_LAYOUT_RESOURCES.collapeSplitterButton()); expandLeft = createButton(SPLIT_LAYOUT_RESOURCES.expandSplitterButton()); } /** * If the direction is WEST and the west panel has not been inserted yet, then create a new {@code LayoutPanel}, * and insert the passed in {@code Widget} into that panel, and insert the panel into the {@code SplitLayoutPanel}. * If the direction is CENTER and the center panel has not been inserted yet, then create a new {@code LayoutPanel}, * and insert the passed in {@code Widget} into that panel, and insert the panel into the {@code SplitLayoutPanel}. */ @Override public void insert(Widget child, Direction direction, double size, Widget before) { Widget insertedWidget = child; if (direction == Direction.WEST) { if (westPanel == null) { westPanel = new LayoutPanel() { @Override public void onResize() { super.onResize(); LayoutData layout = (LayoutData) westPanel.getLayoutData(); if (layout.size == 0) { setStoredSplitterWidth(WEST_SPLITTER_OLD_KEY, layout.oldSize); } else { final double currentSize = layout.size; Timer comparisonTimer = new Timer() { @Override public void run() { //Check if the size is still the same, if so, then set the old size to be the //new size. LayoutData layout = (LayoutData) westPanel.getLayoutData(); if ((int)layout.size == (int)currentSize) { //Cast to int so comparison works. layout.oldSize = layout.size; } } }; comparisonTimer.schedule(STOP_WAIT_TIME); //run in 250ms. } } }; collapseLeft.setVisible(true); westPanel.add(collapseLeft); size = getStoredStackPanelWidth(WEST_SPLITTER_KEY); Scheduler.get().scheduleDeferred(() -> { LayoutData layout = (LayoutData) westPanel.getLayoutData(); layout.oldSize = getStoredStackPanelWidth(WEST_SPLITTER_OLD_KEY); }); } westPanel.add(child); insertedWidget = westPanel; } else if (direction == Direction.CENTER) { if (centerPanel == null) { centerPanel = new LayoutPanel(); centerPanel.add(expandLeft); } centerPanel.add(child); insertedWidget = centerPanel; } super.insert(insertedWidget, direction, size, before); } /** * Create a new expand/collapse button. * @param imageResource The {@code ImageResource} to use to style the button. * @return A new {@code PushButton}. */ private PushButton createButton(ImageResource imageResource) { PushButton result = new PushButton(new Image(imageResource), (ClickEvent event) -> toggleVisibleWestPanel()); result.setVisible(false); result.addStyleName(style.sliderButton()); result.addStyleName("tslp_sliderButton_pfly_fix"); //$NON-NLS-1$ return result; } /** * Toggle the west panel. If it is visible, hide it and vice versa. */ private void toggleVisibleWestPanel() { LayoutData layout = (LayoutData) westPanel.getLayoutData(); if (layout.size == 0) { double size = layout.oldSize; if (size == 0) { //The old size is 0, so we should restore the default size. size = getStoredStackPanelWidth(WEST_SPLITTER_OLD_KEY); } // Restore the old size. setWestPanelSize(size); } else { //Collapse to size 0. layout.oldSize = layout.size; setStoredSplitterWidth(WEST_SPLITTER_OLD_KEY, layout.oldSize); setWestPanelSize(0); } } /** * Set the west panel size. * @param size The new size. */ private void setWestPanelSize(double size) { LayoutData layout = (LayoutData) westPanel.getLayoutData(); if (size == layout.size) { return; } layout.size = size; // Defer actually updating the layout, so that if we receive many // mouse events before layout/paint occurs, we'll only update once. if (layoutCommand == null) { layoutCommand = () -> { layoutCommand = null; forceLayout(); }; Scheduler.get().scheduleDeferred(layoutCommand); } } /** * After load, set the the position of the collapse and expand buttons. Including visibility. */ @Override public void onLoad() { super.onLoad(); Scheduler.get().scheduleDeferred(() -> { determineButtonVisiblity(); positionLeftCollapseButton(); }); } @Override public void onResize() { super.onResize(); double currentWidth = westPanel.getOffsetWidth(); setStoredSplitterWidth(WEST_SPLITTER_KEY, currentWidth); determineButtonVisiblity(); positionLeftCollapseButton(); } /** * Determine if the expand or collapse button should be visible. */ private void determineButtonVisiblity() { if (westPanel.getOffsetWidth() == 0) { // Completely collapsed. expandLeft.setVisible(true); collapseLeft.setVisible(false); } else { expandLeft.setVisible(false); collapseLeft.setVisible(true); } } /** * Position the collapse button to be proper in relation to the splitter bar. */ private void positionLeftCollapseButton() { collapseLeft.getElement().getStyle().setLeft( westPanel.getOffsetWidth() - collapseLeft.getOffsetWidth(), Unit.PX); } @Override public void setWidgetToggleDisplayAllowed(Widget widget, boolean allowed) { super.setWidgetToggleDisplayAllowed(getActualWidget(widget), allowed); } @Override public void setWidgetSize(Widget widget, double size) { super.setWidgetSize(getActualWidget(widget), size); } @Override public Double getWidgetSize(Widget widget) { return super.getWidgetSize(getActualWidget(widget)); } @Override public void setWidgetMinSize(Widget widget, int minSize) { super.setWidgetMinSize(getActualWidget(widget), minSize); } @Override public void setWidgetSnapClosedSize(Widget widget, int snapClosedSize) { super.setWidgetSnapClosedSize(getActualWidget(widget), snapClosedSize); } /** * Determine if the passed in widget is part of the west or center panel, if so return the center or west panel * as the widget, so the super class will handle the operation properly. * @param widget The {@code Widget} to use to determine if we should return the panel or the widget. * @return A {@code Widget} that is either the original, or a panel that contains the widget. */ private Widget getActualWidget(Widget widget) { Widget checkedWidget = widget; if (westPanel != null && widget.getParent() == westPanel) { checkedWidget = westPanel; } else if (centerPanel != null && widget.getParent() == centerPanel) { checkedWidget = centerPanel; } return checkedWidget; } /** * Retrieve the stored stack panel width. * @return The west stack panel width as a {@code double} */ private double getStoredStackPanelWidth(String key) { String widthString = clientStorage.getLocalItem(key); double width = DEFAULT_STACK_PANEL_WIDTH; //In case there was no stored width, use the default. try { if (widthString != null) { width = Double.valueOf(widthString); } } catch (NumberFormatException nfe) { //Do nothing. } return width; } /** * Store the current width in the {@code ClientStorage} of the browser so we can recall it when we log in. * @param width The current width in pixels. */ private void setStoredSplitterWidth(String key, Double width) { clientStorage.setLocalItem(key, width.toString()); } }