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

Quick Search    Search Deep

Source code: org/eclipse/jface/action/ActionContributionItem.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.lang.ref.Reference;
14  import java.lang.ref.ReferenceQueue;
15  import java.lang.ref.WeakReference;
16  import java.util.HashMap;
17  import java.util.Iterator;
18  import java.util.Map;
19  
20  import org.eclipse.jface.resource.ImageDescriptor;
21  import org.eclipse.jface.util.IPropertyChangeListener;
22  import org.eclipse.jface.util.Policy;
23  import org.eclipse.jface.util.PropertyChangeEvent;
24  import org.eclipse.swt.SWT;
25  import org.eclipse.swt.graphics.GC;
26  import org.eclipse.swt.graphics.Image;
27  import org.eclipse.swt.graphics.Point;
28  import org.eclipse.swt.graphics.Rectangle;
29  import org.eclipse.swt.widgets.Button;
30  import org.eclipse.swt.widgets.Composite;
31  import org.eclipse.swt.widgets.Display;
32  import org.eclipse.swt.widgets.Event;
33  import org.eclipse.swt.widgets.Item;
34  import org.eclipse.swt.widgets.Listener;
35  import org.eclipse.swt.widgets.Menu;
36  import org.eclipse.swt.widgets.MenuItem;
37  import org.eclipse.swt.widgets.ToolBar;
38  import org.eclipse.swt.widgets.ToolItem;
39  import org.eclipse.swt.widgets.Widget;
40  
41  /**
42   * A contribution item which delegates to an action.
43   * <p>
44   * This class may be instantiated; it is not intended to be subclassed.
45   * </p>
46   */
47  public class ActionContributionItem extends ContributionItem {
48  
49    /**
50     * Mode bit: Show text on tool items, even if an image is present.
51     * If this mode bit is not set, text is only shown on tool items if there is 
52     * no image present.
53     */
54    public static int MODE_FORCE_TEXT = 1;
55    /**
56     * This is the lower bound of the continuous range of accelerator values
57     * that should be handled specially on GTK.  This is to circumnavigate the
58     * special input mode in some cases.
59     */
60    private static final int LOWER_GTK_ACCEL_BOUND = SWT.MOD1 | SWT.MOD2 | 'A';
61    /**
62     * This is the lower bound of the continuous range of accelerator values
63     * that should be handled specially on GTK.  This is to circumnavigate the
64     * special input mode in some cases.
65     */
66    private static final int UPPER_GTK_ACCEL_BOUND = SWT.MOD1 | SWT.MOD2 | 'F';
67  
68    /** a string inserted in the middle of text that has been shortened */
69    private static final String ellipsis = "..."; //$NON-NLS-1$
70    
71    /**
72       * A weakly referenced cache of image descriptors to image instances. This
73       * is used to hold images in memory for action contribution items, while
74       * they are defined. When the image descriptor becomes weakly referred to,
75       * the corresponding image will be disposed.
76       */
77      private static final class ImageCache {
78          
79          /**
80           * This class spoofs a few method calls by passing them through to the
81           * underlying weakly referred object (if available). This allows the
82           * weak reference to be used as a key in a <code>HashMap</code>.
83           * 
84           * @since 3.0
85           */
86          private static final class HashableWeakReference extends WeakReference {
87  
88              /**
89               * Constructs a new instance of <code>HashableWeakReference</code>.
90               * 
91               * @param referent
92               *            The object to refer to; may be <code>null</code>.
93               * @param referenceQueue
94               *            The reference queue to use; should not be
95               *            <code>null</code>.
96               */
97              private HashableWeakReference(final Object referent,
98                      final ReferenceQueue referenceQueue) {
99                  super(referent, referenceQueue);
100             }
101 
102             /**
103              * @see Object#hashCode()
104              */
105             public final int hashCode() {
106                 final Object referent = get();
107                 if (referent == null) { return super.hashCode(); }
108 
109                 return referent.hashCode();
110             }
111 
112             /**
113              * @see Object#equals(java.lang.Object)
114              */
115             public final boolean equals(Object object) {
116                 final Object referent = get();
117                 if (referent == null) { return super.equals(object); }
118 
119                 if (object instanceof HashableWeakReference) {
120                     object = ((HashableWeakReference) object).get();
121                 }
122 
123                 return referent.equals(object);
124             }
125         }
126 
127         /**
128          * A thread for cleaner up the reference queues as the garbage collector
129          * fills them. It takes a map and a reference queue. When an item
130          * appears in the reference queue, it uses it as a key to remove values
131          * from the map. If the value is an image, then it is disposed. To
132          * shutdown the thread, call <code>stopCleaning()</code>.
133          * 
134          * @since 3.0
135          */
136         private static class ReferenceCleanerThread extends Thread {
137             
138             /**
139              * The number of reference cleaner threads created.
140              */
141             private static int threads = 0;
142             
143             /**
144              * A marker indicating that the reference cleaner thread should
145              * exit.  This is enqueued when the thread is told to stop.  Any
146              * referenced enqueued after the thread is told to stop will not be
147              * cleaned up.
148              */
149             private final WeakReference endMarker;
150 
151             /**
152              * The reference queue to check; will not be <code>null</code>.
153              */
154             private final ReferenceQueue referenceQueue;
155 
156             /**
157              * The map from which to remove values. This value will not be
158              * <code>null</code>.
159              */
160             private final Map map;
161 
162             /**
163              * Constructs a new instance of <code>ReferenceCleanerThread</code>.
164              * 
165              * @param referenceQueue
166              *            The reference queue to check for garbage; mmmmm....
167              *            garbage. This value must not be <code>null</code>.
168              * @param map
169              *            The map to check for values; must not be
170              *            <code>null</code>. It is expected that the keys are
171              *            <code>Reference</code> instances. The values are
172              *            expected to be <code>Image</code> objects, but it is
173              *            okay if they are not.
174              */
175             private ReferenceCleanerThread(final ReferenceQueue referenceQueue,
176                     final Map map) {
177                 super("Reference Cleaner - " + ++threads); //$NON-NLS-1$
178                 
179                 if (referenceQueue == null) { throw new NullPointerException(
180                         "The reference queue should not be null."); } //$NON-NLS-1$
181 
182                 if (map == null) { throw new NullPointerException(
183                         "The map should not be null."); } //$NON-NLS-1$
184 
185                 this.endMarker = new WeakReference(referenceQueue, referenceQueue);
186                 this.referenceQueue = referenceQueue;
187                 this.map = map;
188             }
189 
190             /**
191              * Tells this thread to stop trying to clean up. This is usually run
192              * when the cache is shutting down.
193              */
194             private final void stopCleaning() {
195                 endMarker.enqueue();
196             }
197 
198             /**
199              * Waits for new garbage. When new garbage arriving, it removes it,
200              * clears it, and disposes of any corresponding images.
201              */
202             public final void run() {
203                 while (true) {
204                     // Get the next reference to dispose.
205                     Reference reference = null;
206                     try {
207                         reference = referenceQueue.remove();
208                     } catch (final InterruptedException e) {
209                         // Reference will be null.
210                     }
211                     
212                     // Check to see if we've been told to stop.
213                     if (reference == endMarker) {
214                         break;
215                     }
216 
217                     // Remove the image and dispose it.
218                     final Object value = map.remove(reference);
219                     if (value instanceof Image) {
220                         Display.getCurrent().syncExec(new Runnable() {
221 
222                             public void run() {
223                                 final Image image = (Image) value;
224                                 if (!image.isDisposed()) {
225                                     image.dispose();
226                                 }
227                             }
228                         });
229                     }
230 
231                     // Clear the reference.
232                     if (reference != null) {
233                         reference.clear();
234                     }
235                 }
236             }
237         }
238         
239         /**
240          * The thread responsible for cleaning out greyed images that are no
241          * longer needed.
242          */
243         private final ReferenceCleanerThread greyCleaner;
244         
245         /**
246          * A map of image descriptors to the corresponding greyed images. The
247          * image descriptors are actually weak references to image descriptors.
248          * As the weak references become suitable for collection, the
249          * corresponding images (i.e., native resources) will be disposed. This
250          * value may be empty, but it is never <code>null</code>.
251          */
252         private final Map greyMap = new HashMap();
253 
254         /**
255          * A queue of references waiting to be garbage collected. This value is
256          * never <code>null</code>. This is the queue for
257          * <code>greyMap</code>.
258          */
259         private final ReferenceQueue greyReferenceQueue = new ReferenceQueue();
260         
261         /**
262          * The thread responsible for cleaning out images that are no longer
263          * needed.
264          */
265         private final ReferenceCleanerThread imageCleaner;
266 
267         /**
268          * A map of image descriptors to the corresponding loaded images. The
269          * image descriptors are actually weak references to image descriptors.
270          * As the weak references become suitable for collection, the
271          * corresponding images (i.e., native resources) will be disposed. This
272          * value may be empty, but it is never <code>null</code>.
273          */
274         private final Map imageMap = new HashMap();
275 
276         /**
277          * A queue of references waiting to be garbage collected. This value is
278          * never <code>null</code>. This is the queue for
279          * <code>imageMap</code>.
280          */
281         private final ReferenceQueue imageReferenceQueue = new ReferenceQueue();
282 
283         /**
284          * The image to display when no image is available. This value is
285          * <code>null</code> until it is first used.
286          */
287         private Image missingImage = null;
288         
289         /**
290          * Constructs a new instance of <code>ImageCache</code>, and starts a
291          * couple of threads to monitor the reference queues.
292          */
293         private ImageCache() {
294             greyCleaner = new ReferenceCleanerThread(greyReferenceQueue,
295                     greyMap);
296             imageCleaner = new ReferenceCleanerThread(imageReferenceQueue,
297                     imageMap);
298             
299             greyCleaner.start();
300             imageCleaner.start();
301 
302         }
303 
304         /**
305          * Cleans up all images in the cache. This disposes of all of the
306          * images, and drops references to them. This should only be called when
307          * all of the action contribution items are disappearing.
308          */
309         private final void dispose() {
310             // Clean up the missing image.
311             if ((missingImage != null) && (!missingImage.isDisposed())) {
312                 missingImage.dispose();
313                 missingImage = null;
314             }
315 
316             /*
317              * Stop the image cleaner thread, clear all of the weak references
318              * and dispose of all of the images.
319              */
320             imageCleaner.stopCleaning();
321             final Iterator imageItr = imageMap.entrySet().iterator();
322             while (imageItr.hasNext()) {
323                 final Map.Entry entry = (Map.Entry) imageItr.next();
324 
325                 final WeakReference reference = (WeakReference) entry.getKey();
326                 reference.clear();
327 
328                 final Image image = (Image) entry.getValue();
329                 if ((image != null) && (!image.isDisposed())) {
330                     image.dispose();
331                 }
332             }
333             imageMap.clear();
334 
335             /*
336              * Stop the greyed image cleaner thread, clear all of the weak
337              * references and dispose of all of the greyed images.
338              */
339             greyCleaner.stopCleaning();
340             final Iterator greyItr = greyMap.entrySet().iterator();
341             while (greyItr.hasNext()) {
342                 final Map.Entry entry = (Map.Entry) greyItr.next();
343 
344                 final WeakReference reference = (WeakReference) entry.getKey();
345                 reference.clear();
346 
347                 final Image image = (Image) entry.getValue();
348                 if ((image != null) && (!image.isDisposed())) {
349                     image.dispose();
350                 }
351             }
352             greyMap.clear();
353         }
354 
355         /**
356          * Returns the greyed image (i.e., disabled) for the given image
357          * descriptor. This caches the result so that future attempts to get the
358          * greyed image for the same descriptor will only access the cache. When
359          * the last reference to the image descriptor is dropped, the image will
360          * be cleaned up. This clean up makes no time guarantees about how long
361          * this will take.
362          * 
363          * @param descriptor
364          *            The image descriptor for which a greyed image should be
365          *            created; may be <code>null</code>.
366          * @return The greyed image, either newly created or from the cache.
367          *         This value is <code>null</code> if the parameter passed in
368          *         is <code>null</code>.
369          */
370         private final Image getGrayImage(final ImageDescriptor descriptor) {
371             if (descriptor == null) { return null; }
372 
373             // Try to load a cached image.
374             final HashableWeakReference key = new HashableWeakReference(
375                     descriptor, imageReferenceQueue);
376             final Object value = greyMap.get(key);
377             if (value instanceof Image) {
378                 key.clear();
379                 return (Image) value;
380             }
381 
382             // Try to create a grey image from the regular image.
383             final Image image = getImage(descriptor);
384             if (image != null) {
385                 final Image greyImage = new Image(null, image, SWT.IMAGE_GRAY);
386                 greyMap.put(key, greyImage);
387                 return greyImage;
388             }
389 
390             // All attempts have failed.
391             return null;
392         }
393 
394         /**
395          * Returns the regular image (i.e., enabled) for the given image
396          * descriptor. This caches the result so that future attempts to get the
397          * image for the same descriptor will only access the cache. When the
398          * last reference to the image descriptor is dropped, the image will be
399          * cleaned up. This clean up makes no time guarantees about how long
400          * this will take.
401          * 
402          * @param descriptor
403          *            The image descriptor for which an image should be created;
404          *            may be <code>null</code>.
405          * @return The image, either newly created or from the cache. This value
406          *         is <code>null</code> if the parameter passed in is
407          *         <code>null</code>.
408          */
409         private final Image getImage(final ImageDescriptor descriptor) {
410             if (descriptor == null) { return null; }
411 
412             // Try to load the cached value.
413             final HashableWeakReference key = new HashableWeakReference(
414                     descriptor, imageReferenceQueue);
415             final Object value = imageMap.get(key);
416             if (value instanceof Image) {
417                 key.clear();
418                 return (Image) value;
419             }
420 
421             // Use the descriptor to create the image.
422             final Image image = descriptor.createImage();
423             imageMap.put(key, image);
424             return image;
425         }
426 
427         /**
428          * Returns the image to display when no image can be found, or none is
429          * specified. This image is only disposed when the cache is disposed.
430          * 
431          * @return The image to display for missing images. This value will
432          *         never be <code>null</code>.
433          */
434         private final Image getMissingImage() {
435             if (missingImage == null) {
436                 missingImage = getImage(ImageDescriptor
437                         .getMissingImageDescriptor());
438             }
439 
440             return missingImage;
441         }
442     }
443 
444   private static ImageCache globalImageCache;
445 
446   private static boolean USE_COLOR_ICONS = true;
447 
448   /**
449    * Returns whether color icons should be used in toolbars.
450    * 
451    * @return <code>true</code> if color icons should be used in toolbars, 
452    *   <code>false</code> otherwise
453    */
454   public static boolean getUseColorIconsInToolbars() {
455     return USE_COLOR_ICONS;
456   }
457 
458   /**
459    * Sets whether color icons should be used in toolbars.
460    * 
461    * @param useColorIcons <code>true</code> if color icons should be used in toolbars, 
462    *   <code>false</code> otherwise
463    */
464   public static void setUseColorIconsInToolbars(boolean useColorIcons) {
465     USE_COLOR_ICONS = useColorIcons;
466   }
467 
468   /**
469    * The presentation mode.
470    */
471   private int mode = 0;
472 
473   /**
474    * The action.
475    */
476   private IAction action;
477   
478   /**
479      * The listener for changes to the text of the action contributed by an
480      * external source.
481      */
482     private final IPropertyChangeListener actionTextListener = new IPropertyChangeListener() {
483 
484         /**
485          * @see IPropertyChangeListener#propertyChange(PropertyChangeEvent)
486          */
487         public void propertyChange(PropertyChangeEvent event) {
488             update(event.getProperty());
489         }
490     };
491 
492   /**
493    * Listener for SWT button widget events.
494    */
495   private Listener buttonListener;
496 
497   /**
498    * Listener for SWT menu item widget events.
499    */
500   private Listener menuItemListener;
501 
502   /**
503    * Listener for action property change notifications.
504    */
505   private final IPropertyChangeListener propertyListener = new IPropertyChangeListener() {
506     public void propertyChange(PropertyChangeEvent event) {
507       actionPropertyChange(event);
508     }
509   };
510 
511   /**
512    * Listener for SWT tool item widget events.
513    */
514   private Listener toolItemListener;
515 
516   /**
517    * The widget created for this item; <code>null</code>
518    * before creation and after disposal.
519    */
520   private Widget widget = null;
521 
522   /**
523      * Creates a new contribution item from the given action. The id of the
524      * action is used as the id of the item.
525      * 
526      * @param action
527      *            the action
528      */
529     public ActionContributionItem(IAction action) {
530         super(action.getId());
531         this.action = action;
532     }
533   
534   /**
535    * Handles a property change event on the action (forwarded by nested listener).
536    */
537   private void actionPropertyChange(final PropertyChangeEvent e) {
538     // This code should be removed. Avoid using free asyncExec
539 
540     if (isVisible() && widget != null) {
541       Display display = widget.getDisplay();
542       if (display.getThread() == Thread.currentThread()) {
543         update(e.getProperty());
544       } else {
545         display.asyncExec(new Runnable() {
546           public void run() {
547             update(e.getProperty());
548           }
549         });
550       }
551 
552     }
553   }
554 
555   /**
556    * Compares this action contribution item with another object.
557    * Two action contribution items are equal if they refer to the identical Action.
558    */
559   public boolean equals(Object o) {
560     if (!(o instanceof ActionContributionItem)) {
561       return false;
562     }
563     return action.equals(((ActionContributionItem) o).action);
564   }
565   /**
566    * The <code>ActionContributionItem</code> implementation of this
567    * <code>IContributionItem</code> method creates an SWT <code>Button</code> for
568    * the action using the action's style. If the action's checked property has
569    * been set, the button is created and primed to the value of the checked
570    * property.
571    */
572   public void fill(Composite parent) {
573     if (widget == null && parent != null) {
574       int flags = SWT.PUSH;
575       if (action != null) {
576         if (action.getStyle() == IAction.AS_CHECK_BOX)
577           flags = SWT.TOGGLE;
578         if (action.getStyle() == IAction.AS_RADIO_BUTTON)
579           flags = SWT.RADIO;
580       }
581 
582       Button b = new Button(parent, flags);
583       b.setData(this);
584       b.addListener(SWT.Dispose, getButtonListener());
585       // Don't hook a dispose listener on the parent
586       b.addListener(SWT.Selection, getButtonListener());
587       if (action.getHelpListener() != null)
588         b.addHelpListener(action.getHelpListener());
589       widget = b;
590 
591       update(null);
592 
593       // Attach some extra listeners.
594       action.addPropertyChangeListener(propertyListener);
595           if (action != null) {
596               String commandId = action.getActionDefinitionId();
597               ExternalActionManager.ICallback callback = ExternalActionManager.getInstance()
598                       .getCallback();
599 
600               if ((callback != null) && (commandId != null)) {
601                   callback.addPropertyChangeListener(commandId, actionTextListener);
602               }
603           }
604     }
605   }
606   /**
607    * The <code>ActionContributionItem</code> implementation of this
608    * <code>IContributionItem</code> method creates an SWT <code>MenuItem</code>
609    * for the action using the action's style. If the action's checked property has
610    * been set, a button is created and primed to the value of the checked
611    * property. If the action's menu creator property has been set, a cascading
612    * submenu is created.
613    */
614   public void fill(Menu parent, int index) {
615     if (widget == null && parent != null) {
616       Menu subMenu = null;
617       int flags = SWT.PUSH;
618       if (action != null) {
619         int style = action.getStyle();
620         if (style == IAction.AS_CHECK_BOX)
621           flags = SWT.CHECK;
622         else if (style == IAction.AS_RADIO_BUTTON)
623           flags = SWT.RADIO;
624         else if (style == IAction.AS_DROP_DOWN_MENU) {
625           IMenuCreator mc = action.getMenuCreator();
626           if (mc != null) {
627             subMenu = mc.getMenu(parent);
628             flags = SWT.CASCADE;
629           }
630         }
631       }
632 
633       MenuItem mi = null;
634       if (index >= 0)
635         mi = new MenuItem(parent, flags, index);
636       else
637         mi = new MenuItem(parent, flags);
638       widget = mi;
639 
640       mi.setData(this);
641       mi.addListener(SWT.Dispose, getMenuItemListener());
642       mi.addListener(SWT.Selection, getMenuItemListener());
643       if (action.getHelpListener() != null)
644         mi.addHelpListener(action.getHelpListener());
645 
646       if (subMenu != null)
647         mi.setMenu(subMenu);
648 
649       update(null);
650 
651       // Attach some extra listeners.
652       action.addPropertyChangeListener(propertyListener);
653           if (action != null) {
654               String commandId = action.getActionDefinitionId();
655               ExternalActionManager.ICallback callback = ExternalActionManager.getInstance()
656                       .getCallback();
657 
658               if ((callback != null) && (commandId != null)) {
659                   callback.addPropertyChangeListener(commandId, actionTextListener);
660               }
661           }
662     }
663   }
664   /**
665    * The <code>ActionContributionItem</code> implementation of this ,
666    * <code>IContributionItem</code> method creates an SWT <code>ToolItem</code>
667    * for the action using the action's style. If the action's checked property has
668    * been set, a button is created and primed to the value of the checked
669    * property. If the action's menu creator property has been set, a drop-down
670    * tool item is created.
671    */
672   public void fill(ToolBar parent, int index) {
673     if (widget == null && parent != null) {
674       int flags = SWT.PUSH;
675       if (action != null) {
676         int style = action.getStyle();
677         if (style == IAction.AS_CHECK_BOX)
678           flags = SWT.CHECK;
679         else if (style == IAction.AS_RADIO_BUTTON)
680           flags = SWT.RADIO;
681         else if (style == IAction.AS_DROP_DOWN_MENU)
682           flags = SWT.DROP_DOWN;
683       }
684 
685       ToolItem ti = null;
686       if (index >= 0)
687         ti = new ToolItem(parent, flags, index);
688       else
689         ti = new ToolItem(parent, flags);
690       ti.setData(this);
691       ti.addListener(SWT.Selection, getToolItemListener());
692       ti.addListener(SWT.Dispose, getToolItemListener());
693 
694       widget = ti;
695 
696       update(null);
697 
698       // Attach some extra listeners.
699       action.addPropertyChangeListener(propertyListener);
700           if (action != null) {
701               String commandId = action.getActionDefinitionId();
702               ExternalActionManager.ICallback callback = ExternalActionManager.getInstance()
703                       .getCallback();
704 
705               if ((callback != null) && (commandId != null)) {
706                   callback.addPropertyChangeListener(commandId, actionTextListener);
707               }
708           }
709     }
710   }
711   /**
712    * Returns the action associated with this contribution item.
713    *
714    * @return the action
715    */
716   public IAction getAction() {
717     return action;
718   }
719   /**
720    * Returns the listener for SWT button widget events.
721    * 
722    * @return a listener for button events
723    */
724   private Listener getButtonListener() {
725     if (buttonListener == null) {
726       buttonListener = new Listener() {
727         public void handleEvent(Event event) {
728           switch (event.type) {
729             case SWT.Dispose :
730               handleWidgetDispose(event);
731               break;
732             case SWT.Selection :
733               Widget ew = event.widget;
734               if (ew != null) {
735                 handleWidgetSelection(event, ((Button) ew).getSelection());
736               }
737               break;
738           }
739         }
740       };
741     }
742     return buttonListener;
743   }
744   /**
745    * Returns the image cache.
746    * The cache is global, and is shared by all action contribution items.
747    * This has the disadvantage that once an image is allocated, it is never freed until the display
748    * is disposed.  However, it has the advantage that the same image in different contribution managers
749    * is only ever created once.
750    */
751   private static ImageCache getImageCache() {
752     ImageCache cache = globalImageCache;
753     if (cache == null) {
754       globalImageCache = cache = new ImageCache();
755       Display display = Display.getDefault();
756       if (display != null) {
757         display.disposeExec(new Runnable() {
758           public void run() {
759             if (globalImageCache != null) {
760               globalImageCache.dispose();
761               globalImageCache = null;
762             }
763           }
764         });
765       }
766     }
767     return cache;
768   }
769   /**
770    * Returns the listener for SWT menu item widget events.
771    * 
772    * @return a listener for menu item events
773    */
774   private Listener getMenuItemListener() {
775     if (menuItemListener == null) {
776       menuItemListener = new Listener() {
777         public void handleEvent(Event event) {
778           switch (event.type) {
779             case SWT.Dispose :
780               handleWidgetDispose(event);
781               break;
782             case SWT.Selection :
783               Widget ew = event.widget;
784               if (ew != null) {
785                 handleWidgetSelection(event, ((MenuItem) ew).getSelection());
786               }
787               break;
788           }
789         }
790       };
791     }
792     return menuItemListener;
793   }
794   /**
795    * Returns the presentation mode, which is the bitwise-or of the 
796    * <code>MODE_*</code> constants.  The default mode setting is 0, meaning
797    * that for menu items, both text and image are shown (if present), but for
798    * tool items, the text is shown only if there is no image.
799    * 
800    * @return the presentation mode settings
801    * 
802    * @since 3.0
803    */
804   public int getMode() {
805     return mode;
806   }
807   
808   /**
809    * Returns the listener for SWT tool item widget events.
810    * 
811    * @return a listener for tool item events
812    */
813   private Listener getToolItemListener() {
814     if (toolItemListener == null) {
815       toolItemListener = new Listener() {
816         public void handleEvent(Event event) {
817           switch (event.type) {
818             case SWT.Dispose :
819               handleWidgetDispose(event);
820               break;
821             case SWT.Selection :
822               Widget ew = event.widget;
823               if (ew != null) {
824                 handleWidgetSelection(event, ((ToolItem) ew).getSelection());
825               }
826               break;
827           }
828         }
829       };
830     }
831     return toolItemListener;
832   }
833   /**
834    * Handles a widget dispose event for the widget corresponding to this item.
835    */
836   private void handleWidgetDispose(Event e) {
837       // Check if our widget is the one being disposed.
838     if (e.widget == widget) {
839       // Dispose of the menu creator.
840       if (action.getStyle() == IAction.AS_DROP_DOWN_MENU) {
841         IMenuCreator mc = action.getMenuCreator();
842         if (mc != null) {
843           mc.dispose();
844         }
845       }
846       
847       // Unhook all of the listeners.
848       action.removePropertyChangeListener(propertyListener);
849           if (action != null) {
850               String commandId = action.getActionDefinitionId();
851               ExternalActionManager.ICallback callback = ExternalActionManager.getInstance()
852                       .getCallback();
853 
854               if ((callback != null) && (commandId != null)) {
855                   callback.removePropertyChangeListener(commandId, actionTextListener);
856               }
857           }
858           
859           // Clear the widget field.
860       widget = null;
861     }
862   }
863   /**
864    * Handles a widget selection event.
865    */
866   private void handleWidgetSelection(Event e, boolean selection) {
867 
868     Widget item = e.widget;
869     if (item != null) {
870       int style = item.getStyle();
871 
872       if ((style & (SWT.TOGGLE | SWT.CHECK)) != 0) {
873         if (action.getStyle() == IAction.AS_CHECK_BOX) {
874           action.setChecked(selection);
875         }
876       } else if ((style & SWT.RADIO) != 0) {
877         if (action.getStyle() == IAction.AS_RADIO_BUTTON) {
878           action.setChecked(selection);
879         }
880       } else if ((style & SWT.DROP_DOWN) != 0) {
881         if (e.detail == 4) { // on drop-down button
882           if (action.getStyle() == IAction.AS_DROP_DOWN_MENU) {
883             IMenuCreator mc = action.getMenuCreator();
884             ToolItem ti = (ToolItem) item;
885             // we create the menu as a sub-menu of "dummy" so that we can use
886             // it in a cascading menu too.
887             // If created on a SWT control we would get an SWT error...
888             //Menu dummy= new Menu(ti.getParent());
889             //Menu m= mc.getMenu(dummy);
890             //dummy.dispose();
891             if (mc != null) {
892               Menu m = mc.getMenu(ti.getParent());
893               if (m != null) {
894                 // position the menu below the drop down item
895                 Rectangle b = ti.getBounds();
896                 Point p = ti.getParent().toDisplay(new Point(b.x, b.y + b.height));
897                 m.setLocation(p.x, p.y); // waiting for SWT 0.42
898                 m.setVisible(true);
899                 return; // we don't fire the action
900               }
901             }
902           }
903         }
904       }
905 
906       // Ensure action is enabled first.
907       // See 1GAN3M6: ITPUI:WINNT - Any IAction in the workbench can be executed while disabled.
908       if (action.isEnabled()) {
909         boolean trace = Policy.TRACE_ACTIONS;
910         
911         long ms = System.currentTimeMillis();
912         if (trace)
913           System.out.println("Running action: " + action.getText()); //$NON-NLS-1$
914 
915         action.runWithEvent(e);
916 
917         if (trace)
918           System.out.println((System.currentTimeMillis() - ms) + " ms to run action: " + action.getText()); //$NON-NLS-1$
919       }
920     }
921   }
922   /* (non-Javadoc)
923    * Method declared on Object.
924    */
925   public int hashCode() {
926     return action.hashCode();
927   }
928 
929   /**
930    * Returns whether the given action has any images.
931    * 
932    * @param actionToCheck the action
933    * @return <code>true</code> if the action has any images, <code>false</code> if not
934    */
935   private boolean hasImages(IAction actionToCheck) {
936     return actionToCheck.getImageDescriptor() != null
937       || actionToCheck.getHoverImageDescriptor() != null
938       || actionToCheck.getDisabledImageDescriptor() != null;
939   }
940 
941   /**
942    * Returns whether the command corresponding to this action
943    * is active.
944    */
945   private boolean isCommandActive() {
946     IAction actionToCheck = getAction();
947 
948     if (actionToCheck != null) {
949       String commandId = actionToCheck.getActionDefinitionId();
950       ExternalActionManager.ICallback callback = ExternalActionManager.getInstance().getCallback();
951 
952       if (callback != null)
953         return callback.isActive(commandId);
954     }
955     return true;
956   }
957   /**
958    * The action item implementation of this <code>IContributionItem</code>
959    * method returns <code>true</code> for menu items and <code>false</code>
960    * for everything else.
961    */
962   public boolean isDynamic() {
963     if (widget instanceof MenuItem) {
964       //Optimization. Only recreate the item is the check or radio style has changed. 
965       boolean itemIsCheck = (widget.getStyle() & SWT.CHECK) != 0;
966       boolean actionIsCheck =
967         getAction() != null && getAction().getStyle() == IAction.AS_CHECK_BOX;
968       boolean itemIsRadio = (widget.getStyle() & SWT.RADIO) != 0;
969       boolean actionIsRadio =
970         getAction() != null && getAction().getStyle() == IAction.AS_RADIO_BUTTON;
971       return (itemIsCheck != actionIsCheck) || (itemIsRadio != actionIsRadio);
972     }
973     return false;
974   }
975 
976   /* (non-Javadoc)
977    * Method declared on IContributionItem.
978    */
979   public boolean isEnabled() {
980     return action != null && action.isEnabled();
981   }
982   /**
983    * Returns <code>true</code> if this item is allowed to enable,
984    * <code>false</code> otherwise.
985    * 
986    * @return if this item is allowed to be enabled
987    * @since 2.0
988    */
989   protected boolean isEnabledAllowed() {
990     if (getParent() == null)
991       return true;
992     Boolean value = getParent().getOverrides().getEnabled(this);
993     return (value == null) ? true : value.booleanValue();
994   }
995 
996   /**
997    * The <code>ActionContributionItem</code> implementation of this 
998    * <code>ContributionItem</code> method extends the super implementation
999    * by also checking whether the command corresponding to this action is active.
1000   */
1001  public boolean isVisible() {
1002    return super.isVisible() && isCommandActive();
1003  }
1004
1005  /**
1006   * Sets the presentation mode, which is the bitwise-or of the 
1007   * <code>MODE_*</code> constants.
1008   * 
1009   * @return the presentation mode settings
1010   * 
1011   * @since 3.0
1012   */
1013  public void setMode(int mode) {
1014    this.mode = mode;
1015    update();
1016  }
1017  
1018  /**
1019   * The action item implementation of this <code>IContributionItem</code>
1020   * method calls <code>update(null)</code>.
1021   */
1022  public final void update() {
1023    update(null);
1024  }
1025  /**
1026   * Synchronizes the UI with the given property.
1027   *
1028   * @param propertyName the name of the property, or <code>null</code> meaning all applicable
1029   *   properties 
1030   */
1031  public void update(String propertyName) {
1032    if (widget != null) {
1033      // determine what to do      
1034      boolean textChanged = propertyName == null || propertyName.equals(IAction.TEXT);
1035      boolean imageChanged = propertyName == null || propertyName.equals(IAction.IMAGE);
1036      boolean tooltipTextChanged =
1037        propertyName == null || propertyName.equals(IAction.TOOL_TIP_TEXT);
1038      boolean enableStateChanged =
1039        propertyName == null
1040          || propertyName.equals(IAction.ENABLED)
1041          || propertyName.equals(IContributionManagerOverrides.P_ENABLED);
1042      boolean checkChanged =
1043        (action.getStyle() == IAction.AS_CHECK_BOX
1044          || action.getStyle() == IAction.AS_RADIO_BUTTON)
1045          && (propertyName == null || propertyName.equals(IAction.CHECKED));
1046
1047      if (widget instanceof ToolItem) {
1048        ToolItem ti = (ToolItem) widget;
1049        String text = action.getText();
1050        // the set text is shown only if there is no image or if forced by MODE_FORCE_TEXT
1051        boolean showText = text != null && ((getMode() & MODE_FORCE_TEXT) != 0 || !hasImages(action));
1052        
1053        // only do the trimming if the text will be used
1054        if (showText && text != null) {
1055          text = Action.removeAcceleratorText(text);
1056          text = Action.removeMnemonics(text);
1057        }
1058
1059        if (textChanged) {
1060          String textToSet = showText ? text : ""; //$NON-NLS-1$
1061          boolean rightStyle = (ti.getParent().getStyle() & SWT.RIGHT) != 0;
1062          if (rightStyle || !ti.getText().equals(textToSet)) {
1063            // In addition to being required to update the text if it
1064            // gets nulled out in the action, this is also a workaround 
1065            // for bug 50151: Using SWT.RIGHT on a ToolBar leaves blank space
1066            ti.setText(textToSet);
1067          }
1068        }
1069
1070        if (imageChanged) {
1071          // only substitute a missing image if it has no text
1072          updateImages(!showText);
1073        }
1074
1075        if (tooltipTextChanged || textChanged) {
1076          String toolTip = action.getToolTipText();
1077          // if the text is showing, then only set the tooltip if different
1078          if (!showText || toolTip != null && !toolTip.equals(text)) {
1079            ti.setToolTipText(action.getToolTipText());
1080          }
1081          else {
1082            ti.setToolTipText(null);
1083          }
1084        }
1085
1086        if (enableStateChanged) {
1087          boolean shouldBeEnabled = action.isEnabled() && isEnabledAllowed();
1088
1089          if (ti.getEnabled() != shouldBeEnabled)
1090            ti.setEnabled(shouldBeEnabled);
1091        }
1092
1093        if (checkChanged) {
1094          boolean bv = action.isChecked();
1095
1096          if (ti.getSelection() != bv)
1097            ti.setSelection(bv);
1098        }
1099        return;
1100      }
1101
1102      if (widget instanceof MenuItem) {
1103        MenuItem mi = (MenuItem) widget;
1104
1105        if (textChanged) {
1106          int accelerator = 0;
1107          String acceleratorText = null;
1108          IAction updatedAction = getAction();
1109          String text = null;
1110
1111          // Set the accelerator using the action's accelerator.
1112          accelerator = updatedAction.getAccelerator();
1113          
1114          /* Process accelerators on GTK in a special way to avoid
1115           * Bug 42009.  We will override the native input method by
1116           * allowing these reserved accelerators to be placed on the
1117           * menu.  We will only do this for "Ctrl+Shift+[A-F]".
1118           */
1119          ExternalActionManager.ICallback callback =
1120            ExternalActionManager.getInstance().getCallback();
1121          String commandId = updatedAction.getActionDefinitionId();
1122          if (SWT.getPlatform().equals("gtk")) { //$NON-NLS-1$
1123            if ((callback != null) && (commandId != null)) {
1124                Integer commandAccelerator = callback.getAccelerator(commandId);
1125                if (commandAccelerator != null) {
1126                    int accelInt = callback.getAccelerator(commandId).intValue();
1127                    if ((accelInt >= LOWER_GTK_ACCEL_BOUND) && (accelInt <= UPPER_GTK_ACCEL_BOUND)) {
1128                        accelerator = accelInt;
1129                        acceleratorText = callback.getAcceleratorText(commandId);
1130                    }
1131                }
1132              }
1133          }
1134          
1135          if (accelerator == 0) {
1136            if ((callback != null) && (commandId != null)) {
1137                acceleratorText = callback.getAcceleratorText(commandId);
1138            }
1139          } else {
1140            acceleratorText = Action.convertAccelerator(accelerator);
1141          }
1142
1143          IContributionManagerOverrides overrides = null;
1144
1145          if (getParent() != null)
1146            overrides = getParent().getOverrides();
1147
1148          if (overrides != null)
1149            text = getParent().getOverrides().getText(this);
1150
1151          mi.setAccelerator(accelerator);
1152
1153          if (text == null)
1154            text = updatedAction.getText();
1155
1156          if (text == null)
1157            text = ""; //$NON-NLS-1$
1158          else
1159            text = Action.removeAcceleratorText(text);
1160
1161          if (acceleratorText == null)
1162            mi.setText(text);
1163          else
1164            mi.setText(text + '\t' + acceleratorText);
1165        }
1166
1167        if (imageChanged)
1168          updateImages(false);
1169
1170        if (enableStateChanged) {
1171          boolean shouldBeEnabled = action.isEnabled() && isEnabledAllowed();
1172
1173          if (mi.getEnabled() != shouldBeEnabled)
1174            mi.setEnabled(shouldBeEnabled);
1175        }
1176
1177        if (checkChanged) {
1178          boolean bv = action.isChecked();
1179
1180          if (mi.getSelection() != bv)
1181            mi.setSelection(bv);
1182        }
1183
1184        return;
1185      }
1186
1187      if (widget instanceof Button) {
1188        Button button = (Button) widget;
1189
1190        if (imageChanged && updateImages(false))
1191          textChanged = false; // don't update text if it has an image
1192
1193        if (textChanged) {
1194          String text = action.getText();
1195
1196          if (text != null)
1197            button.setText(text);
1198        }
1199
1200        if (tooltipTextChanged)
1201          button.setToolTipText(action.getToolTipText());
1202
1203        if (enableStateChanged) {
1204          boolean shouldBeEnabled = action.isEnabled() && isEnabledAllowed();
1205
1206          if (button.getEnabled() != shouldBeEnabled)
1207            button.setEnabled(shouldBeEnabled);
1208        }
1209
1210        if (checkChanged) {
1211          boolean bv = action.isChecked();
1212
1213          if (button.getSelection() != bv)
1214            button.setSelection(bv);
1215        }
1216        return;
1217      }
1218    }
1219  }
1220  /**
1221   * Updates the images for this action.
1222   *
1223   * @param forceImage <code>true</code> if some form of image is compulsory,
1224   *  and <code>false</code> if it is acceptable for this item to have no image
1225   * @return <code>true</code> if there are images for this action, <code>false</code> if not
1226   */
1227  private boolean updateImages(boolean forceImage) {
1228    ImageCache cache = getImageCache();
1229
1230    if (widget instanceof ToolItem) {
1231      if (USE_COLOR_ICONS) {
1232        Image image = cache.getImage(action.getHoverImageDescriptor());
1233        if (image == null) {
1234          image = cache.getImage(action.getImageDescriptor());
1235        }
1236        Image disabledImage = cache.getImage(action.getDisabledImageDescriptor());
1237
1238        // Make sure there is a valid image.
1239        if (image == null && forceImage) {
1240          image = cache.getMissingImage();
1241        }
1242
1243        // performance: more efficient in SWT to set disabled and hot image before regular image
1244        if (disabledImage != null) {
1245          // Set the disabled image if we were able to create one.
1246          // Assumes that SWT.ToolItem will use platform's default
1247          // behavior to show item when it is disabled and a disabled
1248          // image has not been set. 
1249           ((ToolItem) widget).setDisabledImage(disabledImage);
1250        }
1251        ((ToolItem) widget).setImage(image);
1252
1253        return image != null;
1254      } else {
1255        Image image = cache.getImage(action.getImageDescriptor());
1256        Image hoverImage = cache.getImage(action.getHoverImageDescriptor());
1257        Image disabledImage = cache.getImage(action.getDisabledImageDescriptor());
1258
1259        // If there is no regular image, but there is a hover image,
1260        // convert the hover image to gray and use it as the regular image.
1261        if (image == null && hoverImage != null) {
1262          image = cache.getGrayImage(action.getHoverImageDescriptor());
1263        } else {
1264          // If there is no hover image, use the regular image as the hover image,
1265          // and convert the regular image to gray
1266          if (hoverImage == null && image != null) {
1267            hoverImage = image;
1268            image = cache.getGrayImage(action.getImageDescriptor());
1269          }
1270        }
1271
1272        // Make sure there is a valid image.
1273        if (hoverImage == null && image == null && forceImage) {
1274          image = cache.getMissingImage();
1275        }
1276
1277        // performance: more efficient in SWT to set disabled and hot image before regular image
1278        if (disabledImage != null) {
1279          // Set the disabled image if we were able to create one.
1280          // Assumes that SWT.ToolItem will use platform's default
1281          // behavior to show item when it is disabled and a disabled
1282          // image has not been set. 
1283           ((ToolItem) widget).setDisabledImage(disabledImage);
1284        }
1285        ((ToolItem) widget).setHotImage(hoverImage);
1286        ((ToolItem) widget).setImage(image);
1287
1288        return image != null;
1289      }
1290    } else if (widget instanceof Item || widget instanceof Button) {
1291      // Use hover image if there is one, otherwise use regular image.
1292      Image image = cache.getImage(action.getHoverImageDescriptor());
1293      if (image == null) {
1294        image = cache.getImage(action.getImageDescriptor());
1295      }
1296      // Make sure there is a valid image.
1297      if (image == null && forceImage) {
1298        image = cache.getMissingImage();
1299      }
1300      if (widget instanceof Item) {
1301        ((Item) widget).setImage(image);
1302      } else if (widget instanceof Button) {
1303        ((Button) widget).setImage(image);
1304      }
1305      return image != null;
1306    }
1307    return false;
1308  }
1309
1310  /**
1311   * Shorten the given text <code>t</code> so that its length doesn't
1312   * exceed the given width. The default implementation replaces characters
1313   * in the center of the original string with an ellipsis ("..."). Override
1314   * if you need a different strategy.
1315   */
1316  protected String shortenText(String textValue , ToolItem item) {
1317    if (textValue == null)
1318      return null;
1319
1320    GC gc = new GC(item.getDisplay());
1321
1322    int maxWidth = item.getImage().getBounds().width * 4;
1323    
1324    if(gc.textExtent(textValue).x < maxWidth) {
1325      gc.dispose();
1326      return textValue ;
1327    }
1328    
1329    for (int i = textValue.length(); i > 0; i--) {
1330      String test = textValue .substring(0, i);
1331      test = test + ellipsis;
1332      if(gc.textExtent(test).x < maxWidth) {
1333        gc.dispose();
1334        return test ;
1335      }
1336        
1337    }  
1338    gc.dispose();
1339    //If for some reason we fall through abort
1340    return textValue ;
1341  }
1342}