/*
* 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-2003 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.NbBundle;
import org.openide.util.actions.*;
import org.openide.util.WeakListener;
import org.openide.util.RequestProcessor;
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 {
/** generated Serialized Version UID */
static final long serialVersionUID = 5408651725508985475L;
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);
}
try {
// clears the name to default
wizard.setTargetName(null);
// instantiates
wizard.instantiate ();
} catch (IOException e) {
ErrorManager em = ErrorManager.getDefault();
em.annotate(e, NbBundle.getMessage(NewTemplateAction.class, "EXC_TemplateFailed"));
em.notify(e);
}
finally {
if (wizard instanceof DefaultTemplateWizard) {
try {
selectedTemplate = wizard.getTemplate();
// Put the template in the recent list
if (selectedTemplate != null) addRecent (selectedTemplate);
targetFolder = wizard.getTargetFolder();
}
catch (IOException ignore) {
selectedTemplate = null;
targetFolder = null;
}
}
active = 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 ().isReadOnly ()) {
return true;
}
return false;
}
/* Human presentable name of the action. This should be
* presented as an item in a menu.
* @return the name of the action
*/
public String getName() {
return NbBundle.getMessage(NewTemplateAction.class, "NewTemplate");
}
/* Help context where to find more about the action.
* @return the help context for this action
*/
public HelpCtx getHelpCtx() {
return new HelpCtx (NewTemplateAction.class);
}
/* Resource name for the icon.
* @return resource name
*/
protected String iconResource () {
return "org/openide/resources/actions/new.gif"; // NOI18N
}
/* Creates presenter that invokes the associated presenter.
*/
public JMenuItem getMenuPresenter() {
return new Actions.MenuItem (this, true) {
public void setEnabled (boolean e) {
super.setEnabled (true);
}
};
}
/* Creates presenter that invokes the associated presenter.
*/
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() {
TemplateWizard tw = getWizard(null);
if (tw instanceof DefaultTemplateWizard) {
return new MenuWithRecent();
} else {
// The null is correct but depends on the impl of MenuView.Menu
JMenuItem menu = new MenuView.Menu (null, new TemplateActionListener (), 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();
return super.getPopupMenu();
}
};
Actions.connect (menu, this, true);
return menu;
}
}
private class MenuWithRecent extends JMenuPlus {
private boolean initialized = false;
public MenuWithRecent() {
super(); //NewTemplateAction.this.getName());
Actions.setMenuText(this, NewTemplateAction.this.getName(), false);
}
public JPopupMenu getPopupMenu() {
JPopupMenu popup = super.getPopupMenu();
if (!initialized) {
popup.add(new Item(null, true)); // 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, true));
}
// all recent items
if (getRecentList ().size() > 0) popup.add(new JSeparator()); // separator
for (Iterator it = getRecentList ().iterator(); it.hasNext(); ) {
popup.add(new Item((DataObject)it.next(), false));
}
initialized = true;
}
return popup;
}
private class Item extends JMenuItem implements HelpCtx.Provider, ActionListener {
DataObject template; // Null means no template -> show the chooser
boolean fixed;
public Item(DataObject template, boolean fixed) {
super();
this.template = template;
this.fixed = fixed;
setText (template == null ?
NbBundle.getMessage(NewTemplateAction.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);
}
/** 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);
}
}
}
/** 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<data.length; i++) {
DataObject dobj = data[i];
if (dobj instanceof DataShadow)
dobj = ((DataShadow)dobj).getOriginal();
if (isValidTemplate (dobj)) {
l2.add(dobj);
}
}
return l2;
} else {
return new ArrayList(0);
}
}
private void doShowWizard(DataObject template) {
targetFolder = null;
TemplateWizard wizard = getWizard (null);
try {
wizard.setTargetName (null);
Set created = wizard.instantiate (template, targetFolder);
if (created != null && wizard instanceof DefaultTemplateWizard) {
// put the item in the recent list
selectedTemplate = wizard.getTemplate();
if (selectedTemplate != null) addRecent (selectedTemplate);
}
} catch (IOException e) {
ErrorManager em = ErrorManager.getDefault();
em.annotate(e, NbBundle.getMessage(NewTemplateAction.class, "EXC_TemplateFailed"));
em.notify(e);
}
}
private DataFolder getRecentFolder () {
if (recentListFolder == null) {
FileObject fo = Repository.getDefault ().getDefaultFileSystem ().
findResource ("Templates/Recent"); // NOI18N
if (fo != null) {
recentListFolder = DataFolder.findFolder(fo);
}
}
return recentListFolder;
}
private List getRecentList () {
if (!recentChanged) return recentList;
if (getRecentFolder () != null) {
DataObject[] data = getRecentFolder ().getChildren ();
List l2 = new ArrayList(data.length);
for (int i=0; i<data.length; i++) {
DataObject dobj = data[i];
if (dobj instanceof DataShadow)
dobj = ((DataShadow)dobj).getOriginal();
if (isValidTemplate (dobj)) {
l2.add(dobj);
} else {
removeRecent (data[i]);
}
}
recentList = l2;
} else {
recentList = new ArrayList (0);
}
return recentList;
}
private boolean isValidTemplate (DataObject template) {
return (template != null) && template.isTemplate ();
}
private boolean addRecent (DataObject template) {
DataFolder folder = getRecentFolder ();
// no recent folder, no recent templates
if (folder == null) return false;
// check if privileged
if (getPrivilegedList ().contains (template)) return false;
// check if recent already
if (isRecent (template)) return false;
DataObject[] templates = folder.getChildren ();
DataObject[] newOrder = new DataObject[templates.length + 1];
for (int i = 1; i < newOrder.length; i++) {
newOrder[i] = templates[i - 1];
}
try {
newOrder[0] = template.createShadow (folder);
folder.setOrder (newOrder);
} catch (IOException ioe) {
ErrorManager em = ErrorManager.getDefault();
em.notify (ErrorManager.INFORMATIONAL, ioe);
// can't create shadow
return false;
}
// reread children
templates = folder.getChildren ();
int size = templates.length;
while (size > MAX_RECENT_ITEMS) {
// remove last
removeRecent (templates[size - 1]);
size--;
}
recentChanged = true;
return true;
}
private boolean removeRecent (DataObject template) {
DataFolder folder = getRecentFolder ();
// no recent folder, no recent templates
if (folder == null) return false;
try {
template.delete ();
recentChanged = true;
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 recentList.contains (template);
}
/** Create a hierarchy of templates.
* @return a node representing all possible templates
*/
public static Node getTemplateRoot () {
RootChildren ch = new RootChildren ();
// create the root
return ch.getRootFolder ().new FolderNode (ch);
}
/** 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;
TemplateActionListener() {}
public boolean acceptNodes (Node[] nodes) {
if ((nodes == null) || (nodes.length != 1)) {
return false;
}
Node n = nodes[0];
DataObject obj = (DataObject)n.getCookie (DataObject.class);
if (obj == null || !obj.isTemplate ()) {
// do not accept
return false;
}
// in this case the modified wizard will be used as default
TemplateWizard wizard = getWizard (null);
try {
wizard.setTargetName (null);
wizard.instantiate (obj, targetFolder);
} catch (IOException e) {
ErrorManager em = ErrorManager.getDefault();
em.annotate(e, NbBundle.getMessage(NewTemplateAction.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 = WeakListener.node (this, null);
/** Instance not connected to any node.
*/
public RootChildren () {
TopComponent.Registry reg = WindowManager.getDefault ().getRegistry ();
reg.addPropertyChangeListener (WeakListener.propertyChange (this, reg));
updateWizard (getWizard (null));
}
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 () {
DataFolder df = wizard.getTemplatesFolder ();
setKeys (df.getNodeDelegate ().getChildren ().getNodes (true));
// #31152 - the rootFolder must be set after the children were prepared
rootFolder = df;
}
/** 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() {}
}
}