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

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

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.actions;

import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.IOException;
import java.util.*;
import java.lang.ref.*;
import java.util.List;

import javax.swing.*;
import javax.swing.event.*;

import org.openide.*;
import org.openide.awt.Actions;
import org.openide.awt.JMenuPlus;
import org.openide.explorer.view.MenuView;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.Repository;
import org.openide.loaders.*;
import org.openide.nodes.*;
import org.openide.util.Mutex;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.LookupListener;
import org.openide.util.NbBundle;
import org.openide.util.actions.*;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;

/** Creates a new object from template in the selected folder.
* @see DataObject#isTemplate
*
* @author Petr Hamernik, Dafe Simonek
*/
public class NewTemplateAction extends NodeAction {

    private static DataObject selectedTemplate;
    private static DataFolder targetFolder;

    /** Maximum count of recent templates. */
    private static int MAX_RECENT_ITEMS = 5;
    
    /** Getter for wizard.
     * @param the node that is currently activated
     * @return the wizard or null if the wizard should not be enabled
    */
    static TemplateWizard getWizard (Node n) {
        if (n == null) {
            Node[] arr = WindowManager.getDefault ().getRegistry ().getActivatedNodes ();
            if (arr.length == 1) {
                n = arr[0];
            }
        }

        // if activated node isn't folder try parent which should be folder
        Node folder = n;
        // bugfix #29661, start finding the target folder with null folder
        targetFolder = null;
        while (targetFolder == null && folder != null) {
            targetFolder = folder == null ? null : (DataFolder) folder.getCookie (DataFolder.class);
            folder = folder.getParentNode ();
        }
        
        Cookie c = n == null ? null : (Cookie)n.getCookie (Cookie.class);
        if (c != null) {
            TemplateWizard t = c.getTemplateWizard ();
            if (t != null) {
                return t;
            }
        }

        return new DefaultTemplateWizard();
    }

    private boolean active = false;

    // This method is called only for the File->New menu item
    // it gets the node selection from the active TC
    protected void performAction (Node[] activatedNodes) {
        if (active)
            return;
        
        active = true;
        
        Node n = activatedNodes.length == 1 ? activatedNodes[0] : null;
        TemplateWizard wizard = getWizard (n);
        if (wizard instanceof DefaultTemplateWizard) {
            if (targetFolder != null && targetFolder.isValid())
                wizard.setTargetFolder(targetFolder);
            if (selectedTemplate != null && selectedTemplate.isValid())
                wizard.setTemplate(selectedTemplate);
        }
        boolean instantiated = false;
        try {
            // clears the name to default
            wizard.setTargetName(null);
            // instantiates
            instantiated = wizard.instantiate() != null;
        } catch (IOException e) {
            ErrorManager em = ErrorManager.getDefault();
            em.annotate(e, NbBundle.getMessage(DataObject.class, "EXC_TemplateFailed"));
            em.notify(e);
        }
        finally {
            if (wizard instanceof DefaultTemplateWizard) {
                try {
                    if (instantiated) {
                        selectedTemplate = wizard.getTemplate();
                        // Put the template in the recent list
                        if (selectedTemplate != null) {
                            recentChanged = addRecent (selectedTemplate);
                        }
                    }
                    // else selectedTemplate might be e.g. Templates folder itself
                    // which would cause an IOException when trying to make a link
                    targetFolder = wizard.getTargetFolder();
                }
                catch (IOException ignore) {
                    selectedTemplate = null;
                    targetFolder = null;
                }
            }
            active = false;
        }
    }
    
    protected boolean asynchronous() {
        return false;
    }

    /* Enables itself only when activates node is DataFolder.
    */
    protected boolean enable (Node[] activatedNodes) {
        if ((activatedNodes == null) || (activatedNodes.length != 1))
            return false;

        Cookie c = (Cookie)activatedNodes[0].getCookie (Cookie.class);
        if (c != null) {
            // if the current node provides its own wizard...
            return c.getTemplateWizard () != null;
        }
        
        DataFolder cookie = (DataFolder)activatedNodes[0].getCookie(DataFolder.class);
        if (cookie != null && cookie.getPrimaryFile ().canWrite ()) {
            return true;
        }
        return false;
    }

    public String getName() {
        return NbBundle.getMessage(DataObject.class, "NewTemplate");
    }

    public HelpCtx getHelpCtx() {
        return new HelpCtx (NewTemplateAction.class);
    }

    protected String iconResource () {
        return "org/openide/actions/new.gif"; // NOI18N
    }
    
    public JMenuItem getMenuPresenter () {
        return new Actions.MenuItem (this, true) {
                   public void setEnabled (boolean e) {
                       super.setEnabled (true);
                   }
               };
    }

    public Component getToolbarPresenter () {
        return new Actions.ToolbarButton (this) {
                   public void setEnabled (boolean e) {
                       super.setEnabled (true);
                   }
               };
    }
    
    /* Creates presenter that displayes submenu with all
    * templates.
    */
    public JMenuItem getPopupPresenter() {
        return getPopupPresenter (null, this);
    }
    
    private JMenuItem getPopupPresenter (final Lookup actionContext, Action action) {
        Node[] nodes = new Node[0];
        if (actionContext != null) {
            nodes = getNodesFromLookup (actionContext);
        }
        final Node n = (nodes.length == 1) ? nodes[0] : null;
        TemplateWizard tw = getWizard (n);
        
        if (tw instanceof DefaultTemplateWizard) {
            return new MenuWithRecent (n, this.isEnabled ());
        } else {
            // The null is correct but depends on the impl of MenuView.Menu
            JMenuItem menu = new MenuView.Menu (null, new TemplateActionListener (actionContext), false) {
                // this is the only place MenuView.Menu needs the node ready
                // so lets prepare it on-time
                public JPopupMenu getPopupMenu () {
                    if (node == null) node = getTemplateRoot (n);
                    return super.getPopupMenu ();
                }
            };
            Actions.connect (menu, action, true);
            return menu;
        }
    }
    

    private class MenuWithRecent extends JMenuPlus {
        private boolean initialized = false;
        private Node node;
        private boolean canWrite;
        
        public MenuWithRecent(Node n, boolean writable) {
            super(); //NewTemplateAction.this.getName());
            Actions.setMenuText(this, NewTemplateAction.this.getName(), false);
            node = n;
            canWrite = writable;
        }
        
        public JPopupMenu getPopupMenu() {
            JPopupMenu popup = super.getPopupMenu();
            if (!initialized) {
                popup.add(new Item(null)); // New... item
            
                List privileged = getPrivilegedList();
                // all fixed items
                if (privileged.size() > 0) popup.add(new JSeparator()); // separator
                for (Iterator it = privileged.iterator(); it.hasNext(); ) {
                    DataObject dobj = (DataObject)it.next();
                    if (dobj instanceof DataShadow)
                                dobj = ((DataShadow)dobj).getOriginal();
                    popup.add(new Item(dobj));
                }

                // all recent items
                boolean regenerate = false;
                boolean addSeparator = ! getRecentList ().isEmpty ();
                for (Iterator it = getRecentList ().iterator(); it.hasNext(); ) {
                    DataObject dobj = (DataObject)it.next ();
                    if (isValidTemplate (dobj)) {
                        if (addSeparator) popup.add (new JSeparator ()); // separator
                        addSeparator = false;
                        popup.add (new Item (dobj));
                    } else {
                        // some template was unvalidated => have to regenerate next time
                        regenerate = true;
                    }
                }
                recentChanged = recentChanged || regenerate;
                initialized = true;
            }
            return popup;
        }
        
