Source code: org/eclipse/ui/internal/PluginActionBuilder.java
1 /*******************************************************************************
2 * Copyright (c) 2000, 2004 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Common Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/cpl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.ui.internal;
12
13
14 import java.util.ArrayList;
15
16 import org.eclipse.core.runtime.IConfigurationElement;
17 import org.eclipse.core.runtime.Platform;
18 import org.eclipse.jface.action.AbstractGroupMarker;
19 import org.eclipse.jface.action.GroupMarker;
20 import org.eclipse.jface.action.IContributionItem;
21 import org.eclipse.jface.action.IContributionManager;
22 import org.eclipse.jface.action.IMenuManager;
23 import org.eclipse.jface.action.IToolBarManager;
24 import org.eclipse.jface.action.MenuManager;
25 import org.eclipse.jface.action.Separator;
26 import org.eclipse.ui.IWorkbenchActionConstants;
27 import org.eclipse.ui.PlatformUI;
28 import org.eclipse.ui.internal.registry.RegistryReader;
29
30 /**
31 * This class contains shared functionality for reading action contributions
32 * from plugins into workbench parts (both editors and views).
33 */
34 public abstract class PluginActionBuilder extends RegistryReader {
35 public static final String TAG_MENU = "menu"; //$NON-NLS-1$
36 public static final String TAG_ACTION = "action"; //$NON-NLS-1$
37 public static final String TAG_SEPARATOR = "separator"; //$NON-NLS-1$
38 public static final String TAG_GROUP_MARKER = "groupMarker"; //$NON-NLS-1$
39 public static final String TAG_FILTER = "filter"; //$NON-NLS-1$
40 public static final String TAG_VISIBILITY = "visibility"; //$NON-NLS-1$
41 public static final String TAG_ENABLEMENT = "enablement"; //$NON-NLS-1$
42 public static final String TAG_SELECTION = "selection"; //$NON-NLS-1$
43
44 public static final String ATT_TARGET_ID = "targetID"; //$NON-NLS-1$
45 public static final String ATT_ID = "id"; //$NON-NLS-1$
46 public static final String ATT_LABEL = "label"; //$NON-NLS-1$
47 public static final String ATT_ENABLES_FOR = "enablesFor"; //$NON-NLS-1$
48 public static final String ATT_NAME = "name"; //$NON-NLS-1$
49 public static final String ATT_PATH = "path"; //$NON-NLS-1$
50
51 protected String targetID;
52 protected String targetContributionTag;
53 protected BasicContribution currentContribution;
54 protected ArrayList cache;
55
56 /**
57 * The default constructor.
58 */
59 public PluginActionBuilder() {
60 }
61
62 /**
63 * Contributes submenus and/or actions into the provided menu and tool bar
64 * managers.
65 */
66 public final void contribute(IMenuManager menu, IToolBarManager toolbar, boolean appendIfMissing) {
67 if (cache == null)
68 return;
69
70 for (int i = 0; i < cache.size(); i++) {
71 BasicContribution contribution = (BasicContribution)cache.get(i);
72 contribution.contribute(menu, appendIfMissing, toolbar, appendIfMissing);
73 }
74 }
75
76 /**
77 * This factory method returns a new ActionDescriptor for the
78 * configuration element. It should be implemented by subclasses.
79 */
80 protected abstract ActionDescriptor createActionDescriptor(IConfigurationElement element);
81
82 /**
83 * Factory method to create the helper contribution class that will hold
84 * onto the menus and actions contributed.
85 */
86 protected BasicContribution createContribution() {
87 return new BasicContribution();
88 }
89
90 /**
91 * Returns the name of the part ID attribute that is expected
92 * in the target extension.
93 */
94 protected String getTargetID(IConfigurationElement element) {
95 String value = element.getAttribute(ATT_TARGET_ID);
96 return value != null ? value : "???"; //$NON-NLS-1$
97 }
98
99 /**
100 * Reads the contributions from the registry for the provided workbench
101 * part and the provided extension point ID.
102 */
103 protected void readContributions(String id, String tag, String extensionPoint) {
104 cache = null;
105 currentContribution = null;
106 targetID = id;
107 targetContributionTag = tag;
108 readRegistry(Platform.getExtensionRegistry(), PlatformUI.PLUGIN_ID, extensionPoint);
109 }
110
111 /**
112 * Implements abstract method to handle the provided XML element
113 * in the registry.
114 */
115 protected boolean readElement(IConfigurationElement element) {
116 String tag = element.getName();
117
118 // Ignore all object contributions element as these
119 // are handled by the ObjectActionContributorReader.
120 if (tag.equals(ObjectActionContributorReader.TAG_OBJECT_CONTRIBUTION)) {
121 return true;
122 }
123
124 // Found top level contribution element
125 if (tag.equals(targetContributionTag)) {
126 if (targetID != null) {
127 // Ignore contributions not matching target id
128 String id = getTargetID(element);
129 if (id == null || !id.equals(targetID))
130 return true;
131 }
132
133 // Read it's sub-elements
134 currentContribution = createContribution();
135 readElementChildren(element);
136 if (cache == null)
137 cache = new ArrayList(4);
138 cache.add(currentContribution);
139 currentContribution = null;
140 return true;
141 }
142
143
144 // Found menu contribution sub-element
145 if (tag.equals(TAG_MENU)) {
146 currentContribution.addMenu(element);
147 return true;
148 }
149
150 // Found action contribution sub-element
151 if (tag.equals(TAG_ACTION)) {
152 currentContribution.addAction(createActionDescriptor(element));
153 return true;
154 }
155
156 return false;
157 }
158
159
160 /**
161 * Helper class to collect the menus and actions defined within a
162 * contribution element.
163 */
164 protected static class BasicContribution {
165 protected ArrayList menus;
166 protected ArrayList actions;
167
168 public void addMenu(IConfigurationElement element) {
169 if (menus == null)
170 menus = new ArrayList(1);
171 menus.add(element);
172 }
173
174 public void addAction(ActionDescriptor desc) {
175 if (actions == null)
176 actions = new ArrayList(3);
177 actions.add(desc);
178 }
179
180 /**
181 * Contributes submenus and/or actions into the provided menu and tool bar
182 * managers.
183 */
184 public void contribute(IMenuManager menu, boolean menuAppendIfMissing, IToolBarManager toolbar, boolean toolAppendIfMissing) {
185 if (menus != null && menu != null) {
186 for (int i = 0; i < menus.size(); i++) {
187 IConfigurationElement menuElement = (IConfigurationElement) menus.get(i);
188 contributeMenu(menuElement, menu, menuAppendIfMissing);
189 }
190 }
191
192 if (actions != null) {
193 for (int i = 0; i < actions.size(); i++) {
194 ActionDescriptor ad = (ActionDescriptor) actions.get(i);
195 if (menu != null)
196 contributeMenuAction(ad, menu, menuAppendIfMissing);
197 if (toolbar != null)
198 contributeToolbarAction(ad, toolbar, toolAppendIfMissing);
199 }
200 }
201 }
202
203 /**
204 * Creates a menu from the information in the menu configuration element and
205 * adds it into the provided menu manager. If 'appendIfMissing' is true, and
206 * menu path slot is not found, it will be created and menu will be added
207 * into it. Otherwise, add operation will fail.
208 */
209 protected void contributeMenu(IConfigurationElement menuElement, IMenuManager mng, boolean appendIfMissing) {
210 // Get config data.
211 String id = menuElement.getAttribute(ATT_ID);
212 String label = menuElement.getAttribute(ATT_LABEL);
213 String path = menuElement.getAttribute(ATT_PATH);
214 if (label == null) {
215 WorkbenchPlugin.log("Invalid Menu Extension (label == null): " + id); //$NON-NLS-1$
216 return;
217 }
218
219 // Calculate menu path and group.
220 String group = null;
221 if (path != null) {
222 int loc = path.lastIndexOf('/');
223 if (loc != -1) {
224 group = path.substring(loc + 1);
225 path = path.substring(0, loc);
226 } else {
227 // assume that path represents a slot
228 // so actual path portion should be null
229 group = path;
230 path = null;
231 }
232 }
233
234 // Find parent menu.
235 IMenuManager parent = mng;
236 if (path != null) {
237 parent = mng.findMenuUsingPath(path);
238 if (parent == null) {
239 WorkbenchPlugin.log("Invalid Menu Extension (Path is invalid): " + id); //$NON-NLS-1$
240 return;
241 }
242 }
243
244 // Find reference group.
245 if (group == null)
246 group = IWorkbenchActionConstants.MB_ADDITIONS;
247 IContributionItem sep = parent.find(group);
248 if (sep == null) {
249 if (appendIfMissing)
250 addGroup(parent, group);
251 else {
252 WorkbenchPlugin.log("Invalid Menu Extension (Group is invalid): " + id); //$NON-NLS-1$
253 return;
254 }
255 }
256
257 // If the menu does not exist create it.
258 IMenuManager newMenu = parent.findMenuUsingPath(id);
259 if (newMenu == null)
260 newMenu = new MenuManager(label, id);
261
262 // Add the menu
263 try {
264 insertAfter(parent, group, newMenu);
265 } catch (IllegalArgumentException e) {
266 WorkbenchPlugin.log("Invalid Menu Extension (Group is missing): " + id); //$NON-NLS-1$
267 }
268
269 // Get the menu again as it may be wrapped, otherwise adding
270 // the separators and group markers below will not be wrapped
271 // properly if the menu was just created.
272 newMenu = parent.findMenuUsingPath(id);
273 if (newMenu == null)
274 WorkbenchPlugin.log("Could not find new menu: " + id); //$NON-NLS-1$
275
276 // Create separators.
277 IConfigurationElement[] children = menuElement.getChildren();
278 for (int i = 0; i < children.length; i++) {
279 String childName = children[i].getName();
280 if (childName.equals(TAG_SEPARATOR)) {
281 contributeSeparator(newMenu, children[i]);
282 } else if (childName.equals(TAG_GROUP_MARKER)) {
283 contributeGroupMarker(newMenu, children[i]);
284 }
285 }
286 }
287
288 /**
289 * Contributes action from action descriptor into the provided menu manager.
290 */
291 protected void contributeMenuAction(ActionDescriptor ad, IMenuManager menu, boolean appendIfMissing) {
292 // Get config data.
293 String mpath = ad.getMenuPath();
294 String mgroup = ad.getMenuGroup();
295 if (mpath == null && mgroup == null)
296 return;
297
298 // Find parent menu.
299 IMenuManager parent = menu;
300 if (mpath != null) {
301 parent = parent.findMenuUsingPath(mpath);
302 if (parent == null) {
303 WorkbenchPlugin.log("Invalid Menu Extension (Path is invalid): " + ad.getId()); //$NON-NLS-1$
304 return;
305 }
306 }
307
308 // Find reference group.
309 if (mgroup == null)
310 mgroup = IWorkbenchActionConstants.MB_ADDITIONS;
311 IContributionItem sep = parent.find(mgroup);
312 if (sep == null) {
313 if (appendIfMissing)
314 addGroup(parent, mgroup);
315 else {
316 WorkbenchPlugin.log("Invalid Menu Extension (Group is invalid): " + ad.getId()); //$NON-NLS-1$
317 return;
318 }
319 }
320
321 // Add action.
322 try {
323 insertAfter(parent, mgroup, ad.getAction());
324 } catch (IllegalArgumentException e) {
325 WorkbenchPlugin.log("Invalid Menu Extension (Group is missing): " + ad.getId()); //$NON-NLS-1$
326 }
327 }
328
329 /**
330 * Creates a named menu separator from the information in the configuration element.
331 * If the separator already exists do not create a second.
332 */
333 protected void contributeSeparator(IMenuManager menu, IConfigurationElement element) {
334 String id = element.getAttribute(ATT_NAME);
335 if (id == null || id.length() <= 0)
336 return;
337 IContributionItem sep = menu.find(id);
338 if (sep != null)
339 return;
340 insertMenuGroup(menu, new Separator(id));
341 }
342
343 /**
344 * Creates a named menu group marker from the information in the configuration element.
345 * If the marker already exists do not create a second.
346 */
347 protected void contributeGroupMarker(IMenuManager menu, IConfigurationElement element) {
348 String id = element.getAttribute(ATT_NAME);
349 if (id == null || id.length() <= 0)
350 return;
351 IContributionItem marker = menu.find(id);
352 if (marker != null)
353 return;
354 insertMenuGroup(menu, new GroupMarker(id));
355 }
356
357 /**
358 * Contributes action from the action descriptor into the provided tool bar manager.
359 */
360 protected void contributeToolbarAction(ActionDescriptor ad, IToolBarManager toolbar, boolean appendIfMissing) {
361 // Get config data.
362 String tId = ad.getToolbarId();
363 String tgroup = ad.getToolbarGroupId();
364 if (tId == null && tgroup == null)
365 return;
366
367 // Find reference group.
368 if (tgroup == null)
369 tgroup = IWorkbenchActionConstants.MB_ADDITIONS;
370 IContributionItem sep = null;
371 sep = toolbar.find(tgroup);
372 if (sep == null) {
373 if (appendIfMissing) {
374 addGroup(toolbar, tgroup);
375 } else {
376 WorkbenchPlugin.log("Invalid Toolbar Extension (Group is invalid): " + ad.getId()); //$NON-NLS-1$
377 return;
378 }
379 }
380 // Add action to tool bar.
381 try {
382 insertAfter(toolbar, tgroup, ad.getAction());
383 } catch (IllegalArgumentException e) {
384 WorkbenchPlugin.log("Invalid Toolbar Extension (Group is missing): " + ad.getId()); //$NON-NLS-1$
385 }
386 }
387
388 /**
389 * Inserts the separator or group marker into the menu. Subclasses may override.
390 */
391 protected void insertMenuGroup(IMenuManager menu, AbstractGroupMarker marker) {
392 menu.add(marker);
393 }
394
395 /**
396 * Inserts an action after another named contribution item.
397 * Subclasses may override.
398 */
399 protected void insertAfter(IContributionManager mgr, String refId, PluginAction action) {
400 insertAfter(mgr, refId, new PluginActionContributionItem(action));
401 }
402
403 /**
404 * Inserts a contribution item after another named contribution item.
405 * Subclasses may override.
406 */
407 protected void insertAfter(IContributionManager mgr, String refId, IContributionItem item) {
408 mgr.insertAfter(refId, item);
409 }
410
411 /**
412 * Adds a group to a contribution manager.
413 * Subclasses may override.
414 */
415 protected void addGroup(IContributionManager mgr, String name) {
416 mgr.add(new Separator(name));
417 }
418 }
419 }