blob: 0828618d3861dd67968a74677f00f985f2564d13 [file] [log] [blame]
/*
* 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.netbeans.modules.properties;
import java.awt.Component;
import java.awt.datatransfer.Transferable;
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.List;
import java.util.Locale;
import org.openide.DialogDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataNode;
import org.openide.loaders.DataObject;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.nodes.NodeTransfer;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.nodes.PropertySupport;
import org.openide.nodes.Sheet;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.WeakListeners;
import org.openide.util.datatransfer.NewType;
import org.openide.util.datatransfer.PasteType;
import org.openide.util.NbBundle;
/**
* Node representing a <code>PropertiesDataObject</code>.
* Its children ({@link PropertiesLocaleNode}s) represent
* the {@link PropertiesFileEntry} PropertyFileEntries.
*
* @author Petr Jiricka, Peter Zavadsky
* @see PropertiesDataObject
* @see org.openide.loaders.DataNode
*/
public class PropertiesDataNode extends DataNode {
/**
* Listener for changes on <code>propDataObject</code> name and cookie properties.
* Changes display name of components accordingly.
*/
private final transient PropertyChangeListener dataObjectListener;
private boolean multiLocale;
static final String PROPERTY_ENCODING = "projectEncoding";
PropertiesDataNode(PropertiesDataObject propDO, Lookup lookup) {
this(propDO, createChildren(propDO), lookup);
multiLocale = propDO.isMultiLocale();
}
/** Creates data node for a given data object.
* The provided children object will be used to hold all child nodes.
* @param dataObject object to work with
* @param children container for the node
*/
public PropertiesDataNode(DataObject dataObject, Children children, Lookup lookup) {
super(dataObject, children, lookup);
setIconBaseWithExtension("org/netbeans/modules/properties/propertiesObject.png"); // NOI18N
dataObjectListener = new NameUpdater();
dataObject.addPropertyChangeListener(
WeakListeners.propertyChange(dataObjectListener, dataObject));
}
private static Children createChildren(PropertiesDataObject propDO) {
return ((PropertiesFileEntry) propDO.getPrimaryEntry()).getChildren();
// used to use propDO.getChildren() if propDO.isMultiLocale()
// but that is now always false, and anyway too slow to call isMultiLocale here
// (observed 1811 msec), should avoid calculations unless and until children expanded
}
/**
* Listener which listens on changes of the set of
* <code>PropertiesDataObject</code>'s files.
* When the set of files changes, we fire a change of the DataObject's name,
* thus forcing update of the display name. We need this update because
* the CVS status of the PropertiesDataObject may change when the set
* of files is changed.
*/
final class NameUpdater implements PropertyChangeListener {
@Override
public void propertyChange(PropertyChangeEvent e) {
if (DataObject.PROP_FILES.equals(e.getPropertyName())) {
PropertiesDataObject propDO = (PropertiesDataObject) getDataObject();
propDO.fireNameChange();
// If the number of locales changes to more than one or down to
// one, we must exchange the children.
boolean newMultiLocale = propDO.isMultiLocale();
if (newMultiLocale != multiLocale) {
multiLocale = newMultiLocale;
setChildren(createChildren(propDO));
}
}
}
}
/** Gets new types that can be created in this node.
* @return array with <code>NewLocaleType</code> */
@Override
public NewType[] getNewTypes() {
PropertiesDataObject propDO = (PropertiesDataObject) getDataObject();
if (propDO.isMultiLocale()) {
return new NewType[] {new NewLocaleType()};
} else {
PropertiesFileEntry pfEntry = (PropertiesFileEntry) propDO.getPrimaryEntry();
return new NewType[] { new NewLocaleType(),
new PropertiesLocaleNode.NewPropertyType(pfEntry) };
}
}
/** Indicates whether this node has customizer. Overrides superclass method.
* @return true */
@Override
public boolean hasCustomizer() {
return true;
}
/** Gets node customizer. Overrides superclass method.
* @return <code>BundleNodeCustomizer</code> instance.
* @see BundleNodeCustomizer */
@Override
public Component getCustomizer() {
return new BundleNodeCustomizer((PropertiesDataObject)getDataObject());
}
@Override
public void createPasteTypes(Transferable transferable,
List<PasteType> types) {
super.createPasteTypes(transferable, types);
Element.ItemElem item;
Node node = NodeTransfer.node(transferable, NodeTransfer.MOVE);
if (node != null && node.canDestroy()) {
item = node.getCookie(Element.ItemElem.class);
if (item == null) {
return;
}
Node itemNode = getChildren().findChild(item.getKey());
if (node.equals(itemNode)) {
return;
}
types.add(new EntryPasteType(item, node));
} else {
item = NodeTransfer.cookie(transferable,
NodeTransfer.COPY,
Element.ItemElem.class);
if (item != null) {
types.add(new EntryPasteType(item, null));
}
}
}
@Override
protected Sheet createSheet() {
Sheet sheet = super.createSheet();
Sheet.Set set = new Sheet.Set();
set.setName("encoding"); //NOI18N
set.setDisplayName(NbBundle.getMessage(PropertiesDataNode.class, "ENCODING_SET_Name"));
if (set == null) {
set = Sheet.createPropertiesSet();
sheet.put(set);
}
set.put(new ProjectEncodingProperty());
sheet.put(set);
return sheet;
}
private PropertiesDataObject getPropertiesDataObject() {
return (PropertiesDataObject)getDataObject();
}
private PropertiesFileEntry getPropertiesFileEntry() {
return (PropertiesFileEntry)getPropertiesDataObject().getPrimaryEntry();
}
private PropertiesStructure getPropertiesStructure() {
return getPropertiesFileEntry().getHandler().getStructure();
}
/**
* A {@link PasteType} for pasting the key nodes of properties files. This
* class adds or updates the property key, value and comment of the copied
* node to the properties file of this {@link PropertiesDataNode}. Also
* destroys the copied node in case a cut action was performed.
*/
private class EntryPasteType extends PasteType {
/**
* The {@link Element.ItemElem} to paste.
*/
private final Element.ItemElem item;
/**
* The {@link Node} to destroy in case of a cut action.
*/
private final Node node;
/**
* Creates a new instance of {@link EntryPasteType}.
*
* @param item the {@link Element.ItemElem} to paste
* @param node the {@link Node} to destroy in case a cut action was
* performed, otherwise it should be {@code null}
*/
public EntryPasteType(final Element.ItemElem item, final Node node) {
this.item = item;
this.node = node;
}
@Override
public Transferable paste() throws IOException {
final PropertiesStructure ps = getPropertiesStructure();
final Element.ItemElem storedItem = ps.getItem(item.getKey());
if (storedItem == null) {
ps.addItem(item);
} else {
storedItem.setValue(item.getValue());
storedItem.setComment(item.getComment());
}
if (node != null) {
node.destroy();
}
return null;
}
}
/** New type for properties node. It creates new locale for ths bundle. */
private class NewLocaleType extends NewType {
/** Overrides superclass method. */
@Override
public String getName() {
return NbBundle.getBundle(PropertiesDataNode.class).getString("LAB_NewLocaleAction");
}
/** Overrides superclass method. */
@Override
public void create() throws IOException {
final PropertiesDataObject propertiesDataObject =
(PropertiesDataObject)getCookie(DataObject.class);
final Dialog[] dialog = new Dialog[1];
final LocalePanel panel = new LocalePanel();
DialogDescriptor dialogDescriptor = new DialogDescriptor(
panel,
NbBundle.getBundle(PropertiesDataNode.class).getString("CTL_NewLocaleTitle"),
true,
DialogDescriptor.OK_CANCEL_OPTION,
DialogDescriptor.OK_OPTION,
new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
if (evt.getSource() == DialogDescriptor.OK_OPTION) {
if (containsLocale(propertiesDataObject, panel.getLocale())) {
NotifyDescriptor.Message msg = new NotifyDescriptor.Message(
MessageFormat.format(NbBundle.getBundle(PropertiesDataNode.class).getString("MSG_LangExists"), panel.getLocale()),
NotifyDescriptor.ERROR_MESSAGE);
DialogDisplayer.getDefault().notify(msg);
} else {
Util.createLocaleFile(propertiesDataObject, panel.getLocale().toString(), true);
dialog[0].setVisible(false);
dialog[0].dispose();
}
}
}
}
);
dialogDescriptor.setClosingOptions(new Object [] { DialogDescriptor.CANCEL_OPTION });
dialog[0] = DialogDisplayer.getDefault().createDialog(dialogDescriptor);
dialog[0].setVisible(true);
}
} // End of NewLocaleType class.
private static boolean containsLocale(PropertiesDataObject propertiesDataObject, Locale locale) {
FileObject file = propertiesDataObject.getBundleStructure().getNthEntry(0).getFile();
// FileObject file = propertiesDataObject.getPrimaryFile();
String newName = file.getName() + PropertiesDataLoader.PRB_SEPARATOR_CHAR + locale;
BundleStructure structure = propertiesDataObject.getBundleStructure();
for (int i = 0; i<structure.getEntryCount();i++) {
FileObject f = structure.getNthEntry(i).getFile();
if (newName.startsWith(f.getName()) && f.getName().length() > file.getName().length())
file = f;
}
return file.getName().equals(newName);
}
private final class ProjectEncodingProperty extends PropertySupport {
public ProjectEncodingProperty() {
super(PROPERTY_ENCODING,
Boolean.class,
NbBundle.getMessage(PropertiesDataNode.class, "PROP_ENCODING_Name"), // NOI18N
NbBundle.getMessage(PropertiesDataNode.class, "PROP_ENCODING_Hint"), // NOI18N
true, true);
}
@Override
public Object getValue() throws InvocationTargetException {
Object attribute = getDataObject().getPrimaryFile().getAttribute(PROPERTY_ENCODING);
if (attribute == null) {
return false;
}
return attribute;
}
@Override
public void setValue(Object val) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
try {
getDataObject().getPrimaryFile().setAttribute(PROPERTY_ENCODING, val);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
PropertiesEditorSupport propertiesEditor = getPropertiesFileEntry().getPropertiesEditor();
propertiesEditor.resetCharset();
if (propertiesEditor.hasOpenedEditorComponent()) {
NotifyDescriptor.Message message = new NotifyDescriptor.Message(
NbBundle.getMessage(PropertiesDataNode.class, "PROP_ENCODING_Warning", PropertiesDataNode.this.getDisplayName()),
NotifyDescriptor.WARNING_MESSAGE);
DialogDisplayer.getDefault().notify(message);
propertiesEditor.close();
}
}
}
}