All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.actions.ToolsAction Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.openide.actions;

import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.swing.Action;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import org.openide.awt.Actions;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.NbBundle;
import org.openide.awt.DynamicMenuContent;
import org.openide.util.LookupListener;
import org.openide.util.RequestProcessor;
import org.openide.util.actions.CookieAction;
import org.openide.util.actions.NodeAction;
import org.openide.util.actions.Presenter;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;


/** A "meta-action" that displays (in a submenu) a list of enabled actions provided by modules.
* Such registered actions are called "service actions":
* they are provided externally but seem to provide additional services on existing components.
* Often they will be {@link NodeAction}s or {@link CookieAction}s so that they will
* be enabled based on the node selection, i.e. the node containing this popup.
* It is desirable for most nodes to include this action somewhere in their popup menu.
*
* 

Note: you do not need to touch this class to add a service action! * Just register your action into UI/ToolActions layer folder * (read more about layers) * since version 6.15. * * @author Jaroslav Tulach */ public class ToolsAction extends SystemAction implements ContextAwareAction, Presenter.Menu, Presenter.Popup { static final long serialVersionUID = 4906417339959070129L; private static ScheduledFuture taskGl; // Global ActionManager listener monitoring all available actions // and their state static final G gl() { return gl(Long.MAX_VALUE); } static final G gl(long timeOut) { initGl(); for (;;) { try { return taskGl.get(timeOut, TimeUnit.MILLISECONDS); } catch (TimeoutException ex) { return null; } catch (InterruptedException ex) { continue; } catch (Exception ex) { taskGl = null; throw new IllegalStateException(ex); } } } private static synchronized void initGl() { if (taskGl == null) { taskGl = RequestProcessor.getDefault().schedule(new G(), 0, TimeUnit.MILLISECONDS); } } @Override protected void initialize() { super.initialize(); } /* @return name */ @Override public String getName() { return getActionName(); } /* @return help for this action */ @Override public HelpCtx getHelpCtx() { return new HelpCtx(ToolsAction.class); } /* @return menu presenter for the action */ @Override public JMenuItem getMenuPresenter() { return new Inline(this); } /* @return menu presenter for the action */ @Override public JMenuItem getPopupPresenter() { return new Popup(this); } /* Does nothing. */ @Override public void actionPerformed(java.awt.event.ActionEvent ev) { assert false; } @Override public Action createContextAwareInstance(Lookup actionContext) { return new DelegateAction(this, actionContext); } /* @return name */ private static String getActionName() { return NbBundle.getMessage(ToolsAction.class, "CTL_Tools"); } static List getToolActions() { ActionManager am = ActionManager.getDefault(); List arr = new ArrayList(); arr.addAll(Arrays.asList(am.getContextActions())); String pref = arr.isEmpty() ? null : ""; for (Lookup.Item item : gl().result.allItems()) { final Action action = item.getInstance(); if (action == null) { continue; } String where = item.getId().replaceFirst("[^/]*$", ""); // NOI18N if (pref != null && !pref.equals(where)) { arr.add(null); } pref = where; arr.add(action); } return arr; } /** Implementation method that regenerates the items in the menu or * in the array. * * @param forMenu true if will be presented in menu or false if presented in popup * @param list (can be null) */ private static List generate(Action toolsAction, boolean forMenu) { List actions = getToolActions(); List list = new ArrayList(actions.size()); boolean separator = false; boolean firstItemAdded = false; // flag to prevent adding separator before actual menu items // Get action context. Lookup lookup; if (toolsAction instanceof Lookup.Provider) { lookup = ((Lookup.Provider) toolsAction).getLookup(); } else { lookup = null; } for (Action a : actions) { // Retrieve context sensitive action instance if possible. if (lookup != null && a instanceof ContextAwareAction) { a = ((ContextAwareAction) a).createContextAwareInstance(lookup); } if (a == null) { if (firstItemAdded) { separator = true; } } else { boolean isPopup = (a instanceof Presenter.Popup); boolean isMenu = (a instanceof Presenter.Menu); if (!((forMenu && isMenu) || (!forMenu && isPopup)) && (isMenu || isPopup)) { continue; // do not call isEnabled on action that is only popup presenter when building menu (i18nPopupAction) } if (a.isEnabled()) { JMenuItem mi; if (forMenu && isMenu) { mi = ((Presenter.Menu) a).getMenuPresenter(); } else if (!forMenu && isPopup) { mi = ((Presenter.Popup) a).getPopupPresenter(); } else if (!isMenu && !isPopup) { // Generic Swing action. mi = new JMenuItem(); Actions.connect(mi, a, !forMenu); } else { // Should not be here. continue; } if (separator) { list.add(null); separator = false; } list.add(mi); firstItemAdded = true; } } } return list; } //------------------------------------------ /** @deprecated Useless, see {@link ActionManager}. */ @Deprecated public static void setModel(Model m) { throw new SecurityException(); } /** @deprecated Useless, see {@link ActionManager}. */ @Deprecated public static interface Model { public SystemAction[] getActions(); public void addChangeListener(javax.swing.event.ChangeListener l); public void removeChangeListener(javax.swing.event.ChangeListener l); } /** Inline menu that watches model changes only when really needed. */ private static final class Inline extends JMenuItem implements DynamicMenuContent { static final long serialVersionUID = 2269006599727576059L; /** timestamp of the beginning of the last regeneration */ private int timestamp = 0; /** Associated tools action. */ private Action toolsAction; Inline(Action toolsAction) { this.toolsAction = toolsAction; } @NbBundle.Messages({ "LAB_ToolsActionInitializing=Initializing..." }) @Override public JComponent[] synchMenuPresenters(JComponent[] items) { G g = gl(50); if (g == null) { JMenuItem init = new JMenuItem(); init.setText(Bundle.LAB_ToolsActionInitializing()); init.setEnabled(false); return new JMenuItem[] { init }; } if (timestamp == g.getTimestamp()) { return items; } // generate directly list of menu items List l = generate(toolsAction, true); timestamp = gl().getTimestamp(); return l.toArray(new JMenuItem[0]); } @Override public JComponent[] getMenuPresenters() { return synchMenuPresenters(new JComponent[0]); } } //-------------------------------------------------- /** Inline menu that is either empty or contains one submenu.*/ private static final class Popup extends JMenuItem implements DynamicMenuContent { static final long serialVersionUID = 2269006599727576059L; /** sub menu */ private JMenu menu = new MyMenu(); /** Associated tools action. */ private Action toolsAction; public Popup(Action toolsAction) { super(); this.toolsAction = toolsAction; HelpCtx.setHelpIDString(menu, ToolsAction.class.getName()); } @Override public JComponent[] synchMenuPresenters(JComponent[] items) { return gl().isPopupEnabled(toolsAction) ? new JMenuItem[] { menu } : new JMenuItem[0]; } @Override public JComponent[] getMenuPresenters() { return synchMenuPresenters(new JComponent[0]); } /** A special menu that will properly update its submenu before posting */ private class MyMenu extends org.openide.awt.JMenuPlus implements PopupMenuListener { /* A popup menu we've attached our listener to. * If null, the content is not up-to-date */ private JPopupMenu lastPopup = null; MyMenu() { super(getActionName()); } @Override public JPopupMenu getPopupMenu() { JPopupMenu popup = super.getPopupMenu(); fillSubmenu(popup); return popup; } private void fillSubmenu(JPopupMenu pop) { if (lastPopup == null) { pop.addPopupMenuListener(this); lastPopup = pop; removeAll(); Iterator it = generate(toolsAction, false).iterator(); while (it.hasNext()) { java.awt.Component item = (java.awt.Component) it.next(); if (item == null) { addSeparator(); } else { add(item); } } // also work with empty element if (getMenuComponentCount() == 0) { JMenuItem empty = new JMenuItem(NbBundle.getMessage(ToolsAction.class, "CTL_EmptySubMenu")); empty.setEnabled(false); add(empty); } } } @Override public void popupMenuCanceled(PopupMenuEvent e) { } @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { if (lastPopup != null) { lastPopup.removePopupMenuListener(this); lastPopup = null; // clear the status and stop listening } } } } //------------------------------------------------ //---------------------------------------------------------- private static class G implements PropertyChangeListener, LookupListener, Callable { public static final String PROP_STATE = "actionsState"; // NOI18N private int timestamp = 1; private Action[] actions = null; private PropertyChangeSupport supp = new PropertyChangeSupport(this); Lookup.Result result; public G() { // init is done in run } @Override public G call() { ActionManager am = ActionManager.getDefault(); am.addPropertyChangeListener(this); result = Lookups.forPath("UI/ToolActions").lookupResult(Action.class); // NOI18N result.addLookupListener(this); actionsListChanged(); return this; } public final void addPropertyChangeListener(PropertyChangeListener listener) { supp.addPropertyChangeListener(listener); } public final void removePropertyChangeListener(PropertyChangeListener listener) { supp.removePropertyChangeListener(listener); } protected final void firePropertyChange(String name, Object o, Object n) { supp.firePropertyChange(name, o, n); } private void actionsListChanged() { timestamp++; // deregister all actions listeners Action[] copy = actions; if (copy != null) { for (int i = 0; i < copy.length; i++) { Action act = copy[i]; if (act != null) { act.removePropertyChangeListener(this); } } } ActionManager am = ActionManager.getDefault(); List all = new ArrayList(); all.addAll(Arrays.asList(am.getContextActions())); all.addAll(result.allInstances()); copy = all.toArray(new Action[0]); for (int i = 0; i < copy.length; i++) { Action act = copy[i]; if (act != null) { act.addPropertyChangeListener(this); } } actions = copy; firePropertyChange(PROP_STATE, null, null); // tell the world } private void actionStateChanged() { timestamp++; firePropertyChange(PROP_STATE, null, null); // tell the world } @Override public void propertyChange(PropertyChangeEvent ev) { String prop = ev.getPropertyName(); if ((prop == null) || prop.equals(ActionManager.PROP_CONTEXT_ACTIONS)) { actionsListChanged(); } else if (prop.equals("enabled")) { actionStateChanged(); } } /** Tells if there is any action that is willing to provide * Presenter.Popup */ private boolean isPopupEnabled(Action toolsAction) { boolean en = false; Action[] copy = actions; // Get action conext. Lookup lookup; if (toolsAction instanceof Lookup.Provider) { lookup = ((Lookup.Provider) toolsAction).getLookup(); } else { lookup = null; } for (int i = 0; i < copy.length; i++) { if (copy[i] == null) { continue; } // Get context aware action instance if needed. Action act; // Retrieve context aware action instance if possible. if ((lookup != null) && copy[i] instanceof ContextAwareAction) { act = ((ContextAwareAction) copy[i]).createContextAwareInstance(lookup); if (act == null) { throw new IllegalStateException("createContextAwareInstance for " + copy[i] + " returned null!"); } } else { act = copy[i]; } if (act.isEnabled()) { en = true; break; } } return en; } private int getTimestamp() { return timestamp; } @Override public void resultChanged(LookupEvent ev) { actionsListChanged(); } } /** Delegate tools action. Which act accordingly to current context * (represented by lookup). */ private static final class DelegateAction extends Object implements Action, Presenter.Menu, Presenter.Popup, Lookup.Provider { private ToolsAction delegate; private Lookup lookup; /** support for listeners */ private PropertyChangeSupport support = new PropertyChangeSupport(this); public DelegateAction(ToolsAction delegate, Lookup actionContext) { this.delegate = delegate; this.lookup = actionContext; } /** Overrides superclass method, adds delegate description. */ @Override public String toString() { return super.toString() + "[delegate=" + delegate + "]"; // NOI18N } /** Implements Lookup.Provider. */ @Override public Lookup getLookup() { return lookup; } @Override public void actionPerformed(java.awt.event.ActionEvent e) { } @Override public void putValue(String key, Object o) { } @Override public Object getValue(String key) { return delegate.getValue(key); } @Override public boolean isEnabled() { // Irrelevant see G#isPopupEnabled(..). return delegate.isEnabled(); } @Override public void setEnabled(boolean b) { // Irrelevant see G#isPopupEnabled(..). } @Override public void addPropertyChangeListener(PropertyChangeListener listener) { support.addPropertyChangeListener(listener); } @Override public void removePropertyChangeListener(PropertyChangeListener listener) { support.removePropertyChangeListener(listener); } /** Implements Presenter.Menu. */ @Override public javax.swing.JMenuItem getMenuPresenter() { return new Inline(this); } /** Implements Presenter.Popup. */ @Override public javax.swing.JMenuItem getPopupPresenter() { return new ToolsAction.Popup(this); } } // End of DelegateAction. }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy