| /******************************************************************************* |
| * Copyright (c) 2005, 2006 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 |
| *******************************************************************************/ |
| |
| package org.eclipse.ui.internal.layout; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.ListIterator; |
| |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.CBanner; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.ui.internal.WindowTrimProxy; |
| |
| /** |
| * Represents one Trim Area. |
| * |
| * @since 3.2 |
| */ |
| public class TrimArea { |
| |
| /** |
| * This is a convenience class that caches information for a single 'tiled' |
| * line of trim. |
| * |
| * @since 3.2 |
| * |
| */ |
| private class TrimLine { |
| /** The list of controls in this trim line */ |
| List controls = new ArrayList(); |
| |
| /** In horizontal terms this is the 'height' of the tallest control. */ |
| int minorMax; // Default minimum for empty trim areas |
| |
| /** The number of controls that can 'grow' to use leftover space */ |
| int resizableCount; |
| |
| /** The amount of unused space in the line */ |
| int availableSpace; |
| |
| /** |
| * @param majorHint The total amount of space available to tile into |
| */ |
| public TrimLine(int majorHint) { |
| availableSpace = majorHint; |
| } |
| |
| /** |
| * Add a new control to the current line. |
| * |
| * @param ctrl The control to add |
| * @param minorSize it's size in the minor dimension |
| * @param dragHandle its drag handle (if any) |
| */ |
| void addControl(Control ctrl, int tileLength, int minorSize, Control dragHandle) { |
| if (dragHandle != null) { |
| controls.add(dragHandle); |
| } |
| |
| controls.add(ctrl); |
| |
| // If the control is re-sizable then we use the default size as its 'minimum' size |
| if (getData(ctrl).isResizeable()) { |
| resizableCount++; |
| } |
| |
| // Cache the maximum amount of 'minor' space needed |
| if (minorSize > minorMax) { |
| minorMax = minorSize; |
| } |
| |
| // Adjust the amount of remaining space |
| availableSpace -= tileLength; |
| } |
| |
| /** |
| * Terminates a line by calculating the remaining space and fixing the |
| * minorMax if there was a CBanner in the line. |
| * |
| * @return The total amount of 'minor' space required for this line |
| */ |
| int terminate() { |
| // OK, now if there was a banner in the current line we'll re-compute |
| // its height based on the actual width we'll be displaying it at |
| for (Iterator ctrls = controls.iterator(); ctrls.hasNext();) { |
| Control ctrl = (Control) ctrls.next(); |
| if (ctrl instanceof CBanner) { |
| CBanner banner = (CBanner) ctrl; |
| Point bannerPrefSize = (Point) banner.getData(PREFSIZE_DATA_ID); |
| int realWidth = bannerPrefSize.x + (availableSpace / resizableCount); |
| Point cbSize = banner.computeSize(realWidth, SWT.DEFAULT); |
| banner.setData(PREFSIZE_DATA_ID, new Point(bannerPrefSize.x, cbSize.y)); |
| |
| // Update the minor size if necessary |
| if (cbSize.y > minorMax) |
| minorMax = cbSize.y; |
| } |
| } |
| |
| return minorMax; |
| } |
| } |
| |
| // this is no longer necessary, since every piece of window trim defined |
| // itself as the trim layout data. |
| private static final IWindowTrim defaultData = new WindowTrimProxy(null, null, null, 0, true); |
| |
| private static IWindowTrim getData(Control control) { |
| IWindowTrim data = (IWindowTrim) control.getLayoutData(); |
| if (data == null) { |
| data = defaultData; |
| } |
| |
| return data; |
| } |
| |
| /** Our area ID. */ |
| private int fId; |
| |
| /** An NLS display name. */ |
| private String fDisplayName; |
| |
| /** The last 'tiled' set of trim lines */ |
| private List lines = new ArrayList(); |
| |
| /** Each trimArea is an ordered list of TrimDescriptors. */ |
| private ArrayList fTrim; |
| |
| // layout constants |
| private static final String PREFSIZE_DATA_ID = "prefSize"; //$NON-NLS-1$ |
| private static final int MIN_BANNER_LEFT = 150; |
| private static int TILE_SPACING = 2; |
| private static int LINE_SPACING = 2; |
| |
| private Rectangle curRect = new Rectangle(0,0,0,0); |
| |
| /** |
| * Create the trim area with its ID. |
| * |
| * @param id |
| * @param displayName |
| * the NLS display name |
| */ |
| public TrimArea(int id, String displayName) { |
| fTrim = new ArrayList(); |
| fId = id; |
| fDisplayName = displayName; |
| } |
| |
| /** |
| * return true of the trim area is empty |
| * |
| * @return <code>true</code> |
| */ |
| public boolean isEmpty() { |
| return fTrim.isEmpty(); |
| } |
| |
| /** |
| * @return The rectangle currently occupied by this trim area |
| */ |
| public Rectangle getCurRect() { |
| return curRect; |
| } |
| |
| /** |
| * Return the ordered list of trim for this area. |
| * |
| * @return a List containing IWindowTrim |
| */ |
| public List getTrims() { |
| List trim = new ArrayList(fTrim.size()); |
| Iterator d = fTrim.iterator(); |
| |
| while (d.hasNext()) { |
| TrimDescriptor desc = (TrimDescriptor) d.next(); |
| trim.add(desc.getTrim()); |
| } |
| return trim; |
| } |
| |
| /** |
| * Return the ordered list of trim descriptors for this area. |
| * |
| * @return a List containing TrimDescriptor |
| */ |
| public List getDescriptors() { |
| return (List) fTrim.clone(); |
| } |
| |
| /** |
| * Calculates the correct 'preferred size' of the given control. |
| * For controls that cannot be resized (i.e. stretched) the preferred |
| * size is simply the control's current size. For controls that can be |
| * stretched it represents the minimum size that the control can have |
| * before being tiled onto a separate line. |
| * |
| * This method also managed the two stretch-able controls known by |
| * the Workbench; the StatusLine and the CBanner. These controls |
| * require specialized help (i.e. hacks) since their respective |
| * <code>computeSize</code> methods cannot correctly compute their |
| * preferred size. |
| * |
| * @param ctrl The control to get the preferred size for |
| * @return The preferred size of the given control |
| */ |
| private Point getPrefSize(Control ctrl) { |
| // A control's prefSize -is- its current size |
| Point prefSize = ctrl.getSize(); |
| |
| // If the control has not yet sized itself then initialize it to its preferred size |
| if ((prefSize.x == 0 || prefSize.y == 0) && !getData(ctrl).isResizeable()) { |
| prefSize = ctrl.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| ctrl.setSize(prefSize); |
| } |
| |
| // If the control is re-sizable then we use the default size as its 'minimum' size |
| if (getData(ctrl).isResizeable()) { |
| // Special case: we allow sufficient room to ensure that the right area, |
| // the 'swoop' and some constant size for the CoolBar are available. |
| // The height is set to zero but is re-calculated after the true width that |
| // the banner will occupy is calculated |
| if (ctrl instanceof CBanner) { |
| CBanner banner = (CBanner) ctrl; |
| prefSize.x = banner.getRightWidth() + banner.getBorderWidth() + MIN_BANNER_LEFT; |
| prefSize.y = 0; // No height for now, computed later |
| } |
| else if (getData(ctrl).getId().equals("org.eclipse.jface.action.StatusLineManager")) { //$NON-NLS-1$ |
| // Hack !! should fix StatusLine itself |
| // We have to force the StatusLine to have a consistent |
| // preferred width (it's computeSize takes the message into |
| // account |
| prefSize = new Point(250, 26); |
| } |
| else { |
| // Normal control, expect it to return its true preferred size |
| prefSize = ctrl.computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| } |
| |
| // Cache the computed preferred size |
| ctrl.setData(PREFSIZE_DATA_ID, prefSize); |
| } |
| |
| return prefSize; |
| } |
| |
| /** |
| * This is where the information required to lay the controls belonging to a |
| * particular trim area out. |
| * <p> |
| * Tile the controls in the trim area into one or more lines. Each line is |
| * guaranteed to take up less than or equal to the 'majorHint' in the major |
| * dimension. The result is a complete cache of the information needed to |
| * lay the controls in the trim area out. |
| * </p> |
| * |
| * @param majorHint The length of the major dimension |
| * |
| * @return A List of <code>TrimLine</code> elements |
| */ |
| public int computeWrappedTrim(int majorHint) { |
| int totalMinor = 0; |
| |
| // Remove any previous tiling information |
| lines.clear(); |
| |
| boolean isHorizontal = !isVertical(); |
| |
| TrimLine curLine = new TrimLine(majorHint); |
| lines.add(curLine); |
| |
| TrimCommonUIHandle dragHandle = null; |
| |
| // Initialize the tilePos to force a 'new' line |
| List caches = getCaches(); |
| for (Iterator cacheIter = caches.iterator(); cacheIter.hasNext();) { |
| SizeCache cache = (SizeCache) cacheIter.next(); |
| Control ctrl = cache.getControl(); |
| |
| // Skip invisible trim |
| if (ctrl == null || !ctrl.isVisible()) |
| continue; |
| |
| // We need to keep the drag handle and the 'real' trim on the same line... |
| if (ctrl instanceof TrimCommonUIHandle) { |
| dragHandle = (TrimCommonUIHandle) ctrl; |
| |
| // Ensure that the dragHandle is sized correctly |
| Point dhSize = dragHandle.getSize(); |
| if (dhSize.x == 0 || dhSize.y == 0) |
| dragHandle.setSize(dragHandle.computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
| |
| continue; |
| } |
| |
| // A control's prefSize -is- its current size |
| Point prefSize = getPrefSize(ctrl); |
| |
| // Will this control fit onto the current line? |
| int tileLength = isHorizontal ? prefSize.x : prefSize.y; |
| int minorSize = isHorizontal ? prefSize.y : prefSize.x; |
| |
| // Including its drag handle? |
| if (dragHandle != null) { |
| Point dhSize = dragHandle.getSize(); |
| tileLength += isHorizontal ? dhSize.x : dhSize.y; |
| } |
| |
| // Space out the controls |
| tileLength += TILE_SPACING; |
| |
| // Place the control into the 'current' line if it'll fit or if |
| // it's the -first- control (this handles the case where a control is too |
| // large to fit into the current TrimArea's 'major' size.) |
| //if ((tilePos + tileLength) <= majorHint || curLine.controls.size() == 0) { |
| if (tileLength < curLine.availableSpace || curLine.controls.size() == 0) { |
| curLine.addControl(ctrl, tileLength, minorSize, dragHandle); |
| } else { |
| totalMinor += curLine.terminate(); |
| |
| // We need a new line... |
| curLine = new TrimLine(majorHint); |
| lines.add(curLine); |
| |
| curLine.addControl(ctrl, tileLength, minorSize, dragHandle); |
| } |
| |
| // If we get here then we've already handled the drag handle |
| dragHandle = null; |
| } |
| |
| // Remember how much space was needed to tile -all- the lines |
| totalMinor += curLine.terminate(); |
| |
| // Finally, add enough room to provide spacing between the lines |
| totalMinor += (lines.size() + 1) * LINE_SPACING; |
| |
| return totalMinor; |
| } |
| |
| static int tileCount = 0; |
| /** |
| * Re-position and, in the case of re-sizable trim, re-size the |
| * controls in the trim based on the information cached in the |
| * last call to 'computeWrappedTrim'. |
| * |
| * @param anchorX The X position to start tiling |
| * @param anchorY The Y position to start tiling |
| * @param major The length of the trim area in the major dimension |
| */ |
| public void tileTrim(int anchorX, int anchorY, int major) { |
| // Capture the location of the tiled rectangle |
| curRect.x = anchorX; |
| curRect.y = anchorY; |
| |
| boolean isHorizontal = !isVertical(); |
| |
| int tileX = anchorX; |
| int tileY = anchorY; |
| |
| if (isHorizontal) { |
| tileX += TILE_SPACING; |
| tileY += LINE_SPACING; |
| } |
| else { |
| tileY += TILE_SPACING; |
| tileX += LINE_SPACING; |
| } |
| |
| for (Iterator lineIter = lines.iterator(); lineIter.hasNext();) { |
| TrimLine line = (TrimLine) lineIter.next(); |
| |
| int curExtraSpace = line.availableSpace; |
| int curResizeCount = line.resizableCount; |
| for (Iterator ctrlIter = line.controls.iterator(); ctrlIter.hasNext();) { |
| Control ctrl = (Control) ctrlIter.next(); |
| |
| // Make the control the correct size |
| Point prefSize = ctrl.getSize(); |
| if (getData(ctrl).isResizeable() && curResizeCount > 0) { |
| // -DONT- trash the actual cached size !! |
| Point cachedPrefSize = (Point) ctrl.getData(PREFSIZE_DATA_ID); |
| prefSize.x = cachedPrefSize.x; |
| prefSize.y = cachedPrefSize.y; |
| |
| int resizeAmount = curExtraSpace/curResizeCount; |
| if (isHorizontal) |
| prefSize.x += resizeAmount; |
| else |
| prefSize.y += resizeAmount; |
| |
| curExtraSpace -= resizeAmount; |
| curResizeCount--; |
| |
| ctrl.setSize(prefSize); |
| } |
| |
| // Now, position the control |
| ctrl.setLocation(tileX, tileY); |
| |
| // adjust for the control |
| if (isHorizontal) |
| tileX += prefSize.x; |
| else |
| tileY += prefSize.y; |
| |
| // Adjust the TILE_SPACING (unless it's a handle) |
| if (!(ctrl instanceof TrimCommonUIHandle)) { |
| if (isHorizontal) |
| tileX += TILE_SPACING; |
| else |
| tileY += TILE_SPACING; |
| } |
| } |
| |
| if (isHorizontal) { |
| tileY += (line.minorMax + LINE_SPACING); |
| tileX = anchorX + TILE_SPACING; |
| } |
| else { |
| tileX += (line.minorMax + LINE_SPACING); |
| tileY = anchorY + TILE_SPACING; |
| } |
| } |
| |
| // capture the bounds of the tiled area |
| if (isHorizontal) { |
| curRect.width = major; |
| curRect.height = tileY - anchorY; |
| } |
| else { |
| curRect.width = tileX - anchorX; |
| curRect.height = major; |
| } |
| } |
| |
| /** |
| * return true if this area orientation is vertical. |
| * |
| * @return <code>true</code> |
| */ |
| public boolean isVertical() { |
| return fId == SWT.LEFT || fId == SWT.RIGHT; |
| } |
| |
| /** |
| * The ID for this area. |
| * |
| * @return the ID. |
| */ |
| public int getId() { |
| return fId; |
| } |
| |
| /** |
| * The NLS display name for this area. |
| * |
| * @return the String display name. |
| */ |
| public String getDisplayName() { |
| return fDisplayName; |
| } |
| |
| /** |
| * Add the descriptor representing a piece of trim to this trim area. |
| * |
| * @param desc |
| * the trim descriptor |
| */ |
| public void addTrim(TrimDescriptor desc) { |
| fTrim.add(desc); |
| } |
| |
| /** |
| * Insert this desc before the other desc. If beforeMe is not |
| * part of this area it just defaults to an add. |
| * |
| * @param desc |
| * the window trim |
| * @param beforeMe |
| * before this trim |
| */ |
| public void addTrim(TrimDescriptor desc, TrimDescriptor beforeMe) { |
| int idx = fTrim.indexOf(beforeMe); |
| if (idx == -1) { |
| fTrim.add(desc); |
| } else { |
| ListIterator i = fTrim.listIterator(idx); |
| i.add(desc); |
| } |
| } |
| |
| /** |
| * Remove the descriptor representing a piece of trim from this trim area. |
| * |
| * @param desc |
| * the trim descriptor |
| */ |
| public void removeTrim(TrimDescriptor desc) { |
| fTrim.remove(desc); |
| } |
| |
| /** |
| * Does this area contain a piece of trim. |
| * |
| * @param desc |
| * the trim |
| * @return <code>true</code> if we contain the trim. |
| */ |
| public boolean contains(TrimDescriptor desc) { |
| return fTrim.contains(desc); |
| } |
| |
| /** |
| * Takes the trim area and turns it into an List of {@link SizeCache}. |
| * There can be more items in the return list than there are trim |
| * descriptors in the area. |
| * |
| * @return a list of {@link SizeCache} |
| */ |
| public List getCaches() { |
| ArrayList result = new ArrayList(fTrim.size()); |
| Iterator d = fTrim.iterator(); |
| while (d.hasNext()) { |
| TrimDescriptor desc = (TrimDescriptor) d.next(); |
| if (desc.getDockingCache() != null) { |
| result.add(desc.getDockingCache()); |
| } |
| result.add(desc.getCache()); |
| } |
| return result; |
| } |
| } |