        private class Item extends JMenuItem implements HelpCtx.Provider, ActionListener {
            DataObject template; // Null means no template -> show the chooser
            public Item(DataObject template) {
                super();
                this.template = template;
                
                setText (template == null ? 
                    NbBundle.getMessage(DataObject.class, "NewTemplateAction") :
                    template.getNodeDelegate().getDisplayName()
                );
                    
                if (template == null) {
                    setIcon (NewTemplateAction.this.getIcon());
                } else {
                    setIcon (new ImageIcon(template.getNodeDelegate().getIcon(java.beans.BeanInfo.ICON_COLOR_16x16)));
                }
                
                addActionListener(this);
                // recommendation from issue 32191, don't enable popup menu items on read-only folders
                setEnabled (canWrite);
            }
            
            /** Get context help for this item.*/
            public HelpCtx getHelpCtx() {
                if (template != null) {
                    return template.getHelpCtx();
                }
                return NewTemplateAction.this.getHelpCtx();
            }
            
            /** Invoked when an action occurs. */
            public void actionPerformed(ActionEvent e) {
                doShowWizard(template, node);
            }
        }
    }
    
    /** Cached content of Templates/Privileged */
    private DataFolder privilegedListFolder;
    
    /** Cached content of Templates/Recent */
    private DataFolder recentListFolder;
    
    private boolean recentChanged = true;
    private List recentList = new ArrayList (0);
    
