/******************************************************************************* * 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 org.eclipse.jface.action.ContributionItem; import org.eclipse.jface.action.MenuManager; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Cursor; 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.CoolBar; import org.eclipse.swt.widgets.CoolItem; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.ui.internal.IChangeListener; import org.eclipse.ui.internal.IntModel; import org.eclipse.ui.internal.RadioMenu; import org.eclipse.ui.internal.WindowTrimProxy; import org.eclipse.ui.internal.WorkbenchMessages; import org.eclipse.ui.internal.dnd.DragUtil; import org.eclipse.ui.presentations.PresentationUtil; /** * This control provides common UI functionality for trim elements. Its * lifecycle is managed by the <code>TrimLayout</code> which automatically * adds a UI handle to all added trim elements. It uses an instance of a * CoolBar to provide the platform-specific drag affordance. * <p> * It provides the following features: * <p> * Drag affordance and handling: * <ol> * <li>Drag affordance is provided in the <code>paintControl</code> method</li> * <li>Drag handling is provided to allow rearrangement within a trim side or * to other sides, depending on the values returned by <code>IWindowTrim.getValidSides</code></li> * </ol> * </p> * <p> * Context Menu: * <ol> * <li>A "Dock on" menu item is provided to allow changing the side, depending on the values returned by * <code>IWindowTrim.getValidSides</code></li> * <li>A "Close" menu item is provided to allow the User to close (hide) the trim element, * based on the value returned by <code>IWindowTrim.isCloseable</code> * </ol> * </p> * <p> * @since 3.2 * </p> */ public class TrimCommonUIHandle extends Composite { /* * Fields */ private TrimLayout layout; private IWindowTrim trim; private Control toDrag; private int orientation; // CoolBar handling private CoolBar cb = null; private CoolItem ci = null; private static int horizontalHandleSize = -1; private static int verticalHandleSize = -1; /* * Context Menu */ private MenuManager dockMenuManager; private ContributionItem dockContributionItem = null; private Menu sidesMenu; private MenuItem dockCascade; private RadioMenu radioButtons; private IntModel radioVal = new IntModel(0); // private Menu showMenu; // private MenuItem showCascade; /* * Listeners... */ /** * This listener starts a drag operation when * the Drag and Drop manager tells it to */ private Listener dragListener = new Listener() { public void handleEvent(Event event) { // Only allow 'left mouse' drags... if (event.button != 3) { Point position = DragUtil.getEventLoc(event); startDraggingTrim(position); } } }; /** * This listener brings up the context menu */ private Listener menuListener = new Listener() { public void handleEvent(Event event) { Point loc = new Point(event.x, event.y); if (event.type == SWT.MenuDetect) { showDockTrimPopup(loc); } } }; /** * Listen to size changes in the control so we can adjust the * Coolbar and CoolItem to match. */ private ControlListener controlListener = new ControlListener() { public void controlMoved(ControlEvent e) { } public void controlResized(ControlEvent e) { if (e.widget instanceof TrimCommonUIHandle) { TrimCommonUIHandle ctrl = (TrimCommonUIHandle) e.widget; Point size = ctrl.getSize(); // Set the CoolBar and item to match the handle's size cb.setSize(size); ci.setSize(size); ci.setPreferredSize(size); cb.layout(true); } } }; /** * Create a new trim UI handle for a particular IWindowTrim item * * @param layout the TrimLayout we're being used in * @param trim the IWindowTrim we're acting on behalf of * @param curSide the SWT side that the trim is currently on */ public TrimCommonUIHandle(TrimLayout layout, IWindowTrim trim, int curSide) { super(trim.getControl().getParent(), SWT.NONE); // Set the control up with all its various hooks, cursor... setup(layout, trim, curSide); // Listen to size changes to keep the CoolBar synched addControlListener(controlListener); } /** * Set up the trim with its cursor, drag listener, context menu and menu listener. * This method can also be used to 'recycle' a trim handle as long as the new handle * is for trim under the same parent as it was originally used for. */ public void setup(TrimLayout layout, IWindowTrim trim, int curSide) { this.layout = layout; this.trim = trim; this.toDrag = trim.getControl(); this.radioVal.set(curSide); // remember the orientation to use orientation = (curSide == SWT.LEFT || curSide == SWT.RIGHT) ? SWT.VERTICAL : SWT.HORIZONTAL; // Insert a CoolBar and extras in order to provide the drag affordance insertCoolBar(orientation); // Create a window trim proxy for the handle createWindowTrimProxy(); // Set the cursor affordance setDragCursor(); // Set up the dragging behaviour PresentationUtil.addDragListener(cb, dragListener); // Create the docking context menu dockMenuManager = new MenuManager(); dockContributionItem = getDockingContribution(); dockMenuManager.add(dockContributionItem); cb.addListener(SWT.MenuDetect, menuListener); setVisible(true); } /** * Handle the event generated when a User selects a new side to * dock this trim on using the context menu */ private void handleShowOnChange() { layout.removeTrim(trim); trim.dock(radioVal.get()); layout.addTrim(radioVal.get(), trim, null); // perform an optimized layout to show the trim in its new location LayoutUtil.resize(trim.getControl()); } /** * Create and format the IWindowTrim for the handle, ensuring that the * handle will be 'wide' enough to display the drag affordance. */ private void createWindowTrimProxy() { // Create a window trim proxy for the handle WindowTrimProxy proxy = new WindowTrimProxy(this, "NONE", "NONE", //$NON-NLS-1$ //$NON-NLS-2$ SWT.TOP | SWT.BOTTOM | SWT.LEFT | SWT.RIGHT, false); // Set up the handle's hints based on the computed size that // the handle has to be (i.e. if it's HORIZONTAL then the // 'width' is determined by the space required to show the // CB's drag affordance). if (orientation == SWT.HORIZONTAL) { proxy.setWidthHint(getHandleSize()); proxy.setHeightHint(0); } else { proxy.setWidthHint(0); proxy.setHeightHint(getHandleSize()); } setLayoutData(proxy); } /** * Calculate a size for the handle that will be large enough to show * the CoolBar's drag affordance. * * @return The size that the handle has to be, based on the orientation */ private int getHandleSize() { // Do we already have a 'cached' value? if (orientation == SWT.HORIZONTAL && horizontalHandleSize != -1) { return horizontalHandleSize; } if (orientation == SWT.VERTICAL && verticalHandleSize != -1) { return verticalHandleSize; } // Must be the first time, calculate the value CoolBar bar = new CoolBar (trim.getControl().getParent(), orientation); CoolItem item = new CoolItem (bar, SWT.NONE); Label ctrl = new Label (bar, SWT.PUSH); ctrl.setText ("Button 1"); //$NON-NLS-1$ Point size = ctrl.computeSize (SWT.DEFAULT, SWT.DEFAULT); Point ps = item.computeSize (size.x, size.y); item.setPreferredSize (ps); item.setControl (ctrl); bar.pack (); // OK, now the difference between the location of the CB and the // location of the Point bl = ctrl.getLocation(); Point cl = bar.getLocation(); // Toss them now... ctrl.dispose(); item.dispose(); bar.dispose(); // The 'size' is the difference between the start of teh CoolBar and // start of its first control int length; if (orientation == SWT.HORIZONTAL) { length = bl.x - cl.x; horizontalHandleSize = length; } else { length = bl.y - cl.y; verticalHandleSize = length; } return length; } /** * Place a CoolBar / CoolItem / Control inside the current * UI handle. These elements will maintain thier size based on * the size of their 'parent' (this). * * @param parent * @param orientation */ public void insertCoolBar(int orientation) { // Clean up the previous info in case we've changed orientation if (cb != null) { ci.dispose(); PresentationUtil.removeDragListener(cb, dragListener); cb.dispose(); } // Create the necessary parts... cb = new CoolBar(this, orientation | SWT.FLAT); cb.setLocation(0,0); ci = new CoolItem(cb, SWT.FLAT); // Create a composite in order to get the handles to appear Composite comp = new Composite(cb, SWT.NONE); ci.setControl(comp); } /** * Set the cursor to the four-way arrow to indicate that the * trim can be dragged */ private void setDragCursor() { Cursor dragCursor = toDrag.getDisplay().getSystemCursor(SWT.CURSOR_SIZEALL); setCursor(dragCursor); } /* (non-Javadoc) * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean) */ public Point computeSize(int wHint, int hHint, boolean changed) { Point ctrlPrefSize = trim.getControl().computeSize(wHint, hHint); if (orientation == SWT.HORIZONTAL) { return new Point(getHandleSize(), ctrlPrefSize.y); } // Must be vertical.... return new Point(ctrlPrefSize.x, getHandleSize()); } /** * Construct (if necessary) a context menu contribution item and return it. This * is explicitly <code>public</code> so that trim elements can retrieve the item * and add it into their own context menus if desired. * * @return The contribution item for the handle's context menu. */ public ContributionItem getDockingContribution() { if (dockContributionItem == null) { dockContributionItem = new ContributionItem() { public void fill(Menu menu, int index) { // populate from superclass super.fill(menu, index); // Add a 'Close' menu entry if the trim supports the operation if (trim.isCloseable()) { MenuItem closeItem = new MenuItem(menu, SWT.PUSH, index++); closeItem.setText(WorkbenchMessages.TrimCommon_Close); closeItem.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { handleCloseTrim(); } public void widgetDefaultSelected(SelectionEvent e) { } }); new MenuItem(menu, SWT.SEPARATOR, index++); } // Test Hook: add a menu entry that brings up a dialog to allow // testing with various GUI prefs. // MenuItem closeItem = new MenuItem(menu, SWT.PUSH, index++); // closeItem.setText("Change Preferences"); //$NON-NLS-1$ // // closeItem.addSelectionListener(new SelectionListener() { // public void widgetSelected(SelectionEvent e) { // handleChangePreferences(); // } // // public void widgetDefaultSelected(SelectionEvent e) { // } // }); // // new MenuItem(menu, SWT.SEPARATOR, index++); // Create a cascading menu to allow the user to dock the trim dockCascade = new MenuItem(menu, SWT.CASCADE, index++); { dockCascade.setText(WorkbenchMessages.TrimCommon_DockOn); sidesMenu = new Menu(dockCascade); radioButtons = new RadioMenu(sidesMenu, radioVal); radioButtons.addMenuItem(WorkbenchMessages.TrimCommon_Top, new Integer(SWT.TOP)); radioButtons.addMenuItem(WorkbenchMessages.TrimCommon_Bottom, new Integer(SWT.BOTTOM)); radioButtons.addMenuItem(WorkbenchMessages.TrimCommon_Left, new Integer(SWT.LEFT)); radioButtons.addMenuItem(WorkbenchMessages.TrimCommon_Right, new Integer(SWT.RIGHT)); dockCascade.setMenu(sidesMenu); } // if the radioVal changes it means that the User wants to change the docking location radioVal.addChangeListener(new IChangeListener() { public void update(boolean changed) { if (changed) { handleShowOnChange(); } } }); // Provide Show / Hide trim capabilities // showCascade = new MenuItem(menu, SWT.CASCADE, index++); // { // showCascade.setText(WorkbenchMessages.TrimCommon_ShowTrim); // // showMenu = new Menu(dockCascade); // // // Construct a 'hide/show' cascade from -all- the existing trim... // List trimItems = layout.getAllTrim(); // Iterator d = trimItems.iterator(); // while (d.hasNext()) { // IWindowTrim trimItem = (IWindowTrim) d.next(); // MenuItem item = new MenuItem(showMenu, SWT.CHECK); // item.setText(trimItem.getDisplayName()); // item.setSelection(trimItem.getControl().getVisible()); // item.setData(trimItem); // // // TODO: Make this work...wire it off for now // item.setEnabled(false); // // item.addSelectionListener(new SelectionListener() { // // public void widgetSelected(SelectionEvent e) { // IWindowTrim trim = (IWindowTrim) e.widget.getData(); // layout.setTrimVisible(trim, !trim.getControl().getVisible()); // } // // public void widgetDefaultSelected(SelectionEvent e) { // } // // }); // } // // showCascade.setMenu(showMenu); // } } }; } return dockContributionItem; } /** * Test Hook: Bring up a dialog that allows the user to * modify the trimdragging GUI preferences. */ // private void handleChangePreferences() { // TrimDragPreferenceDialog dlg = new TrimDragPreferenceDialog(getShell()); // dlg.open(); // } /** * Handle the event generated when the "Close" item is * selected on the context menu. This removes the associated * trim and calls back to the IWidnowTrim to inform it that * the User has closed the trim. */ private void handleCloseTrim() { layout.removeTrim(trim); trim.handleClose(); } /* (non-Javadoc) * @see org.eclipse.swt.widgets.Widget#dispose() */ public void dispose() { if (radioButtons != null) { radioButtons.dispose(); } // tidy up... removeControlListener(controlListener); removeListener(SWT.MenuDetect, menuListener); super.dispose(); } /** * Begins dragging the trim * * @param position initial mouse position */ protected void startDraggingTrim(Point position) { Rectangle fakeBounds = new Rectangle(100000, 0,0,0); DragUtil.performDrag(trim, fakeBounds, position, true); } /** * Shows the popup menu for an item in the fast view bar. */ private void showDockTrimPopup(Point pt) { Menu menu = dockMenuManager.createContextMenu(toDrag); menu.setLocation(pt.x, pt.y); menu.setVisible(true); } }