/******************************************************************************* * Copyright (c) 2011, 2016 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Christian Walther (Indel AG) - Bug 399458: Fix layout overlap in line-wrapped trim bar * Christian Walther (Indel AG) - Bug 389012: Fix division by zero in TrimBarLayout * Marc-Andre Laperle (Ericsson) - Bug 466233: Toolbar items are wrongly rendered into a "drop-down" * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654 * Simon Scholz <simon.scholz@vogella.com> - Bug 476386 * Patrik Suzzi <psuzzi@gmail.com> - Bug 507404 *******************************************************************************/ package org.eclipse.e4.ui.workbench.renderers.swt; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.e4.ui.internal.workbench.swt.AbstractPartRenderer; import org.eclipse.e4.ui.model.application.ui.MUIElement; import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; import org.eclipse.e4.ui.model.application.ui.basic.MTrimElement; import org.eclipse.e4.ui.model.application.ui.menu.MToolBar; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Layout; import org.eclipse.swt.widgets.ToolBar; /** * This class manages the {@link Layout} of the applications' TrimBar. */ public class TrimBarLayout extends Layout { class TrimLine { Map<Control, Point> sizeMap = new HashMap<>(); List<Control> ctrls = new ArrayList<>(); int spacerCount = 0; int extraSpace = 0; int major = 0; int minor = 0; public void addControl(Control ctrl) { Point ctrlSize = computeSize(ctrl); int ctrlMajor = horizontal ? ctrlSize.x : ctrlSize.y; int ctrlMinor = horizontal ? ctrlSize.y : ctrlSize.x; major += ctrlMajor; if (ctrlMinor > minor) minor = ctrlMinor; sizeMap.put(ctrl, ctrlSize); ctrls.add(ctrl); if (isSpacer(ctrl)) spacerCount++; } public void mergeSegment(TrimLine segment) { sizeMap.putAll(segment.sizeMap); ctrls.addAll(segment.ctrls); major += segment.major; if (segment.minor > minor) minor = segment.minor; spacerCount += segment.spacerCount; } } private List<TrimLine> lines = new ArrayList<>(); /** * When applied as a tag to a tool control (e.g. LayoutModifierToolControl), * it causes the tool control to grab all available space to its right * within its containing {@link MTrimBar}. Items after a spacer will be * aligned to the right side of the {@link MTrimBar}. */ public final static String SPACER = "stretch"; //$NON-NLS-1$ /** * When applied as a tag to a tool control (e.g. LayoutModifierToolControl) * within a {@link MTrimBar}, it causes the tool control to be glued to the * items to its immediate left and right so that in case the * {@link MTrimBar} must be wrapped, then the glued items stay together. */ public final static String GLUE = "glue"; //$NON-NLS-1$ private final boolean horizontal; public int marginLeft = 0; public int marginRight = 0; public int marginTop = 0; public int marginBottom = 0; public int wrapSpacing = 0; /** * Constructor for the TrimBarLayout. * * @param horizontal * specifies the orientation of the {@link Layout}. If true, items * are placed in a horizontal orientation, otherwise in a * vertical orientation. */ public TrimBarLayout(boolean horizontal) { this.horizontal = horizontal; } @Override protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { if (flushCache) { // Clear the current cache lines.clear(); } // First, hide any empty toolbars Object bar = composite.getData(AbstractPartRenderer.OWNING_ME); if (bar instanceof MTrimBar) { ((MTrimBar) bar).getChildren().forEach(this::hideManagedTB); } int totalMajor = horizontal ? wHint - (marginLeft + marginRight) : hHint - (marginTop + marginBottom); int totalMinor = 0; int spaceLeft = totalMajor; TrimLine curLine = new TrimLine(); Control[] kids = composite.getChildren(); for (int i = 0; i < kids.length; i++) { Control ctrl = kids[i]; // GLUE Handling; gather any glued controls up into a 'segment' TrimLine segment = new TrimLine(); segment.addControl(ctrl); while (i < (kids.length - 2) && isGlue(kids[i + 1])) { segment.addControl(kids[i + 1]); segment.addControl(kids[i + 2]); i += 2; } // Do we have enough space ? if (segment.major <= spaceLeft) { // Yes, add the segment to the current line curLine.mergeSegment(segment); spaceLeft -= segment.major; } else { // No, cache the current line and start a new one curLine.extraSpace = spaceLeft; lines.add(curLine); totalMinor += curLine.minor; curLine = segment; spaceLeft = totalMajor - segment.major; } } if (curLine.ctrls.size() > 0) { curLine.extraSpace = spaceLeft; lines.add(curLine); totalMinor += curLine.minor; } // Adjust the 'totalMinor' to account for the margins int totalWrapSpacing = (lines.size() - 1) * wrapSpacing; totalMinor += horizontal ? (marginTop + marginBottom) + totalWrapSpacing : (marginLeft + marginRight) + totalWrapSpacing; Point calcSize = horizontal ? new Point(wHint, totalMinor) : new Point(totalMinor, hHint); return calcSize; } private Point computeSize(Control ctrl) { Point ctrlSize = ctrl.computeSize(SWT.DEFAULT, SWT.DEFAULT); // Hack! the StatusLine doesn't compute a useable size if (isStatusLine(ctrl)) { ctrlSize.x = 375; ctrlSize.y = 26; } return ctrlSize; } /** * This is a HACK ! Due to compatibility restrictions we have the case where * we <b>must</b> leave 'empty' toolbars in the trim. This code detects this * particular scenario and hides any TB's of this type... * * @param te * The proposed trim element * @return <code>true</code> iff this element represents an empty managed * TB. */ private boolean hideManagedTB(MTrimElement te) { if (!(te instanceof MToolBar) || !(te.getRenderer() instanceof ToolBarManagerRenderer)) return false; if (!(te.getWidget() instanceof Composite)) return false; Composite teComp = (Composite) te.getWidget(); Control[] kids = teComp.getChildren(); if (kids.length != 1 || !(kids[0] instanceof ToolBar)) return false; boolean barVisible = ((ToolBar) kids[0]).getItemCount() > 0; // HACK! The trim dragging code uses the visible attribute as well // this is a local 'lock' to prevent the layout from messing with it if (!te.getTags().contains("LockVisibility")) { //$NON-NLS-1$ te.setVisible(barVisible); } return !barVisible; } @Override protected void layout(Composite composite, boolean flushCache) { if (flushCache) { // Clear the current cache lines.clear(); } Rectangle bounds = composite.getBounds(); // offset the rectangle to allow for the margins bounds.x = marginLeft; bounds.y = marginTop; bounds.width -= (marginLeft + marginRight); bounds.height -= (marginTop + marginBottom); // If we were called directly we need to fill the caches if (lines.size() == 0) { if (horizontal) computeSize(composite, bounds.width, SWT.DEFAULT, true); else computeSize(composite, SWT.DEFAULT, bounds.height, true); } if (lines.size() == 0) return; for (TrimLine curLine : lines) { tileLine(curLine, bounds); if (horizontal) bounds.y += curLine.minor + wrapSpacing; else bounds.x += curLine.minor + wrapSpacing; } } private void tileLine(TrimLine curLine, Rectangle bounds) { int curX = bounds.x; int curY = bounds.y; int remainingExtraSpace = curLine.extraSpace; int remainingSpacerCount = curLine.spacerCount; for (Control ctrl : curLine.ctrls) { if (ctrl.isDisposed()) { continue; } Point ctrlSize = curLine.sizeMap.get(ctrl); int ctrlWidth = ctrlSize.x; int ctrlHeight = ctrlSize.y; boolean zeroSize = ctrlWidth == 0 && ctrlHeight == 0; // If its a 'spacer' then add any available 'extra' space to it if (isSpacer(ctrl)) { int extra = remainingExtraSpace / remainingSpacerCount; if (horizontal) { ctrlWidth += extra; // leave out 4 pixels at the bottom to avoid overlapping the // 1px bottom border of the toolbar (bug 389941) ctrl.setBounds(curX, curY, ctrlWidth, curLine.minor - 4); } else { ctrlHeight += extra; ctrl.setBounds(curX, curY, curLine.minor, ctrlHeight); } zeroSize = false; remainingExtraSpace -= extra; remainingSpacerCount--; } if (horizontal) { int offset = (curLine.minor - ctrlHeight) / 2; if (!isSpacer(ctrl)) { if (!zeroSize) ctrl.setBounds(curX, curY + offset, ctrlWidth, ctrlHeight); else ctrl.setBounds(curX, curY, 0, 0); } curX += ctrlWidth; } else { int offset = (curLine.minor - ctrlWidth) / 2; ctrl.setBounds(curX + offset, curY, ctrlWidth, ctrlHeight); curY += ctrlHeight; } } } private boolean isSpacer(Control ctrl) { MUIElement element = (MUIElement) ctrl.getData(AbstractPartRenderer.OWNING_ME); if (element != null && element.getTags().contains(SPACER)) return true; return false; } private boolean isGlue(Control ctrl) { MUIElement element = (MUIElement) ctrl.getData(AbstractPartRenderer.OWNING_ME); if (element != null && element.getTags().contains(GLUE)) return true; return false; } private boolean isStatusLine(Control ctrl) { MUIElement element = (MUIElement) ctrl.getData(AbstractPartRenderer.OWNING_ME); if (element != null && element.getElementId() != null && element.getElementId().equals("org.eclipse.ui.StatusLine")) //$NON-NLS-1$ return true; return false; } /** * Get a Control at a certain position from the TrimBar. * * @param trimComp * TrimBar {@link Composite} * @param trimPos * position in the TrimBar * * @return the {@link Control} at the given trimPos {@link Point} or * <code>null</code>, if the given Point does not fit into any * TrimBar-Control's bounds. */ public Control ctrlFromPoint(Composite trimComp, Point trimPos) { if (trimComp == null || trimComp.isDisposed() || lines == null || lines.size() == 0) return null; Control[] kids = trimComp.getChildren(); for (Control kid : kids) { if (kid.isDisposed()) continue; if (kid.getBounds().contains(trimPos)) return kid; } return null; } }