Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/jface/action/MenuManager.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.jface.action;
12  
13  import java.util.ArrayList;
14  import java.util.Iterator;
15  import java.util.List;
16  
17  import org.eclipse.swt.SWT;
18  import org.eclipse.swt.events.MenuAdapter;
19  import org.eclipse.swt.events.MenuEvent;
20  import org.eclipse.swt.widgets.Composite;
21  import org.eclipse.swt.widgets.Control;
22  import org.eclipse.swt.widgets.CoolBar;
23  import org.eclipse.swt.widgets.Decorations;
24  import org.eclipse.swt.widgets.Menu;
25  import org.eclipse.swt.widgets.MenuItem;
26  import org.eclipse.swt.widgets.Shell;
27  import org.eclipse.swt.widgets.ToolBar;
28  
29  import org.eclipse.jface.util.ListenerList;
30  
31  /**
32   * A menu manager is a contribution manager which realizes itself and its items
33   * in a menu control; either as a menu bar, a sub-menu, or a context menu.
34   * <p>
35   * This class may be instantiated; it may also be subclassed.
36   * </p>
37   */
38  public class MenuManager extends ContributionManager implements IMenuManager {
39  
40    /**
41     * The menu id.
42     */
43    private String id;
44  
45    /**
46     * List of registered menu listeners (element type: <code>IMenuListener</code>).
47     */
48    private ListenerList listeners = new ListenerList(1);
49  
50    /**
51     * The menu control; <code>null</code> before
52     * creation and after disposal.
53     */
54    private Menu menu = null;
55  
56    /**
57     * The menu item widget; <code>null</code> before
58     * creation and after disposal. This field is used
59     * when this menu manager is a sub-menu.
60     */
61    private MenuItem menuItem;
62  
63    /**
64     * The text for a sub-menu.
65     */
66    private String menuText;
67  
68    /**
69     * The overrides for items of this manager
70     */
71    private IContributionManagerOverrides overrides;
72  
73    /**
74     * The parent contribution manager.
75     */
76    private IContributionManager parent;
77  
78    /**
79     * Indicates whether <code>removeAll</code> should be
80     * called just before the menu is displayed.
81     */
82    private boolean removeAllWhenShown = false;
83  
84    /**
85     * Indicates this item is visible in its manager; <code>true</code> 
86     * by default.
87     */
88    private boolean visible = true;
89  
90    /**
91     * Creates a menu manager.  The text and id are <code>null</code>.
92     * Typically used for creating a context menu, where it doesn't need to be referred to by id.
93     */
94    public MenuManager() {
95      this(null, null);
96    }
97  
98    /**
99     * Creates a menu manager with the given text. The id of the menu
100    * is <code>null</code>.
101    * Typically used for creating a sub-menu, where it doesn't need to be referred to by id.
102    *
103    * @param text the text for the menu, or <code>null</code> if none
104    */
105   public MenuManager(String text) {
106     this(text, null);
107   }
108 
109   /**
110    * Creates a menu manager with the given text and id.
111    * Typically used for creating a sub-menu, where it needs to be referred to by id.
112    *
113    * @param text the text for the menu, or <code>null</code> if none
114    * @param id the menu id, or <code>null</code> if it is to have no id
115    */
116   public MenuManager(String text, String id) {
117     this.menuText = text;
118     this.id = id;
119   }
120 
121   /* (non-Javadoc)
122    * @see org.eclipse.jface.action.IMenuManager#addMenuListener(org.eclipse.jface.action.IMenuListener)
123    */
124   public void addMenuListener(IMenuListener listener) {
125     listeners.add(listener);
126   }
127 
128   /**
129    * Creates and returns an SWT context menu control for this menu,
130    * and installs all registered contributions.
131    * Does not create a new control if one already exists.
132    * <p>
133    * Note that the menu is not expected to be dynamic.
134    * </p>
135    *
136    * @param parent the parent control
137    * @return the menu control
138    */
139   public Menu createContextMenu(Control parent) {
140     if (!menuExist()) {
141       menu = new Menu(parent);
142       initializeMenu();
143     }
144     return menu;
145   }
146 
147   /**
148    * Creates and returns an SWT menu bar control for this menu,
149    * for use in the given <code>Decorations</code>, and installs all registered
150    * contributions. Does not create a new control if one already exists.
151    *
152    * @param parent the parent decorations
153    * @return the menu control
154    * @since 2.1
155    */
156   public Menu createMenuBar(Decorations parent) {
157     if (!menuExist()) {
158       menu = new Menu(parent, SWT.BAR);
159       update(false);
160     }
161     return menu;
162   }
163 
164   /**
165    * Creates and returns an SWT menu bar control for this menu, for use in the
166    * given <code>Shell</code>, and installs all registered contributions. Does not
167    * create a new control if one already exists. This implementation simply calls
168    * the <code>createMenuBar(Decorations)</code> method
169    *
170    * @param parent the parent decorations
171    * @return the menu control
172    * @deprecated use <code>createMenuBar(Decorations)</code> instead.
173    */
174   public Menu createMenuBar(Shell parent) {
175     return createMenuBar((Decorations) parent);
176   }
177 
178   /**
179    * Disposes of this menu manager and frees all allocated SWT resources.
180    * Notifies all contribution items of the dispose. Note that this method does
181    * not clean up references between this menu manager and its associated
182    * contribution items. Use <code>removeAll</code> for that purpose.
183    */
184   public void dispose() {
185     if (menuExist())
186       menu.dispose();
187     menu = null;
188 
189     if (menuItem != null) {
190       menuItem.dispose();
191       menuItem = null;
192     }
193 
194     IContributionItem[] items = getItems();
195     for (int i = 0; i < items.length; i++) {
196       items[i].dispose();
197     }
198   }
199 
200   /* (non-Javadoc)
201    * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.Composite)
202    */
203   public void fill(Composite parent) {
204   }
205 
206   /* (non-Javadoc)
207    * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.CoolBar, int)
208    */
209   public void fill(CoolBar parent, int index) {
210   }
211 
212   /* (non-Javadoc)
213    * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.Menu, int)
214    */
215   public void fill(Menu parent, int index) {
216     if (menuItem == null || menuItem.isDisposed()) {
217       if (index >= 0)
218         menuItem = new MenuItem(parent, SWT.CASCADE, index);
219       else
220         menuItem = new MenuItem(parent, SWT.CASCADE);
221 
222       menuItem.setText(getMenuText());
223 
224       if (!menuExist())
225         menu = new Menu(parent);
226 
227       menuItem.setMenu(menu);
228 
229       initializeMenu();
230 
231       // populate the submenu, in order to enable accelerators
232       // and to set enabled state on the menuItem properly
233       update(true);
234     }
235   }
236 
237   /* (non-Javadoc)
238    * @see org.eclipse.jface.action.IContributionItem#fill(org.eclipse.swt.widgets.ToolBar, int)
239    */
240   public void fill(ToolBar parent, int index) {
241   }
242 
243   /* (non-Javadoc)
244    * @see org.eclipse.jface.action.IMenuManager#findMenuUsingPath(java.lang.String)
245    */
246   public IMenuManager findMenuUsingPath(String path) {
247     IContributionItem item = findUsingPath(path);
248     if (item instanceof IMenuManager)
249       return (IMenuManager) item;
250     return null;
251   }
252 
253   /* (non-Javadoc)
254    * @see org.eclipse.jface.action.IMenuManager#findUsingPath(java.lang.String)
255    */
256   public IContributionItem findUsingPath(String path) {
257     String id = path;
258     String rest = null;
259     int separator = path.indexOf('/');
260     if (separator != -1) {
261       id = path.substring(0, separator);
262       rest = path.substring(separator + 1);
263     } else {
264       return super.find(path);
265     }
266 
267     IContributionItem item = super.find(id);
268     if (item instanceof IMenuManager) {
269       IMenuManager manager = (IMenuManager) item;
270       return manager.findUsingPath(rest);
271     }
272     return null;
273   }
274 
275   /**
276    * Notifies any menu listeners that a menu is about to show.
277    * Only listeners registered at the time this method is called are notified.
278    *
279    * @param manager the menu manager
280    *
281    * @see IMenuListener#menuAboutToShow
282    */
283   private void fireAboutToShow(IMenuManager manager) {
284     Object[] listeners = this.listeners.getListeners();
285     for (int i = 0; i < listeners.length; ++i) {
286       ((IMenuListener) listeners[i]).menuAboutToShow(manager);
287     }
288   }
289 
290   /**
291    * Returns the menu id.
292    * The menu id is used when creating a contribution item 
293    * for adding this menu as a sub menu of another.
294    *
295    * @return the menu id
296    */
297   public String getId() {
298     return id;
299   }
300 
301   /**
302    * Returns the SWT menu control for this menu manager.
303    *
304    * @return the menu control
305    */
306   public Menu getMenu() {
307     return menu;
308   }
309 
310   /**
311    * Returns the text shown in the menu.
312    *
313    * @return the menu text
314    */
315   public String getMenuText() {
316     return menuText;
317   }
318 
319   /* (non-Javadoc)
320    * @see org.eclipse.jface.action.IContributionManager#getOverrides()
321    */
322   public IContributionManagerOverrides getOverrides() {
323     if (overrides == null) {
324       if (parent == null) {
325         overrides = new IContributionManagerOverrides() {
326           public Integer getAccelerator(IContributionItem item) {
327             return null;
328           }
329           public String getAcceleratorText(IContributionItem item) {
330             return null;
331           }
332           public Boolean getEnabled(IContributionItem item) {
333             return null;
334           }
335           public String getText(IContributionItem item) {
336             return null;
337           }
338         };
339       } else {
340         overrides = parent.getOverrides();
341       }
342       super.setOverrides(overrides);
343     }
344     return overrides;
345   }
346 
347   /**
348    * Returns the parent contribution manager of this manger.
349    * 
350    * @return the parent contribution manager
351    * @since 2.0
352    */
353   public IContributionManager getParent() {
354     return parent;
355   }
356 
357   /* (non-Javadoc)
358    * @see org.eclipse.jface.action.IMenuManager#getRemoveAllWhenShown()
359    */
360   public boolean getRemoveAllWhenShown() {
361     return removeAllWhenShown;
362   }
363 
364   /**
365    * Notifies all listeners that this menu is about to appear.
366    */
367   private void handleAboutToShow() {
368     if (removeAllWhenShown)
369       removeAll();
370     fireAboutToShow(this);
371     update(false, true);
372   }
373 
374   /**
375    * Initializes the menu control.
376    */
377   private void initializeMenu() {
378     menu.addMenuListener(new MenuAdapter() {
379       public void menuHidden(MenuEvent e) {
380         //      ApplicationWindow.resetDescription(e.widget);
381       }
382       public void menuShown(MenuEvent e) {
383         handleAboutToShow();
384       }
385     });
386     markDirty();
387     // Don't do an update(true) here, in case menu is never opened.
388     // Always do it lazily in handleAboutToShow().
389   }
390 
391   /* (non-Javadoc)
392    * @see org.eclipse.jface.action.IContributionItem#isDynamic()
393    */
394   public boolean isDynamic() {
395     return false;
396   }
397 
398   /**
399    * Returns whether this menu should be enabled or not.
400    * Used to enable the menu item containing this menu when it is realized as a sub-menu.
401    * <p>
402    * The default implementation of this framework method
403    * returns <code>true</code>. Subclasses may reimplement.
404    * </p>
405    *
406    * @return <code>true</code> if enabled, and
407    *   <code>false</code> if disabled
408    */
409   public boolean isEnabled() {
410     return true;
411   }
412 
413   /* (non-Javadoc)
414    * @see org.eclipse.jface.action.IContributionItem#isGroupMarker()
415    */
416   public boolean isGroupMarker() {
417     return false;
418   }
419 
420   /* (non-Javadoc)
421    * @see org.eclipse.jface.action.IContributionItem#isSeparator()
422    */
423   public boolean isSeparator() {
424     return false;
425   }
426 
427   /**
428    * @deprecated this method is no longer a part of the 
429    *   {@link org.eclipse.jface.action.IContributionItem} API.
430    */
431   public boolean isSubstituteFor(IContributionItem item) {
432     return this.equals(item);
433   }
434 
435   /* (non-Javadoc)
436    * @see org.eclipse.jface.action.IContributionItem#isVisible()
437    */
438   public boolean isVisible() {  
439     if (!visible)
440       return false; // short circut calculations in this case
441     
442     // menus arent visible if all of its children are invisible (or only contains visible separators).
443     IContributionItem [] childItems = getItems();
444     boolean visibleChildren = false;
445     for (int j = 0; j < childItems.length; j++) {
446       if (childItems[j].isVisible() && !childItems[j].isSeparator()) {
447         visibleChildren = true;
448         break;
449       }
450     }
451     
452     return visibleChildren;  
453   }
454 
455   /**
456    * Returns whether the menu control is created
457    * and not disposed.
458    * 
459    * @return <code>true</code> if the control is created
460    *  and not disposed, <code>false</code> otherwise
461    */
462   private boolean menuExist() {
463     return menu != null && !menu.isDisposed();
464   }
465 
466   /* (non-Javadoc)
467    * @see org.eclipse.jface.action.IMenuManager#removeMenuListener(org.eclipse.jface.action.IMenuListener)
468    */
469   public void removeMenuListener(IMenuListener listener) {
470     listeners.remove(listener);
471   }
472 
473   /* (non-Javadoc)
474    * @see org.eclipse.jface.action.IContributionItem#saveWidgetState()
475    */
476   public void saveWidgetState() {
477   }
478 
479   /**
480    * Sets the overrides for this contribution manager
481    * 
482    * @param newOverrides the overrides for the items of this manager
483    * @since 2.0
484    */
485   public void setOverrides(IContributionManagerOverrides newOverrides) {
486     overrides = newOverrides;
487     super.setOverrides(overrides);
488   }
489 
490   /* (non-Javadoc)
491    * @see org.eclipse.jface.action.IContributionItem#setParent(org.eclipse.jface.action.IContributionManager)
492    */
493   public void setParent(IContributionManager manager) {
494     parent = manager;
495   }
496 
497   /* (non-Javadoc)
498    * @see org.eclipse.jface.action.IMenuManager#setRemoveAllWhenShown(boolean)
499    */
500   public void setRemoveAllWhenShown(boolean removeAll) {
501     this.removeAllWhenShown = removeAll;
502   }
503 
504   /* (non-Javadoc)
505    * @see org.eclipse.jface.action.IContributionItem#setVisible(boolean)
506    */
507   public void setVisible(boolean visible) {
508     this.visible = visible;
509   }
510 
511   /* (non-Javadoc)
512    * @see org.eclipse.jface.action.IContributionItem#update()
513    */
514   public void update() {
515     updateMenuItem();
516   }
517 
518   /**
519    * The <code>MenuManager</code> implementation of this <code>IContributionManager</code>
520    * updates this menu, but not any of its submenus.
521    *
522    * @see #updateAll
523    */
524   public void update(boolean force) {
525     update(force, false);
526   }
527 
528   /**
529    * Incrementally builds the menu from the contribution items.
530    * This method leaves out double separators and separators in the first 
531    * or last position.
532    *
533    * @param force <code>true</code> means update even if not dirty,
534    *   and <code>false</code> for normal incremental updating
535    * @param recursive <code>true</code> means recursively update 
536    *   all submenus, and <code>false</code> means just this menu
537    */
538   protected void update(boolean force, boolean recursive) {
539     if (isDirty() || force) {
540       if (menuExist()) {
541         // clean contains all active items without double separators
542         IContributionItem[] items = getItems();
543         List clean = new ArrayList(items.length);
544         IContributionItem separator = null;
545         for (int i = 0; i < items.length; ++i) {
546           IContributionItem ci = items[i];
547           if (!ci.isVisible())
548             continue;
549           if (ci.isSeparator()) {
550             // delay creation until necessary 
551             // (handles both adjacent separators, and separator at end)
552             separator = ci;
553           } else {
554             if (separator != null) {
555               if (clean.size() > 0) // no separator if first item
556                 clean.add(separator);
557               separator = null;
558             }
559             clean.add(ci);
560           }
561         }
562 
563         // remove obsolete (removed or non active)
564         MenuItem[] mi = menu.getItems();
565 
566         for (int i = 0; i < mi.length; i++) {
567           Object data = mi[i].getData();
568 
569           if (data == null || !clean.contains(data)) {
570             mi[i].dispose();
571           } else if (
572             data instanceof IContributionItem
573               && ((IContributionItem) data).isDynamic()
574               && ((IContributionItem) data).isDirty()) {
575             mi[i].dispose();
576           }
577         }
578 
579         // add new
580         mi = menu.getItems();
581         int srcIx = 0;
582         int destIx = 0;
583 
584         for (Iterator e = clean.iterator(); e.hasNext();) {
585           IContributionItem src = (IContributionItem) e.next();
586           IContributionItem dest;
587 
588           // get corresponding item in SWT widget
589           if (srcIx < mi.length)
590             dest = (IContributionItem) mi[srcIx].getData();
591           else
592             dest = null;
593 
594           if (dest != null && src.equals(dest)) {
595             srcIx++;
596             destIx++;
597           } else if (dest != null && dest.isSeparator() && src.isSeparator()) {
598             mi[srcIx].setData(src);
599             srcIx++;
600             destIx++;
601           } else {
602             int start = menu.getItemCount();
603             src.fill(menu, destIx);
604             int newItems = menu.getItemCount() - start;
605             for (int i = 0; i < newItems; i++) {
606               MenuItem item = menu.getItem(destIx++);
607               item.setData(src);
608             }
609           }
610 
611           // May be we can optimize this call. If the menu has just
612           // been created via the call src.fill(fMenuBar, destIx) then
613           // the menu has already been updated with update(true) 
614           // (see MenuManager). So if force is true we do it again. But
615           // we can't set force to false since then information for the
616           // sub sub menus is lost.
617           if (recursive) {
618             IContributionItem item = src;
619             if (item instanceof SubContributionItem)
620               item = ((SubContributionItem) item).getInnerItem();
621             if (item instanceof IMenuManager)
622                ((IMenuManager) item).updateAll(force);
623           }
624 
625         }
626 
627         // remove any old menu items not accounted for
628         for (; srcIx < mi.length; srcIx++)
629           mi[srcIx].dispose();
630 
631         setDirty(false);
632       }
633     } else {
634       // I am not dirty. Check if I must recursivly walk down the hierarchy.
635       if (recursive) {
636         IContributionItem[] items = getItems();
637         for (int i = 0; i < items.length; ++i) {
638           IContributionItem ci = items[i];
639           if (ci instanceof IMenuManager) {
640             IMenuManager mm = (IMenuManager) ci;
641             if (mm.isVisible()) {
642               mm.updateAll(force);
643             }
644           }
645         }
646       }
647     }
648     updateMenuItem();
649   }
650 
651   /* (non-Javadoc)
652    * @see org.eclipse.jface.action.IContributionItem#update(java.lang.String)
653    */
654   public void update(String property) {
655     IContributionItem items[] = getItems();
656 
657     for (int i = 0; i < items.length; i++)
658       items[i].update(property);
659 
660     if (menu != null
661       && !menu.isDisposed()
662       && menu.getParentItem() != null
663       && IAction.TEXT.equals(property)) {
664       String text = getOverrides().getText(this);
665 
666       if (text == null)
667         text = getMenuText();
668 
669       if (text != null) {
670         ExternalActionManager.ICallback callback =
671           ExternalActionManager.getInstance().getCallback();
672 
673         if (callback != null) {
674           int index = text.indexOf('&');
675 
676           if (index >= 0 && index < text.length() - 1) {
677             char character = Character.toUpperCase(text.charAt(index + 1));
678 
679             if (callback.isAcceleratorInUse(SWT.ALT | character)) {
680               if (index == 0)
681                 text = text.substring(1);
682               else
683                 text = text.substring(0, index) + text.substring(index + 1);
684             }
685           }
686         }
687 
688         menu.getParentItem().setText(text);
689       }
690     }
691   }
692 
693   /* (non-Javadoc)
694    * @see org.eclipse.jface.action.IMenuManager#updateAll(boolean)
695    */
696   public void updateAll(boolean force) {
697     update(force, true);
698   }
699 
700   /**
701    * Updates the menu item for this sub menu.
702    * The menu item is disabled if this sub menu is empty.
703    * Does nothing if this menu is not a submenu.
704    */
705   private void updateMenuItem() {
706     /*
707      * Commented out until proper solution to enablement of
708      * menu item for a sub-menu is found. See bug 30833 for
709      * more details.
710      *  
711       if (menuItem != null && !menuItem.isDisposed() && menuExist()) {
712         IContributionItem items[] = getItems();
713         boolean enabled = false;
714         for (int i = 0; i < items.length; i++) {
715           IContributionItem item = items[i];
716           enabled = item.isEnabled();
717           if(enabled) break;
718         }
719         // Workaround for 1GDDCN2: SWT:Linux - MenuItem.setEnabled() always causes a redraw
720         if (menuItem.getEnabled() != enabled)
721           menuItem.setEnabled(enabled);
722       }
723     */
724     // Partial fix for bug #34969 - diable the menu item if no
725     // items in sub-menu (for context menus).
726     if (menuItem != null && !menuItem.isDisposed() && menuExist()) {
727       boolean enabled = menu.getItemCount() > 0;
728       // Workaround for 1GDDCN2: SWT:Linux - MenuItem.setEnabled() always causes a redraw
729       if (menuItem.getEnabled() != enabled) {
730         // We only do this for context menus (for bug #34969)
731         Menu topMenu = menu;
732         while (topMenu.getParentMenu() != null)
733           topMenu = topMenu.getParentMenu();
734         if ((topMenu.getStyle() & SWT.BAR) == 0)
735           menuItem.setEnabled(enabled);
736       }
737     }
738   }
739 }