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