Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
View | Details | Raw Unified | Return to bug 157072
Collapse All | Expand All

(-)Eclipse (+289 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2006 IBM Corporation and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     IBM Corporation - initial API and implementation
10
 ******************************************************************************/
11
12
package org.eclipse.ui.actions;
13
14
import java.util.ArrayList;
15
import java.util.List;
16
17
import org.eclipse.jface.action.IMenuManager;
18
import org.eclipse.jface.action.MenuManager;
19
import org.eclipse.swt.custom.StyledText;
20
import org.eclipse.swt.graphics.GC;
21
import org.eclipse.swt.graphics.Point;
22
import org.eclipse.swt.graphics.Rectangle;
23
import org.eclipse.swt.widgets.Control;
24
import org.eclipse.swt.widgets.Display;
25
import org.eclipse.swt.widgets.Menu;
26
import org.eclipse.swt.widgets.Table;
27
import org.eclipse.swt.widgets.TableItem;
28
import org.eclipse.swt.widgets.Tree;
29
import org.eclipse.swt.widgets.TreeItem;
30
31
/**
32
 * Abstract class that is capable of creating a context menu under the mouse
33
 * pointer.
34
 * 
35
 * @since 3.3
36
 */
37
public abstract class QuickMenuCreator {
38
39
	private static final int CHAR_INDENT = 3;
40
41
	private Menu quickMenu;
42
43
	/**
44
	 * Create the context menu.
45
	 */
46
	public void createMenu() {
47
		Display display = Display.getCurrent();
48
		if (display == null) {
49
			return;
50
		}
51
		Control focus = display.getFocusControl();
52
		if (focus == null || focus.isDisposed()) {
53
			return;
54
		}
55
56
		MenuManager menu = new MenuManager();
57
		fillMenu(menu);
58
		if (quickMenu != null) {
59
			quickMenu.dispose();
60
			quickMenu = null;
61
		}
62
		quickMenu = menu.createContextMenu(focus.getShell());
63
		Point location = computeMenuLocation(focus);
64
		if (location == null) {
65
			return;
66
		}
67
		quickMenu.setLocation(location);
68
		quickMenu.setVisible(true);
69
	}
70
71
	/**
72
	 * Create the contents of the context menu.
73
	 * 
74
	 * @param menu
75
	 *            the menu to fill
76
	 */
77
	protected abstract void fillMenu(IMenuManager menu);
78
79
	/**
80
	 * Determine the optimal point for this menu to appear.
81
	 * 
82
	 * @param focus
83
	 *            the focus control
84
	 * @return the optimal placement
85
	 */
86
	private Point computeMenuLocation(Control focus) {
87
		Point cursorLocation = focus.getDisplay().getCursorLocation();
88
		Rectangle clientArea = null;
89
		Point result = null;
90
		if (focus instanceof StyledText) {
91
			StyledText styledText = (StyledText) focus;
92
			clientArea = styledText.getClientArea();
93
			result = computeMenuLocation(styledText);
94
		} else if (focus instanceof Tree) {
95
			Tree tree = (Tree) focus;
96
			clientArea = tree.getClientArea();
97
			result = computeMenuLocation(tree);
98
		} else if (focus instanceof Table) {
99
			Table table = (Table) focus;
100
			clientArea = table.getClientArea();
101
			result = computeMenuLocation(table);
102
		}
103
		if (result == null) {
104
			result = focus.toControl(cursorLocation);
105
		}
106
		if (clientArea != null && !clientArea.contains(result)) {
107
			result = new Point(clientArea.x + clientArea.width / 2,
108
					clientArea.y + clientArea.height / 2);
109
		}
110
		Rectangle shellArea = focus.getShell().getClientArea();
111
		if (!shellArea.contains(focus.getShell().toControl(
112
				focus.toDisplay(result)))) {
113
			result = new Point(shellArea.x + shellArea.width / 2, shellArea.y
114
					+ shellArea.height / 2);
115
		}
116
		return focus.toDisplay(result);
117
	}
118
119
	/**
120
	 * Hook to compute the menu location if the focus widget is a styled text
121
	 * widget.
122
	 * 
123
	 * @param text
124
	 *            the styled text widget that has the focus
125
	 * 
126
	 * @return a widget relative position of the menu to pop up or
127
	 *         <code>null</code> if now position inside the widget can be
128
	 *         computed
129
	 */
130
	private Point computeMenuLocation(StyledText text) {
131
		Point result = text.getLocationAtOffset(text.getCaretOffset());
132
		result.y += text.getLineHeight();
133
		if (!text.getClientArea().contains(result)) {
134
			return null;
135
		}
136
		return result;
137
	}
138
139
	/**
140
	 * Hook to compute the menu location if the focus widget is a tree widget.
141
	 * 
142
	 * @param tree
143
	 *            the tree widget that has the focus
144
	 * 
145
	 * @return a widget relative position of the menu to pop up or
146
	 *         <code>null</code> if now position inside the widget can be
147
	 *         computed
148
	 */
149
	private Point computeMenuLocation(Tree tree) {
150
		TreeItem[] items = tree.getSelection();
151
		Rectangle clientArea = tree.getClientArea();
152
		switch (items.length) {
153
		case 0:
154
			return null;
155
		case 1:
156
			Rectangle bounds = items[0].getBounds();
157
			Rectangle intersect = clientArea.intersection(bounds);
158
			if (intersect != null && intersect.height == bounds.height) {
159
				return new Point(Math.max(0, bounds.x
160
						+ getAvarageCharWith(tree) * CHAR_INDENT), bounds.y
161
						+ bounds.height);
162
			}
163
			return null;
164
165
		default:
166
			Rectangle[] rectangles = new Rectangle[items.length];
167
			for (int i = 0; i < rectangles.length; i++) {
168
				rectangles[i] = items[i].getBounds();
169
			}
170
			Point cursorLocation = tree.getDisplay().getCursorLocation();
171
			Point result = findBestLocation(getIncludedPositions(rectangles,
172
					clientArea), tree.toControl(cursorLocation));
173
			if (result != null) {
174
				result.x = result.x + getAvarageCharWith(tree) * CHAR_INDENT;
175
			}
176
			return result;
177
		}
178
	}
179
180
	/**
181
	 * Hook to compute the menu location if the focus widget is a table widget.
182
	 * 
183
	 * @param table
184
	 *            the table widget that has the focus
185
	 * 
186
	 * @return a widget relative position of the menu to pop up or
187
	 *         <code>null</code> if now position inside the widget can be
188
	 *         computed
189
	 */
190
	private Point computeMenuLocation(Table table) {
191
		TableItem[] items = table.getSelection();
192
		Rectangle clientArea = table.getClientArea();
193
		switch (items.length) {
194
		case 0: {
195
			return null;
196
		}
197
		case 1: {
198
			Rectangle bounds = items[0].getBounds(0);
199
			Rectangle iBounds = items[0].getImageBounds(0);
200
			Rectangle intersect = clientArea.intersection(bounds);
201
			if (intersect != null && intersect.height == bounds.height) {
202
				return new Point(Math.max(0, bounds.x + iBounds.width
203
						+ getAvarageCharWith(table) * CHAR_INDENT), bounds.y
204
						+ bounds.height);
205
			}
206
			return null;
207
208
		}
209
		default: {
210
			Rectangle[] rectangles = new Rectangle[items.length];
211
			for (int i = 0; i < rectangles.length; i++) {
212
				rectangles[i] = items[i].getBounds(0);
213
			}
214
			Rectangle iBounds = items[0].getImageBounds(0);
215
			Point cursorLocation = table.getDisplay().getCursorLocation();
216
			Point result = findBestLocation(getIncludedPositions(rectangles,
217
					clientArea), table.toControl(cursorLocation));
218
			if (result != null) {
219
				result.x = result.x + iBounds.width + getAvarageCharWith(table)
220
						* CHAR_INDENT;
221
			}
222
			return result;
223
		}
224
		}
225
	}
226
227
	private Point[] getIncludedPositions(Rectangle[] rectangles,
228
			Rectangle widgetBounds) {
229
		List result = new ArrayList();
230
		for (int i = 0; i < rectangles.length; i++) {
231
			Rectangle rectangle = rectangles[i];
232
			Rectangle intersect = widgetBounds.intersection(rectangle);
233
			if (intersect != null && intersect.height == rectangle.height) {
234
				result.add(new Point(intersect.x, intersect.y
235
						+ intersect.height));
236
			}
237
		}
238
		return (Point[]) result.toArray(new Point[result.size()]);
239
	}
240
241
	private Point findBestLocation(Point[] points, Point relativeCursor) {
242
		Point result = null;
243
		double bestDist = Double.MAX_VALUE;
244
		for (int i = 0; i < points.length; i++) {
245
			Point point = points[i];
246
			int a = 0;
247
			int b = 0;
248
			if (point.x > relativeCursor.x) {
249
				a = point.x - relativeCursor.x;
250
			} else {
251
				a = relativeCursor.x - point.x;
252
			}
253
			if (point.y > relativeCursor.y) {
254
				b = point.y - relativeCursor.y;
255
			} else {
256
				b = relativeCursor.y - point.y;
257
			}
258
			double dist = Math.sqrt(a * a + b * b);
259
			if (dist < bestDist) {
260
				result = point;
261
				bestDist = dist;
262
			}
263
		}
264
		return result;
265
	}
266
267
	private int getAvarageCharWith(Control control) {
268
		GC gc = null;
269
		try {
270
			gc = new GC(control);
271
			return gc.getFontMetrics().getAverageCharWidth();
272
		} finally {
273
			if (gc != null) {
274
				gc.dispose();
275
			}
276
		}
277
	}
278
279
	/**
280
	 * Dispose of this quick menu creator. Subclasses should ensure that they
281
	 * call this method.
282
	 */
283
	public void dispose() {
284
		if (quickMenu != null) {
285
			quickMenu.dispose();
286
			quickMenu = null;
287
		}
288
	}
289
}
(-)src/org/eclipse/ui/internal/ide/actions/QuickMenuAction.java (-237 / +15 lines)
Lines 10-35 Link Here
10
 *******************************************************************************/
10
 *******************************************************************************/
11
package org.eclipse.ui.internal.ide.actions;
11
package org.eclipse.ui.internal.ide.actions;
12
12
13
import java.util.ArrayList;
14
import java.util.List;
15
16
import org.eclipse.jface.action.Action;
13
import org.eclipse.jface.action.Action;
17
import org.eclipse.jface.action.IMenuManager;
14
import org.eclipse.jface.action.IMenuManager;
18
import org.eclipse.jface.action.MenuManager;
19
import org.eclipse.jface.bindings.TriggerSequence;
15
import org.eclipse.jface.bindings.TriggerSequence;
20
import org.eclipse.swt.custom.StyledText;
21
import org.eclipse.swt.graphics.GC;
22
import org.eclipse.swt.graphics.Point;
23
import org.eclipse.swt.graphics.Rectangle;
24
import org.eclipse.swt.widgets.Control;
25
import org.eclipse.swt.widgets.Display;
26
import org.eclipse.swt.widgets.Menu;
27
import org.eclipse.swt.widgets.Table;
28
import org.eclipse.swt.widgets.TableItem;
29
import org.eclipse.swt.widgets.Tree;
30
import org.eclipse.swt.widgets.TreeItem;
31
import org.eclipse.ui.IWorkbench;
16
import org.eclipse.ui.IWorkbench;
32
import org.eclipse.ui.PlatformUI;
17
import org.eclipse.ui.PlatformUI;
18
import org.eclipse.ui.actions.QuickMenuCreator;
33
import org.eclipse.ui.keys.IBindingService;
19
import org.eclipse.ui.keys.IBindingService;
34
20
35
/**
21
/**
Lines 40-48 Link Here
40
 */
26
 */
41
public abstract class QuickMenuAction extends Action {
27
public abstract class QuickMenuAction extends Action {
42
28
43
    private static final int CHAR_INDENT = 3;
29
	private QuickMenuCreator creator = new QuickMenuCreator() {
44
30
		protected void fillMenu(IMenuManager menu) {
45
    private Menu quickMenu;
31
			QuickMenuAction.this.fillMenu(menu);
32
		}
33
	};
46
34
47
    /**
35
    /**
48
     * Creates a new quick menu action with the given command id.
36
     * Creates a new quick menu action with the given command id.
Lines 59-92 Link Here
59
     * {@inheritDoc}
47
     * {@inheritDoc}
60
     */
48
     */
61
    public void run() {
49
    public void run() {
62
        Display display = Display.getCurrent();
50
    	creator.createMenu();
63
        if (display == null) {
64
			return;
65
		}
66
        Control focus = display.getFocusControl();
67
        if (focus == null || focus.isDisposed()) {
68
			return;
69
		}
70
71
        MenuManager menu = new MenuManager();
72
        fillMenu(menu);
73
        if (quickMenu != null) {
74
            quickMenu.dispose();
75
            quickMenu = null;
76
        }
77
        quickMenu = menu.createContextMenu(focus.getShell());
78
        Point location = computeMenuLocation(focus, quickMenu);
79
        if (location == null) {
80
			return;
81
		}
82
        quickMenu.setLocation(location);
83
        quickMenu.setVisible(true);
84
    }
51
    }
85
52
    
53
    /**
54
     * Dispose of this menu action.
55
     */
86
    public void dispose() {
56
    public void dispose() {
87
        if (quickMenu != null) {
57
        if (creator != null) {
88
            quickMenu.dispose();
58
            creator.dispose();
89
            quickMenu = null;
59
            creator = null;
90
        }
60
        }
91
    }
61
    }
92
62
Lines 96-102 Link Here
96
     * @param menu the sub menu to fill
66
     * @param menu the sub menu to fill
97
     */
67
     */
98
    protected abstract void fillMenu(IMenuManager menu);
68
    protected abstract void fillMenu(IMenuManager menu);
99
69
    
100
    /**
70
    /**
101
     * Returns the short cut assigned to the sub menu or <code>null</code> if
71
     * Returns the short cut assigned to the sub menu or <code>null</code> if
102
     * no short cut is assigned.
72
     * no short cut is assigned.
Lines 115-310 Link Here
115
85
116
		return null;
86
		return null;
117
    }
87
    }
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, shellArea.y
147
                    + 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
        Point result = text.getLocationAtOffset(text.getCaretOffset());
164
        result.y += text.getLineHeight();
165
        if (!text.getClientArea().contains(result)) {
166
			return null;
167
		}
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), bounds.y
193
                        + 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().getCursorLocation();
203
            Point result = findBestLocation(getIncludedPositions(rectangles,
204
                    clientArea), tree.toControl(cursorLocation));
205
            if (result != null) {
206
				result.x = result.x + getAvarageCharWith(tree) * CHAR_INDENT;
207
			}
208
            return result;
209
        }
210
    }
211
212
    /**
213
     * Hook to compute the menu location if the focus widget is
214
     * a table widget.
215
     * 
216
     * @param table the table widget that has the focus
217
     * 
218
     * @return a widget relative position of the menu to pop up or
219
     *  <code>null</code> if now position inside the widget can
220
     *  be computed
221
     */
222
    protected Point computeMenuLocation(Table table) {
223
        TableItem[] items = table.getSelection();
224
        Rectangle clientArea = table.getClientArea();
225
        switch (items.length) {
226
        case 0: {
227
            return null;
228
        }
229
        case 1: {
230
            Rectangle bounds = items[0].getBounds(0);
231
            Rectangle iBounds = items[0].getImageBounds(0);
232
            Rectangle intersect = clientArea.intersection(bounds);
233
            if (intersect != null && intersect.height == bounds.height) {
234
                return new Point(Math.max(0, bounds.x + iBounds.width
235
                        + getAvarageCharWith(table) * CHAR_INDENT), bounds.y
236
                        + bounds.height);
237
            } else {
238
                return null;
239
            }
240
        }
241
        default: {
242
            Rectangle[] rectangles = new Rectangle[items.length];
243
            for (int i = 0; i < rectangles.length; i++) {
244
                rectangles[i] = items[i].getBounds(0);
245
            }
246
            Rectangle iBounds = items[0].getImageBounds(0);
247
            Point cursorLocation = table.getDisplay().getCursorLocation();
248
            Point result = findBestLocation(getIncludedPositions(rectangles,
249
                    clientArea), table.toControl(cursorLocation));
250
            if (result != null) {
251
				result.x = result.x + iBounds.width + getAvarageCharWith(table)
252
                        * CHAR_INDENT;
253
			}
254
            return result;
255
        }
256
        }
257
    }
258
259
    private Point[] getIncludedPositions(Rectangle[] rectangles,
260
            Rectangle widgetBounds) {
261
        List result = new ArrayList();
262
        for (int i = 0; i < rectangles.length; i++) {
263
            Rectangle rectangle = rectangles[i];
264
            Rectangle intersect = widgetBounds.intersection(rectangle);
265
            if (intersect != null && intersect.height == rectangle.height) {
266
                result.add(new Point(intersect.x, intersect.y
267
                        + intersect.height));
268
            }
269
        }
270
        return (Point[]) result.toArray(new Point[result.size()]);
271
    }
272
273
    private Point findBestLocation(Point[] points, Point relativeCursor) {
274
        Point result = null;
275
        double bestDist = Double.MAX_VALUE;
276
        for (int i = 0; i < points.length; i++) {
277
            Point point = points[i];
278
            int a = 0;
279
            int b = 0;
280
            if (point.x > relativeCursor.x) {
281
                a = point.x - relativeCursor.x;
282
            } else {
283
                a = relativeCursor.x - point.x;
284
            }
285
            if (point.y > relativeCursor.y) {
286
                b = point.y - relativeCursor.y;
287
            } else {
288
                b = relativeCursor.y - point.y;
289
            }
290
            double dist = Math.sqrt(a * a + b * b);
291
            if (dist < bestDist) {
292
                result = point;
293
                bestDist = dist;
294
            }
295
        }
296
        return result;
297
    }
298
299
    private int getAvarageCharWith(Control control) {
300
        GC gc = null;
301
        try {
302
            gc = new GC(control);
303
            return gc.getFontMetrics().getAverageCharWidth();
304
        } finally {
305
            if (gc != null) {
306
				gc.dispose();
307
			}
308
        }
309
    }
310
}
88
}

Return to bug 157072