001: /*******************************************************************************
002: * Copyright (c) 2005, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.layout;
011:
012: import org.eclipse.jface.action.ContributionItem;
013: import org.eclipse.jface.action.MenuManager;
014: import org.eclipse.swt.SWT;
015: import org.eclipse.swt.events.ControlEvent;
016: import org.eclipse.swt.events.ControlListener;
017: import org.eclipse.swt.events.SelectionEvent;
018: import org.eclipse.swt.events.SelectionListener;
019: import org.eclipse.swt.graphics.Cursor;
020: import org.eclipse.swt.graphics.Point;
021: import org.eclipse.swt.graphics.Rectangle;
022: import org.eclipse.swt.widgets.Composite;
023: import org.eclipse.swt.widgets.Control;
024: import org.eclipse.swt.widgets.CoolBar;
025: import org.eclipse.swt.widgets.CoolItem;
026: import org.eclipse.swt.widgets.Event;
027: import org.eclipse.swt.widgets.Label;
028: import org.eclipse.swt.widgets.Listener;
029: import org.eclipse.swt.widgets.Menu;
030: import org.eclipse.swt.widgets.MenuItem;
031: import org.eclipse.ui.internal.IChangeListener;
032: import org.eclipse.ui.internal.IntModel;
033: import org.eclipse.ui.internal.RadioMenu;
034: import org.eclipse.ui.internal.WindowTrimProxy;
035: import org.eclipse.ui.internal.WorkbenchMessages;
036: import org.eclipse.ui.internal.dnd.DragUtil;
037: import org.eclipse.ui.presentations.PresentationUtil;
038:
039: /**
040: * This control provides common UI functionality for trim elements. Its
041: * lifecycle is managed by the <code>TrimLayout</code> which automatically
042: * adds a UI handle to all added trim elements. It uses an instance of a
043: * CoolBar to provide the platform-specific drag affordance.
044: * <p>
045: * It provides the following features:
046: * <p>
047: * Drag affordance and handling:
048: * <ol>
049: * <li>Drag affordance is provided in the <code>paintControl</code> method</li>
050: * <li>Drag handling is provided to allow rearrangement within a trim side or
051: * to other sides, depending on the values returned by <code>IWindowTrim.getValidSides</code></li>
052: * </ol>
053: * </p>
054: * <p>
055: * Context Menu:
056: * <ol>
057: * <li>A "Dock on" menu item is provided to allow changing the side, depending on the values returned by
058: * <code>IWindowTrim.getValidSides</code></li>
059: * <li>A "Close" menu item is provided to allow the User to close (hide) the trim element,
060: * based on the value returned by <code>IWindowTrim.isCloseable</code>
061: * </ol>
062: * </p>
063: * <p>
064: * @since 3.2
065: * </p>
066: */
067: public class TrimCommonUIHandle extends Composite {
068: /*
069: * Fields
070: */
071: private TrimLayout layout;
072: private IWindowTrim trim;
073: private Control toDrag;
074: private int orientation;
075:
076: // CoolBar handling
077: private CoolBar cb = null;
078: private CoolItem ci = null;
079: private static int horizontalHandleSize = -1;
080: private static int verticalHandleSize = -1;
081:
082: /*
083: * Context Menu
084: */
085: private MenuManager dockMenuManager;
086: private ContributionItem dockContributionItem = null;
087: private Menu sidesMenu;
088: private MenuItem dockCascade;
089: private RadioMenu radioButtons;
090: private IntModel radioVal = new IntModel(0);
091: // private Menu showMenu;
092: // private MenuItem showCascade;
093:
094: /*
095: * Listeners...
096: */
097:
098: /**
099: * This listener starts a drag operation when
100: * the Drag and Drop manager tells it to
101: */
102: private Listener dragListener = new Listener() {
103: public void handleEvent(Event event) {
104: // Only allow 'left mouse' drags...
105: if (event.button != 3) {
106: Point position = DragUtil.getEventLoc(event);
107: startDraggingTrim(position);
108: }
109: }
110: };
111:
112: /**
113: * This listener brings up the context menu
114: */
115: private Listener menuListener = new Listener() {
116: public void handleEvent(Event event) {
117: Point loc = new Point(event.x, event.y);
118: if (event.type == SWT.MenuDetect) {
119: showDockTrimPopup(loc);
120: }
121: }
122: };
123:
124: /**
125: * Listen to size changes in the control so we can adjust the
126: * Coolbar and CoolItem to match.
127: */
128: private ControlListener controlListener = new ControlListener() {
129: public void controlMoved(ControlEvent e) {
130: }
131:
132: public void controlResized(ControlEvent e) {
133: if (e.widget instanceof TrimCommonUIHandle) {
134: TrimCommonUIHandle ctrl = (TrimCommonUIHandle) e.widget;
135: Point size = ctrl.getSize();
136:
137: // Set the CoolBar and item to match the handle's size
138: cb.setSize(size);
139: ci.setSize(size);
140: ci.setPreferredSize(size);
141: cb.layout(true);
142: }
143: }
144: };
145:
146: /**
147: * Create a new trim UI handle for a particular IWindowTrim item
148: *
149: * @param layout the TrimLayout we're being used in
150: * @param trim the IWindowTrim we're acting on behalf of
151: * @param curSide the SWT side that the trim is currently on
152: */
153: public TrimCommonUIHandle(TrimLayout layout, IWindowTrim trim,
154: int curSide) {
155: super (trim.getControl().getParent(), SWT.NONE);
156:
157: // Set the control up with all its various hooks, cursor...
158: setup(layout, trim, curSide);
159:
160: // Listen to size changes to keep the CoolBar synched
161: addControlListener(controlListener);
162: }
163:
164: /**
165: * Set up the trim with its cursor, drag listener, context menu and menu listener.
166: * This method can also be used to 'recycle' a trim handle as long as the new handle
167: * is for trim under the same parent as it was originally used for.
168: */
169: public void setup(TrimLayout layout, IWindowTrim trim, int curSide) {
170: this .layout = layout;
171: this .trim = trim;
172: this .toDrag = trim.getControl();
173: this .radioVal.set(curSide);
174:
175: // remember the orientation to use
176: orientation = (curSide == SWT.LEFT || curSide == SWT.RIGHT) ? SWT.VERTICAL
177: : SWT.HORIZONTAL;
178:
179: // Insert a CoolBar and extras in order to provide the drag affordance
180: insertCoolBar(orientation);
181:
182: // Create a window trim proxy for the handle
183: createWindowTrimProxy();
184:
185: // Set the cursor affordance
186: setDragCursor();
187:
188: // Set up the dragging behaviour
189: PresentationUtil.addDragListener(cb, dragListener);
190:
191: // Create the docking context menu
192: dockMenuManager = new MenuManager();
193: dockContributionItem = getDockingContribution();
194: dockMenuManager.add(dockContributionItem);
195:
196: cb.addListener(SWT.MenuDetect, menuListener);
197:
198: setVisible(true);
199: }
200:
201: /**
202: * Handle the event generated when a User selects a new side to
203: * dock this trim on using the context menu
204: */
205: private void handleShowOnChange() {
206: layout.removeTrim(trim);
207: trim.dock(radioVal.get());
208: layout.addTrim(radioVal.get(), trim, null);
209:
210: // perform an optimized layout to show the trim in its new location
211: LayoutUtil.resize(trim.getControl());
212: }
213:
214: /**
215: * Create and format the IWindowTrim for the handle, ensuring that the
216: * handle will be 'wide' enough to display the drag affordance.
217: */
218: private void createWindowTrimProxy() {
219: // Create a window trim proxy for the handle
220: WindowTrimProxy proxy = new WindowTrimProxy(this ,
221: "NONE", "NONE", //$NON-NLS-1$ //$NON-NLS-2$
222: SWT.TOP | SWT.BOTTOM | SWT.LEFT | SWT.RIGHT, false);
223:
224: // Set up the handle's hints based on the computed size that
225: // the handle has to be (i.e. if it's HORIZONTAL then the
226: // 'width' is determined by the space required to show the
227: // CB's drag affordance).
228: if (orientation == SWT.HORIZONTAL) {
229: proxy.setWidthHint(getHandleSize());
230: proxy.setHeightHint(0);
231: } else {
232: proxy.setWidthHint(0);
233: proxy.setHeightHint(getHandleSize());
234: }
235:
236: setLayoutData(proxy);
237: }
238:
239: /**
240: * Calculate a size for the handle that will be large enough to show
241: * the CoolBar's drag affordance.
242: *
243: * @return The size that the handle has to be, based on the orientation
244: */
245: private int getHandleSize() {
246: // Do we already have a 'cached' value?
247: if (orientation == SWT.HORIZONTAL && horizontalHandleSize != -1) {
248: return horizontalHandleSize;
249: }
250:
251: if (orientation == SWT.VERTICAL && verticalHandleSize != -1) {
252: return verticalHandleSize;
253: }
254:
255: // Must be the first time, calculate the value
256: CoolBar bar = new CoolBar(trim.getControl().getParent(),
257: orientation);
258:
259: CoolItem item = new CoolItem(bar, SWT.NONE);
260:
261: Label ctrl = new Label(bar, SWT.PUSH);
262: ctrl.setText("Button 1"); //$NON-NLS-1$
263: Point size = ctrl.computeSize(SWT.DEFAULT, SWT.DEFAULT);
264:
265: Point ps = item.computeSize(size.x, size.y);
266: item.setPreferredSize(ps);
267: item.setControl(ctrl);
268:
269: bar.pack();
270:
271: // OK, now the difference between the location of the CB and the
272: // location of the
273: Point bl = ctrl.getLocation();
274: Point cl = bar.getLocation();
275:
276: // Toss them now...
277: ctrl.dispose();
278: item.dispose();
279: bar.dispose();
280:
281: // The 'size' is the difference between the start of teh CoolBar and
282: // start of its first control
283: int length;
284: if (orientation == SWT.HORIZONTAL) {
285: length = bl.x - cl.x;
286: horizontalHandleSize = length;
287: } else {
288: length = bl.y - cl.y;
289: verticalHandleSize = length;
290: }
291:
292: return length;
293: }
294:
295: /**
296: * Place a CoolBar / CoolItem / Control inside the current
297: * UI handle. These elements will maintain thier size based on
298: * the size of their 'parent' (this).
299: *
300: * @param parent
301: * @param orientation
302: */
303: public void insertCoolBar(int orientation) {
304: // Clean up the previous info in case we've changed orientation
305: if (cb != null) {
306: ci.dispose();
307: PresentationUtil.removeDragListener(cb, dragListener);
308: cb.dispose();
309: }
310:
311: // Create the necessary parts...
312: cb = new CoolBar(this , orientation | SWT.FLAT);
313: cb.setLocation(0, 0);
314: ci = new CoolItem(cb, SWT.FLAT);
315:
316: // Create a composite in order to get the handles to appear
317: Composite comp = new Composite(cb, SWT.NONE);
318: ci.setControl(comp);
319: }
320:
321: /**
322: * Set the cursor to the four-way arrow to indicate that the
323: * trim can be dragged
324: */
325: private void setDragCursor() {
326: Cursor dragCursor = toDrag.getDisplay().getSystemCursor(
327: SWT.CURSOR_SIZEALL);
328: setCursor(dragCursor);
329: }
330:
331: /* (non-Javadoc)
332: * @see org.eclipse.swt.widgets.Composite#computeSize(int, int, boolean)
333: */
334: public Point computeSize(int wHint, int hHint, boolean changed) {
335: Point ctrlPrefSize = trim.getControl()
336: .computeSize(wHint, hHint);
337: if (orientation == SWT.HORIZONTAL) {
338: return new Point(getHandleSize(), ctrlPrefSize.y);
339: }
340:
341: // Must be vertical....
342: return new Point(ctrlPrefSize.x, getHandleSize());
343: }
344:
345: /**
346: * Construct (if necessary) a context menu contribution item and return it. This
347: * is explicitly <code>public</code> so that trim elements can retrieve the item
348: * and add it into their own context menus if desired.
349: *
350: * @return The contribution item for the handle's context menu.
351: */
352: public ContributionItem getDockingContribution() {
353: if (dockContributionItem == null) {
354: dockContributionItem = new ContributionItem() {
355: public void fill(Menu menu, int index) {
356: // populate from superclass
357: super .fill(menu, index);
358:
359: // Add a 'Close' menu entry if the trim supports the operation
360: if (trim.isCloseable()) {
361: MenuItem closeItem = new MenuItem(menu,
362: SWT.PUSH, index++);
363: closeItem
364: .setText(WorkbenchMessages.TrimCommon_Close);
365:
366: closeItem
367: .addSelectionListener(new SelectionListener() {
368: public void widgetSelected(
369: SelectionEvent e) {
370: handleCloseTrim();
371: }
372:
373: public void widgetDefaultSelected(
374: SelectionEvent e) {
375: }
376: });
377:
378: new MenuItem(menu, SWT.SEPARATOR, index++);
379: }
380:
381: // Test Hook: add a menu entry that brings up a dialog to allow
382: // testing with various GUI prefs.
383: // MenuItem closeItem = new MenuItem(menu, SWT.PUSH, index++);
384: // closeItem.setText("Change Preferences"); //$NON-NLS-1$
385: //
386: // closeItem.addSelectionListener(new SelectionListener() {
387: // public void widgetSelected(SelectionEvent e) {
388: // handleChangePreferences();
389: // }
390: //
391: // public void widgetDefaultSelected(SelectionEvent e) {
392: // }
393: // });
394: //
395: // new MenuItem(menu, SWT.SEPARATOR, index++);
396:
397: // Create a cascading menu to allow the user to dock the trim
398: dockCascade = new MenuItem(menu, SWT.CASCADE,
399: index++);
400: {
401: dockCascade
402: .setText(WorkbenchMessages.TrimCommon_DockOn);
403:
404: sidesMenu = new Menu(dockCascade);
405: radioButtons = new RadioMenu(sidesMenu,
406: radioVal);
407:
408: radioButtons.addMenuItem(
409: WorkbenchMessages.TrimCommon_Top,
410: new Integer(SWT.TOP));
411: radioButtons.addMenuItem(
412: WorkbenchMessages.TrimCommon_Bottom,
413: new Integer(SWT.BOTTOM));
414: radioButtons.addMenuItem(
415: WorkbenchMessages.TrimCommon_Left,
416: new Integer(SWT.LEFT));
417: radioButtons.addMenuItem(
418: WorkbenchMessages.TrimCommon_Right,
419: new Integer(SWT.RIGHT));
420:
421: dockCascade.setMenu(sidesMenu);
422: }
423:
424: // if the radioVal changes it means that the User wants to change the docking location
425: radioVal.addChangeListener(new IChangeListener() {
426: public void update(boolean changed) {
427: if (changed) {
428: handleShowOnChange();
429: }
430: }
431: });
432:
433: // Provide Show / Hide trim capabilities
434: // showCascade = new MenuItem(menu, SWT.CASCADE, index++);
435: // {
436: // showCascade.setText(WorkbenchMessages.TrimCommon_ShowTrim);
437: //
438: // showMenu = new Menu(dockCascade);
439: //
440: // // Construct a 'hide/show' cascade from -all- the existing trim...
441: // List trimItems = layout.getAllTrim();
442: // Iterator d = trimItems.iterator();
443: // while (d.hasNext()) {
444: // IWindowTrim trimItem = (IWindowTrim) d.next();
445: // MenuItem item = new MenuItem(showMenu, SWT.CHECK);
446: // item.setText(trimItem.getDisplayName());
447: // item.setSelection(trimItem.getControl().getVisible());
448: // item.setData(trimItem);
449: //
450: // // TODO: Make this work...wire it off for now
451: // item.setEnabled(false);
452: //
453: // item.addSelectionListener(new SelectionListener() {
454: //
455: // public void widgetSelected(SelectionEvent e) {
456: // IWindowTrim trim = (IWindowTrim) e.widget.getData();
457: // layout.setTrimVisible(trim, !trim.getControl().getVisible());
458: // }
459: //
460: // public void widgetDefaultSelected(SelectionEvent e) {
461: // }
462: //
463: // });
464: // }
465: //
466: // showCascade.setMenu(showMenu);
467: // }
468: }
469: };
470: }
471: return dockContributionItem;
472: }
473:
474: /**
475: * Test Hook: Bring up a dialog that allows the user to
476: * modify the trimdragging GUI preferences.
477: */
478: // private void handleChangePreferences() {
479: // TrimDragPreferenceDialog dlg = new TrimDragPreferenceDialog(getShell());
480: // dlg.open();
481: // }
482: /**
483: * Handle the event generated when the "Close" item is
484: * selected on the context menu. This removes the associated
485: * trim and calls back to the IWidnowTrim to inform it that
486: * the User has closed the trim.
487: */
488: private void handleCloseTrim() {
489: layout.removeTrim(trim);
490: trim.handleClose();
491: }
492:
493: /* (non-Javadoc)
494: * @see org.eclipse.swt.widgets.Widget#dispose()
495: */
496: public void dispose() {
497: if (radioButtons != null) {
498: radioButtons.dispose();
499: }
500:
501: // tidy up...
502: removeControlListener(controlListener);
503: removeListener(SWT.MenuDetect, menuListener);
504:
505: super .dispose();
506: }
507:
508: /**
509: * Begins dragging the trim
510: *
511: * @param position initial mouse position
512: */
513: protected void startDraggingTrim(Point position) {
514: Rectangle fakeBounds = new Rectangle(100000, 0, 0, 0);
515: DragUtil.performDrag(trim, fakeBounds, position, true);
516: }
517:
518: /**
519: * Shows the popup menu for an item in the fast view bar.
520: */
521: private void showDockTrimPopup(Point pt) {
522: Menu menu = dockMenuManager.createContextMenu(toDrag);
523: menu.setLocation(pt.x, pt.y);
524: menu.setVisible(true);
525: }
526: }
|