001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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.jdt.internal.ui.actions;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.swt.custom.StyledText;
016: import org.eclipse.swt.graphics.GC;
017: import org.eclipse.swt.graphics.Point;
018: import org.eclipse.swt.graphics.Rectangle;
019: import org.eclipse.swt.widgets.Control;
020: import org.eclipse.swt.widgets.Display;
021: import org.eclipse.swt.widgets.Menu;
022: import org.eclipse.swt.widgets.Table;
023: import org.eclipse.swt.widgets.TableItem;
024: import org.eclipse.swt.widgets.Tree;
025: import org.eclipse.swt.widgets.TreeItem;
026:
027: import org.eclipse.jface.action.Action;
028: import org.eclipse.jface.action.IMenuManager;
029: import org.eclipse.jface.action.MenuManager;
030:
031: import org.eclipse.ui.PlatformUI;
032: import org.eclipse.ui.keys.IBindingService;
033:
034: /**
035: * A quick menu actions provides support to assign short cuts
036: * to sub menus.
037: *
038: * @since 3.0
039: */
040: public abstract class QuickMenuAction extends Action {
041:
042: private static final int CHAR_INDENT = 3;
043:
044: /**
045: * Creates a new quick menu action with the given command id.
046: *
047: * @param commandId the command id of the short cut used to open
048: * the sub menu
049: */
050: public QuickMenuAction(String commandId) {
051: setActionDefinitionId(commandId);
052: }
053:
054: /**
055: * {@inheritDoc}
056: */
057: public void run() {
058: Display display = Display.getCurrent();
059: if (display == null)
060: return;
061: Control focus = display.getFocusControl();
062: if (focus == null || focus.isDisposed())
063: return;
064:
065: MenuManager menu = new MenuManager();
066: fillMenu(menu);
067: final Menu widget = menu.createContextMenu(focus.getShell());
068: Point location = computeMenuLocation(focus, widget);
069: if (location == null)
070: return;
071: widget.setLocation(location);
072: widget.setVisible(true);
073: while (!widget.isDisposed() && widget.isVisible()) {
074: if (!display.readAndDispatch())
075: display.sleep();
076: }
077: if (!widget.isDisposed()) {
078: widget.dispose();
079: }
080: }
081:
082: /**
083: * Hook to fill a menu manager with the items of the sub menu.
084: *
085: * @param menu the sub menu to fill
086: */
087: protected abstract void fillMenu(IMenuManager menu);
088:
089: /**
090: * Adds the shortcut to the given menu text and returns it.
091: *
092: * @param menuText the menu text
093: * @return the menu text with the shortcut
094: * @since 3.1
095: */
096: public String addShortcut(String menuText) {
097: String shortcut = getShortcutString();
098: if (menuText == null || shortcut == null)
099: return menuText;
100:
101: return menuText + '\t' + shortcut;
102: }
103:
104: /**
105: * Returns the shortcut assigned to the sub menu or <code>null</code> if
106: * no short cut is assigned.
107: *
108: * @return the shortcut as a human readable string or <code>null</code>
109: */
110: private String getShortcutString() {
111: IBindingService bindingService = (IBindingService) PlatformUI
112: .getWorkbench().getAdapter(IBindingService.class);
113: if (bindingService == null)
114: return null;
115: return bindingService
116: .getBestActiveBindingFormattedFor(getActionDefinitionId());
117: }
118:
119: private Point computeMenuLocation(Control focus, Menu menu) {
120: Point cursorLocation = focus.getDisplay().getCursorLocation();
121: Rectangle clientArea = null;
122: Point result = null;
123: if (focus instanceof StyledText) {
124: StyledText styledText = (StyledText) focus;
125: clientArea = styledText.getClientArea();
126: result = computeMenuLocation(styledText);
127: } else if (focus instanceof Tree) {
128: Tree tree = (Tree) focus;
129: clientArea = tree.getClientArea();
130: result = computeMenuLocation(tree);
131: } else if (focus instanceof Table) {
132: Table table = (Table) focus;
133: clientArea = table.getClientArea();
134: result = computeMenuLocation(table);
135: }
136: if (result == null) {
137: result = focus.toControl(cursorLocation);
138: }
139: if (clientArea != null && !clientArea.contains(result)) {
140: result = new Point(clientArea.x + clientArea.width / 2,
141: clientArea.y + clientArea.height / 2);
142: }
143: Rectangle shellArea = focus.getShell().getClientArea();
144: if (!shellArea.contains(focus.getShell().toControl(
145: focus.toDisplay(result)))) {
146: result = new Point(shellArea.x + shellArea.width / 2,
147: shellArea.y + shellArea.height / 2);
148: }
149: return focus.toDisplay(result);
150: }
151:
152: /**
153: * Hook to compute the menu location if the focus widget is
154: * a styled text widget.
155: *
156: * @param text the styled text widget that has the focus
157: *
158: * @return a widget relative position of the menu to pop up or
159: * <code>null</code> if now position inside the widget can
160: * be computed
161: */
162: protected Point computeMenuLocation(StyledText text) {
163: int offset = text.getCaretOffset();
164: Point result = text.getLocationAtOffset(offset);
165: result.y += text.getLineHeight(offset);
166: if (!text.getClientArea().contains(result))
167: return null;
168: return result;
169: }
170:
171: /**
172: * Hook to compute the menu location if the focus widget is
173: * a tree widget.
174: *
175: * @param tree the tree widget that has the focus
176: *
177: * @return a widget relative position of the menu to pop up or
178: * <code>null</code> if now position inside the widget can
179: * be computed
180: */
181: protected Point computeMenuLocation(Tree tree) {
182: TreeItem[] items = tree.getSelection();
183: Rectangle clientArea = tree.getClientArea();
184: switch (items.length) {
185: case 0:
186: return null;
187: case 1:
188: Rectangle bounds = items[0].getBounds();
189: Rectangle intersect = clientArea.intersection(bounds);
190: if (intersect != null && intersect.height == bounds.height) {
191: return new Point(Math.max(0, bounds.x
192: + getAvarageCharWith(tree) * CHAR_INDENT),
193: bounds.y + bounds.height);
194: } else {
195: return null;
196: }
197: default:
198: Rectangle[] rectangles = new Rectangle[items.length];
199: for (int i = 0; i < rectangles.length; i++) {
200: rectangles[i] = items[i].getBounds();
201: }
202: Point cursorLocation = tree.getDisplay()
203: .getCursorLocation();
204: Point result = findBestLocation(getIncludedPositions(
205: rectangles, clientArea), tree
206: .toControl(cursorLocation));
207: if (result != null)
208: result.x = result.x + getAvarageCharWith(tree)
209: * CHAR_INDENT;
210: return result;
211: }
212: }
213:
214: /**
215: * Hook to compute the menu location if the focus widget is
216: * a table widget.
217: *
218: * @param table the table widget that has the focus
219: *
220: * @return a widget relative position of the menu to pop up or
221: * <code>null</code> if now position inside the widget can
222: * be computed
223: */
224: protected Point computeMenuLocation(Table table) {
225: TableItem[] items = table.getSelection();
226: Rectangle clientArea = table.getClientArea();
227: switch (items.length) {
228: case 0: {
229: return null;
230: }
231: case 1: {
232: Rectangle bounds = items[0].getBounds(0);
233: Rectangle iBounds = items[0].getImageBounds(0);
234: Rectangle intersect = clientArea.intersection(bounds);
235: if (intersect != null && intersect.height == bounds.height) {
236: return new Point(Math.max(0, bounds.x + iBounds.width
237: + getAvarageCharWith(table) * CHAR_INDENT),
238: bounds.y + bounds.height);
239: } else {
240: return null;
241: }
242: }
243: default: {
244: Rectangle[] rectangles = new Rectangle[items.length];
245: for (int i = 0; i < rectangles.length; i++) {
246: rectangles[i] = items[i].getBounds(0);
247: }
248: Rectangle iBounds = items[0].getImageBounds(0);
249: Point cursorLocation = table.getDisplay()
250: .getCursorLocation();
251: Point result = findBestLocation(getIncludedPositions(
252: rectangles, clientArea), table
253: .toControl(cursorLocation));
254: if (result != null)
255: result.x = result.x + iBounds.width
256: + getAvarageCharWith(table) * CHAR_INDENT;
257: return result;
258: }
259: }
260: }
261:
262: private Point[] getIncludedPositions(Rectangle[] rectangles,
263: Rectangle widgetBounds) {
264: List result = new ArrayList();
265: for (int i = 0; i < rectangles.length; i++) {
266: Rectangle rectangle = rectangles[i];
267: Rectangle intersect = widgetBounds.intersection(rectangle);
268: if (intersect != null
269: && intersect.height == rectangle.height) {
270: result.add(new Point(intersect.x, intersect.y
271: + intersect.height));
272: }
273: }
274: return (Point[]) result.toArray(new Point[result.size()]);
275: }
276:
277: private Point findBestLocation(Point[] points, Point relativeCursor) {
278: Point result = null;
279: double bestDist = Double.MAX_VALUE;
280: for (int i = 0; i < points.length; i++) {
281: Point point = points[i];
282: int a = 0;
283: int b = 0;
284: if (point.x > relativeCursor.x) {
285: a = point.x - relativeCursor.x;
286: } else {
287: a = relativeCursor.x - point.x;
288: }
289: if (point.y > relativeCursor.y) {
290: b = point.y - relativeCursor.y;
291: } else {
292: b = relativeCursor.y - point.y;
293: }
294: double dist = Math.sqrt(a * a + b * b);
295: if (dist < bestDist) {
296: result = point;
297: bestDist = dist;
298: }
299: }
300: return result;
301: }
302:
303: private int getAvarageCharWith(Control control) {
304: GC gc = null;
305: try {
306: gc = new GC(control);
307: return gc.getFontMetrics().getAverageCharWidth();
308: } finally {
309: if (gc != null)
310: gc.dispose();
311: }
312: }
313: }
|