/******************************************************************************* * Copyright (c) 2006, 2007 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.menus; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IStatus; import org.eclipse.jface.menus.IWidget; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.internal.WindowTrimProxy; import org.eclipse.ui.internal.WorkbenchWindow; import org.eclipse.ui.internal.layout.IWindowTrim; import org.eclipse.ui.internal.layout.TrimLayout; import org.eclipse.ui.internal.misc.StatusUtil; import org.eclipse.ui.menus.AbstractWorkbenchTrimWidget; import org.eclipse.ui.menus.IMenuService; import org.eclipse.ui.menus.IWorkbenchWidget; import org.eclipse.ui.statushandlers.StatusManager; /** * <p> * An implementation that supports 'trim' elements defined in using the * <code>org.eclipse.ui.menus</code> extension point. * </p> * <p> * This class is not intended to be used outside of the * <code>org.eclipse.ui.workbench</code> plug-in. * </p> * * @since 3.2 */ public class TrimBarManager2 { /** * A List of the URI's representing the trim areas */ private MenuLocationURI[] trimAreaURIs = { new MenuLocationURI("toolbar:command1"), //$NON-NLS-1$ new MenuLocationURI("toolbar:command2"), //$NON-NLS-1$ new MenuLocationURI("toolbar:vertical1"), //$NON-NLS-1$ new MenuLocationURI("toolbar:vertical2"), //$NON-NLS-1$ new MenuLocationURI("toolbar:status"), //$NON-NLS-1$ }; /** * The SWT 'side' corresponding to a URI */ int[] swtSides = { SWT.TOP, SWT.TOP, SWT.LEFT, SWT.RIGHT, SWT.BOTTOM }; /** * The window on which this menu manager exists; never <code>null</code>. */ private STrimBuilder fTrimBuilder; private WorkbenchMenuService fMenuService; private boolean fDirty; /** * Constructs a new instance of <code>TrimBarManager</code>. * * @param window * The window on which this menu manager exists; must not be * <code>null</code>. */ public TrimBarManager2(final WorkbenchWindow window) { if (window == null) { throw new IllegalArgumentException("The window cannot be null"); //$NON-NLS-1$ } // Remember the parameters fMenuService = (WorkbenchMenuService) window.getWorkbench().getService( IMenuService.class); fTrimBuilder = new STrimBuilder(window); // New layouts are always 'dirty' fDirty = true; } /** * Hacked version of the update method that allows the hiding of any trim * sited at SWT.TOP. This is because the Intro management wants there to be * no trim at the top but can only currently indicate this by using the * CoolBar's visibility... * * @param force * @param recursive * @param hideTopTrim */ public void update(boolean force, boolean recursive, boolean hideTopTrim) { if (force || isDirty()) { // Re-render the trim based on the new layout fTrimBuilder.build(hideTopTrim); setDirty(false); } } /** * Copied from the <code>MenuManager</code> method... * * @param force * If true then do the update even if not 'dirty' * @param recursive * Update recursively * * @see org.eclipse.jface.action.MenuManager#update(boolean, boolean) */ public void update(boolean force, boolean recursive) { update(force, recursive, false); } /** * Set the dirty state of the layout * * @param isDirty */ private void setDirty(boolean isDirty) { fDirty = isDirty; } /** * Returns the 'dirty' state of the layout * * @return Always returns 'true' for now */ private boolean isDirty() { return fDirty; } /** * This is a convenience class that maintains the list of the widgets in the * group. This allows any position / orientation changes to the group to be * passed on to all the widgets for that group. * * @since 3.2 * */ private class TrimWidgetProxy extends WindowTrimProxy { private List widgets; private TrimAdditionCacheEntry cacheEntry; private int originalSide; private int curSide; private Composite parent; /** * Constructor that takes in any information necessary to implement an * IWindowTrim and also has enough state to manage a group with multiple * IWidget contributions. * */ public TrimWidgetProxy(List widgets, int side, Composite parent, TrimAdditionCacheEntry entry, boolean resizeable) { super(parent, entry.getId(), entry.getId(), SWT.TOP | SWT.BOTTOM | SWT.LEFT | SWT.RIGHT, resizeable); // Remember our widget structure this.widgets = widgets; this.curSide = side; this.originalSide = side; this.parent = parent; this.cacheEntry = entry; } /* * (non-Javadoc) * * @see org.eclipse.ui.internal.WindowTrimProxy#dock(int) */ public void dock(int newSide) { // out with the old... for (Iterator iter = widgets.iterator(); iter.hasNext();) { IWidget widget = (IWidget) iter.next(); widget.dispose(); cacheEntry.removeWidget(widget); } // ...in with the new for (Iterator iter = widgets.iterator(); iter.hasNext();) { IWorkbenchWidget widget = (IWorkbenchWidget) iter.next(); if (widget instanceof AbstractWorkbenchTrimWidget) ((AbstractWorkbenchTrimWidget)widget).fill(parent, curSide, newSide); else widget.fill(parent); } curSide = newSide; parent.layout(); } /** * Disposes all the widgets contributed into this group and then * disposes the group's 'proxy' control */ public void dispose() { for (Iterator iter = widgets.iterator(); iter.hasNext();) { IWidget widget = (IWidget) iter.next(); widget.dispose(); // Remove the IWidget from the entry's cache cacheEntry.removeWidget(widget); } getControl().dispose(); } /** * @return The side that the trim was declared to be on */ public int getSide() { return originalSide; } /** * @return Whether this addition is at the start or end of the * containing trim area */ public boolean isAtStart() { //Delegate to the cache entry return cacheEntry.isAtStart(); } } /** * A convenience class that implements the 'rendering' code necessary to * turn the contributions to the 'trim' bar into actual SWT controls. * * @since 3.2 * */ private class STrimBuilder { /** * The WorkbenchWindow that this builder is for */ private WorkbenchWindow fWindow; /** * The list of <code>WindowTrimProxy</code> elements currently * rendered in the WorkbenchWindow. Used to support the update mechanism * (specifically, it's needed to implement the <code>tearDown</code> * method). */ private List curGroups = new ArrayList(); /** * Map to cache which trim has already been initialized */ private Map initializedTrim = new HashMap(); /** * Construct a trim builder for the given WorkbenchWindow * * @param window * The WorkbenchWindow to render the trim on */ public STrimBuilder(WorkbenchWindow window) { fWindow = window; } /** * Remove any rendered trim. This method will always be directly * followed by a call to the 'build' method to update the contents. */ public void tearDown() { // First, remove all trim for (Iterator iter = curGroups.iterator(); iter.hasNext();) { TrimWidgetProxy proxy = (TrimWidgetProxy) iter.next(); fWindow.getTrimManager().removeTrim(proxy); try { proxy.dispose(); } catch (Throwable e) { IStatus status = null; if (e instanceof CoreException) { status = ((CoreException) e).getStatus(); } else { status = StatusUtil .newStatus( IStatus.ERROR, "Internal plug-in widget delegate error on dispose.", e); //$NON-NLS-1$ } StatusUtil .handleStatus( status, "widget delegate failed on dispose: id = " + proxy.getId(), StatusManager.LOG); //$NON-NLS-1$ } } // Clear out the old list curGroups.clear(); } /** * Construct the trim based on the contributions. * * @param layout * The new layout information * @param hideTopTrim * <code>true</code> iff we don't want to display trim * contributed into the SWT.TOP area. This is because the * 'Intro' View hides the CBanner (and so, presumably, also * wants to not show any other trim at the top. * * @param window * The widnow to 'render' the trim into * */ public void build(boolean hideTopTrim) { tearDown(); for (int i = 0; i < trimAreaURIs.length; i++) { processAdditions(trimAreaURIs[i], hideTopTrim); } } /** * @param menuLocationURI * @param hideTopTrim */ private void processAdditions(MenuLocationURI trimURI, boolean hideTopTrim) { List additions = fMenuService.getAdditionsForURI(trimURI); if (additions.size() == 0) return; int swtSide = getSide(trimURI); // Dont show trim on the top if it's 'hidden' if (swtSide == SWT.TOP && hideTopTrim) return; // Each trim addition represents a 'group' into which one or more // widgets can be placed... for (Iterator iterator = additions.iterator(); iterator.hasNext();) { TrimAdditionCacheEntry trimEntry = (TrimAdditionCacheEntry) iterator.next(); String groupId = trimEntry.getId(); // Get the list of IConfgurationElements representing // widgets in this group List widgets = trimEntry.getWidgets(); if (widgets.size() == 0) continue; // Create a 'container' composite for the group Composite grpComposite = new Composite(fWindow.getShell(), SWT.NONE); grpComposite.setToolTipText(groupId); // Create the layout for the 'group' container...-no- border margins RowLayout rl = new RowLayout(); rl.marginBottom = rl.marginHeight = rl.marginLeft = rl.marginRight = rl.marginTop = rl.marginWidth = 0; grpComposite.setLayout(rl); // keep track of whether -any- of the widgets are resizeable boolean resizeable = false; for (Iterator widgetIter = widgets.iterator(); widgetIter.hasNext();) { IWorkbenchWidget widget = (IWorkbenchWidget) widgetIter.next(); IConfigurationElement widgetElement = trimEntry.getElement(widget); if (widget != null) { resizeable |= trimEntry.fillMajor(widgetElement); renderTrim(grpComposite, widget, swtSide); } } // Create the trim proxy for this group TrimWidgetProxy groupTrimProxy = new TrimWidgetProxy(widgets, swtSide, grpComposite, trimEntry, resizeable); curGroups.add(groupTrimProxy); // 'Site' the group in its default location placeGroup(groupTrimProxy); } } private void placeGroup(final TrimWidgetProxy proxy) { // Get the placement parameters final int side = proxy.getSide(); boolean atStart = proxy.isAtStart(); // Place the trim before any other trim if it's // at the 'start'; otherwise place it at the end IWindowTrim beforeMe = null; if (atStart) { List trim = fWindow.getTrimManager().getAreaTrim(side); if (trim.size() > 0) beforeMe = (IWindowTrim) trim.get(0); } // Add the group into trim...safely try { proxy.dock(side); // ensure that the widgets are properly oriented TrimLayout tl = (TrimLayout) fWindow.getShell().getLayout(); tl.addTrim(side, proxy, beforeMe); } catch (Throwable e) { IStatus status = null; if (e instanceof CoreException) { status = ((CoreException) e).getStatus(); } else { status = StatusUtil .newStatus( IStatus.ERROR, "Internal plug-in widget delegate error on dock.", e); //$NON-NLS-1$ } StatusUtil.handleStatus(status, "widget delegate failed on dock: id = " + proxy.getId(), //$NON-NLS-1$ StatusManager.LOG); } } /** * Render a particular SWidget into a given group * * @param groupComposite * The parent to create the widgets under * @param widget * The SWidget to render * @param side */ private void renderTrim(final Composite groupComposite, IWidget iw, final int side) { // OK, fill the widget if (iw != null) { // The -first- time trim is displayed we'll initialize it if (iw instanceof IWorkbenchWidget && initializedTrim.get(iw) == null) { IWorkbenchWidget iww = (IWorkbenchWidget) iw; iww.init(fWindow); initializedTrim.put(iw, iw); } if (iw instanceof AbstractWorkbenchTrimWidget) ((AbstractWorkbenchTrimWidget)iw).fill(groupComposite, SWT.DEFAULT, side); else iw.fill(groupComposite); } } private int getSide(MenuLocationURI uri) { for (int i = 0; i < trimAreaURIs.length; i++) { if (trimAreaURIs[i].getRawString().equals(uri.getRawString())) return swtSides[i]; } return SWT.BOTTOM; } /** * Reposition any contributed trim whose id is -not- a 'knownId'. If the * id is known then the trim has already been positioned from the stored * workbench state. If it isn't then it's a new contribution whose * default position may have been trashed by the WorkbenchWindow's * 'restoreState' handling. * * @param knownIds * A List of strings containing the ids of any trim that was * explicitly positioned during the restore state. */ public void updateLocations(List knownIds) { for (Iterator iter = curGroups.iterator(); iter.hasNext();) { TrimWidgetProxy proxy = (TrimWidgetProxy) iter.next(); if (!knownIds.contains(proxy.getId())) { placeGroup(proxy); } } } } /** * Updates the placement of any contributed trim that is -not- in the * 'knownIds' list (which indicates that it has already been placed using * cached workspace data. * * Forward on to the bulder for implementation */ public void updateLocations(List knownIds) { fTrimBuilder.updateLocations(knownIds); } /** * unhook the menu service. */ public void dispose() { fMenuService = null; fTrimBuilder = null; } }