    private List getPrivilegedList() {
        if (privilegedListFolder == null) {
            FileObject fo = Repository.getDefault().getDefaultFileSystem().
                                    findResource("Templates/Privileged"); // NOI18N
            if (fo != null) privilegedListFolder = DataFolder.findFolder(fo);
        }
        if (privilegedListFolder != null) {
            DataObject[] data = privilegedListFolder.getChildren();
            List l2 = new ArrayList(data.length);
            for (int i=0; i MAX_RECENT_ITEMS) {
            // remove last
            removeRecent (templates[size - 1]);
            size--;
        }
        
        return true;
    }
    
    private boolean removeRecent (DataObject template) {
        DataFolder folder = getRecentFolder ();
        
        // no recent folder, no recent templates
        if (folder == null) return false;
        
        try {
            template.delete ();
            return true;
        } catch (IOException ioe) {
            ErrorManager em = ErrorManager.getDefault();
            em.notify (ErrorManager.INFORMATIONAL, ioe);
            // it couldn't be deleted
            return false;
        }
    }
    
    private boolean isRecent (DataObject template) {
        return getRecentList ().contains (template);
    }
    
    /** Create a hierarchy of templates.
    * @return a node representing all possible templates
    */
    public static Node getTemplateRoot () {
        RootChildren ch = new RootChildren (null);
        // create the root
        return ch.getRootFolder ().new FolderNode (ch);
    }
    
    private static Node getTemplateRoot (Node n) {
        RootChildren ch = new RootChildren (n);
        // create the root
        Node help = ch.getRootFolder ().new FolderNode (ch);
        return help;
    }
    
    /** Cookie that can be implemented by a node if it wishes to have a 
     * special templates wizard.
     */
    public static interface Cookie extends Node.Cookie {
        /** Getter for the wizard that should be used for this cookie.
         */
        public TemplateWizard getTemplateWizard ();
    }
    
    /** Checks whether an object is acceptable for display as a container.
     */
    private static boolean acceptObj (DataObject obj) {
        if (obj.isTemplate ()) {
            return true;
        }

        if (obj instanceof DataFolder) {
            Object o = obj.getPrimaryFile ().getAttribute ("simple"); // NOI18N
            return o == null || Boolean.TRUE.equals (o);
        }

        return false;
        
    }


    /** Actions listener which instantiates the template */
    private static class TemplateActionListener implements NodeAcceptor, DataFilter {
        static final long serialVersionUID =1214995994333505784L;
        Lookup actionContext;
        TemplateActionListener(Lookup context) {
            actionContext = context;
        }
        public boolean acceptNodes (Node[] nodes) {
            Node[] nodesInContext = null;
            if (actionContext != null) {
                nodesInContext = getNodesFromLookup (actionContext);
            }
            if ((nodesInContext == null) || (nodesInContext.length != 1)) {
                ErrorManager.getDefault ().log (ErrorManager.WARNING, "Wrong count of nodes in context lookup."); //NOI18N
                return false;
            }
            if ((nodes == null) || (nodes.length != 1)) {
                ErrorManager.getDefault ().log (ErrorManager.WARNING, "Wrong count of selected nodes in popup menu."); //NOI18N
                return false;
            }
            Node n = nodes[0];
            DataObject obj = (DataObject)n.getCookie (DataObject.class);
            if (obj == null || !obj.isTemplate ()) {
                ErrorManager.getDefault ().log (ErrorManager.WARNING, "Selected node in popup menu is not acceptable."); //NOI18N
                // do not accept
                return false;
            }
            
            // bugfix #38421, read node in contextLookup to select the right wizard
            TemplateWizard wizard = getWizard (nodesInContext[0]);
            
            try {
                wizard.setTargetName (null);
                wizard.instantiate (obj, targetFolder);
            } catch (IOException e) {
                ErrorManager em = ErrorManager.getDefault();
                em.annotate(e, NbBundle.getMessage(DataObject.class, "EXC_TemplateFailed"));
                em.notify(e);
            }

            // ok
            return true;
        }

        /** Data filter impl.
        */
        public boolean acceptDataObject (DataObject obj) {
            return acceptObj (obj);
        }
    }
    
    /** Root template childen.
     */
    private static class RootChildren extends Children.Keys
    implements NodeListener {
        /** last wizard used with the root */
        private TemplateWizard wizard;
        /** Folder of templates */
        private DataFolder rootFolder;
        /** node to display templates for or null if current selection
         * should be followed
         */
        private WeakReference current;
        /** weak listener */
        private NodeListener listener = org.openide.nodes.NodeOp.weakNodeListener (this, null);
        
        /** Instance not connected to any node.
         */
        public RootChildren (Node n) {
            TopComponent.Registry reg = WindowManager.getDefault ().getRegistry ();
            reg.addPropertyChangeListener (org.openide.util.WeakListeners.propertyChange (this, reg));
            
            updateWizard (getWizard (n));
        }
        
        public DataFolder getRootFolder () {
            if (rootFolder == null) {
                // if rootFolder is null then initialize folder
                doSetKeys ();
            }
            return rootFolder;
        }
               

        /** Creates nodes for nodes.
         */
        protected Node[] createNodes (Object key) {
            Node n = (Node)key;
            String nodeName = n.getDisplayName();
            
            DataObject obj = null;
            DataShadow shadow = (DataShadow)n.getCookie (DataShadow.class);
            if (shadow != null) {
                // I need DataNode here to get localized name of the
                // shadow, but without the ugly "(->)" at the end
                DataNode dn = new DataNode(shadow, Children.LEAF);
                nodeName = dn.getDisplayName();
                obj = shadow.getOriginal();
                n = obj.getNodeDelegate();
            }
            
            if (obj == null)
                obj = (DataObject)n.getCookie (DataObject.class);
            if (obj != null) {
                if (obj.isTemplate ()) {
                    // on normal nodes stop recursion
                    return new Node[] { new DataShadowFilterNode (n, LEAF, nodeName) };
                }
            
                if (acceptObj (obj)) {
                    // on folders use normal filtering
                    return new Node[] { new DataShadowFilterNode (n, new TemplateChildren (n), nodeName) };
                }
            }
            
            return null;
        }
        
        /** Check whether the node has not been updated.
         */
        private void updateNode (Node n) {            
            if (current != null && current.get () == n) {
                return;
            }
            
            if (current != null && current.get () != null) {
                ((Node)current.get ()).removeNodeListener (listener);
            }
            
            n.addNodeListener (listener);
            current = new WeakReference (n);
        }
        
        /** Check whether the wizard was not updated.
         */
        private void updateWizard (TemplateWizard w) {
            if (wizard == w) {
                return;
            }
            
            if (wizard != null) {
                Node n = wizard.getTemplatesFolder ().getNodeDelegate ();
                n.removeNodeListener (listener);
            }
            
            Node newNode = w.getTemplatesFolder ().getNodeDelegate ();
            newNode.addNodeListener (listener);
            wizard = w;
            
            updateKeys ();
        }
        
        /** Updates the keys.
         */
        private void updateKeys () {
            // updateKeys can be called while holding Children.MUTEX
            //   --> replan getNodes(true) to a new thread
            RequestProcessor.getDefault().post(new Runnable() {
                public void run() {
                    doSetKeys ();
                }
            });
        }
        
        // don't call this while holding Children.MUTEX
        private void doSetKeys () {
            rootFolder = wizard.getTemplatesFolder ();
            setKeys (rootFolder.getNodeDelegate ().getChildren ().getNodes (true));
        }
         
         /** Fired when the order of children is changed.
        /** Fired when the order of children is changed.
         * @param ev event describing the change
         */
        public void childrenReordered(NodeReorderEvent ev) {
            updateKeys ();
        }        
        
        /** Fired when a set of children is removed.
         * @param ev event describing the action
         */
        public void childrenRemoved(NodeMemberEvent ev) {
            updateKeys ();
        }
        
        /** Fired when a set of new children is added.
         * @param ev event describing the action
         */
        public void childrenAdded(NodeMemberEvent ev) {
            updateKeys ();
        }
        
        /** Fired when the node is deleted.
         * @param ev event describing the node
         */
        public void nodeDestroyed(NodeEvent ev) {
        }

        /** Listen on changes of cookies.
         */
        public void propertyChange(java.beans.PropertyChangeEvent ev) {
            String pn = ev.getPropertyName ();
            
            if (current != null && ev.getSource () == current.get ()) {
                // change in current node
                if (Node.PROP_COOKIE.equals (pn)) {
                    final Node node = (Node) current.get();
                    Mutex.EVENT.readAccess(new Runnable() {
                        public void run() {
                            updateWizard (getWizard (node));
                        }
                    });
                }
            } else {
                // change in selected nodes
                if (TopComponent.Registry.PROP_ACTIVATED_NODES.equals (pn)) {
                    // change the selected node
                    Node[] arr = WindowManager.getDefault ().getRegistry ().getActivatedNodes ();
                    if (arr.length == 1) {
                        // only if the size is 1
                        updateNode (arr[0]);
                    }
                }
            }
        }
        
    }
    
    /** Filter node children, that stops on data objects (does not go futher)
    */
    private static class TemplateChildren extends FilterNode.Children {
        public TemplateChildren (Node or) {
            super (or);
        }
        
        /** Creates nodes for nodes.
         */
        protected Node[] createNodes (Object key) {
            Node n = (Node)key;
            String nodeName = n.getDisplayName();
            
            DataObject obj = null;
            DataShadow shadow = (DataShadow)n.getCookie (DataShadow.class);
            if (shadow != null) {
                // I need DataNode here to get localized name of the
                // shadow, but without the ugly "(->)" at the end
                DataNode dn = new DataNode(shadow, Children.LEAF);
                nodeName = dn.getDisplayName();
                obj = shadow.getOriginal();
                n = obj.getNodeDelegate();
            }
            
            if (obj == null)
                obj = (DataObject)n.getCookie (DataObject.class);
            if (obj != null) {
                if (obj.isTemplate ()) {
                    // on normal nodes stop recursion
                    return new Node[] { new DataShadowFilterNode (n, LEAF, nodeName) };
                }
            
                if (acceptObj (obj)) {
                    // on folders use normal filtering
                    return new Node[] { new DataShadowFilterNode (n, new TemplateChildren (n), nodeName) };
                }
            }
            return new Node[] {};
        }

    }

    private static class DataShadowFilterNode extends FilterNode {
        
        private String name;
        
        public DataShadowFilterNode (Node or, org.openide.nodes.Children children, String name) {
            super (or, children);
            this.name = name;
            disableDelegation(FilterNode.DELEGATE_SET_DISPLAY_NAME);
        }
        
        public String getDisplayName() {
            return name;
        }
        
    }

    private static class DefaultTemplateWizard extends TemplateWizard {
        DefaultTemplateWizard() {}
    }
    
    // delegate action
    // bugfix 36573, NewTemplateAction provides context aware action
    private static final Node[] EMPTY_NODE_ARRAY = new Node[0];
    
    private class NodeLookupListener implements LookupListener {
        
        public void resultChanged (org.openide.util.LookupEvent ev) {
            updateAction ();
        }
    }
    
    private void updateAction () {}
    
    static private final synchronized Node[] getNodesFromLookup (Lookup lookup) {
        if (lookup != null) {
            Lookup.Result nodesResult = lookup.lookup (new Lookup.Template (Node.class));
            if (nodesResult != null) {
                return (Node[])nodesResult.allInstances ().toArray (EMPTY_NODE_ARRAY);
            }
        }
        return EMPTY_NODE_ARRAY;
    }
    
    
    /** Implements ContextAwareAction interface method. */
    public Action createContextAwareInstance (Lookup actionContext) {
        return new DelegateAction (this, actionContext);
    }
    
    private static final class DelegateAction extends Object
    implements Action, Presenter.Popup, /*Presenter.Menu, Presenter.Toolbar,*/ LookupListener {
        
        private NewTemplateAction delegate;
        private Lookup actionContext;
        private Lookup.Result nodesResult;
        
        private PropertyChangeSupport support = new PropertyChangeSupport (this);
        
        private static Lookup.Template NODES = new Lookup.Template (Node.class);
        
        public DelegateAction (NewTemplateAction action, Lookup actionContext) {
            this.delegate = action;
            this.actionContext = actionContext;
            this.nodesResult = actionContext.lookup (NODES);
            // if a weak listener is used then NewTemplateActionTest fails
            //LookupListener l = (LookupListener)WeakListeners.create (LookupListener.class, (LookupListener)action, nodesResult);
            //nodesResult.addLookupListener (l);
            //l.resultChanged (null);
            nodesResult.addLookupListener (this);
            resultChanged (null);
        }
        
        /** Overrides superclass method, adds delegate description. */
        public String toString () {
            return super.toString () + "[delegate=" + delegate + "]"; // NOI18N
        }
        
        public void putValue (String key, Object value) { }
        
        public boolean isEnabled () {
            return delegate.enable (getNodesFromLookup (actionContext));
        }
        
        public Object getValue (String key) {
            return delegate.getValue (key);
        }
        
        public void setEnabled (boolean b) {
        }
        
        public void actionPerformed (ActionEvent e) {
        }
        
        public void addPropertyChangeListener (PropertyChangeListener listener) {
            support.addPropertyChangeListener (listener);
        }
        
        public void removePropertyChangeListener (PropertyChangeListener listener) {
            support.removePropertyChangeListener (listener);
        }
        
        public JMenuItem getPopupPresenter() {
            return delegate.getPopupPresenter (actionContext, this);
        }

        public void resultChanged (org.openide.util.LookupEvent ev) {
            getPopupPresenter ();
//            getMenuPresenter ();
//            getToolbarPresenter ();
        }
        
//        public JMenuItem getMenuPresenter () {
//            return delegate.getMenuPresenter ();
//        }
//        
//        public Component getToolbarPresenter () {
//            return delegate.getToolbarPresenter ();
//        }
//        
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy