| /* |
| * 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.EventQueue; |
| import java.awt.GridBagConstraints; |
| import java.awt.GridBagLayout; |
| import java.awt.Image; |
| import java.awt.Rectangle; |
| import java.awt.event.ActionListener; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.beans.PropertyChangeSupport; |
| import java.beans.PropertyVetoException; |
| import java.beans.VetoableChangeListener; |
| import java.beans.VetoableChangeSupport; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.io.Serializable; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.Action; |
| import javax.swing.event.ChangeListener; |
| import javax.swing.JPanel; |
| import javax.swing.JTable; |
| import javax.swing.SwingUtilities; |
| import javax.swing.undo.CannotRedoException; |
| import javax.swing.undo.CannotUndoException; |
| |
| import org.openide.actions.FindAction; |
| import org.openide.awt.UndoRedo; |
| import org.openide.cookies.CloseCookie; |
| import org.openide.cookies.OpenCookie; |
| import org.openide.cookies.SaveCookie; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileStateInvalidException; |
| import org.openide.filesystems.FileStatusEvent; |
| import org.openide.filesystems.FileStatusListener; |
| import org.openide.filesystems.FileSystem; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.DataObjectNotFoundException; |
| import org.openide.nodes.Node; |
| import org.openide.NotifyDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.ErrorManager; |
| import org.openide.nodes.Node.Cookie; |
| import org.openide.util.Exceptions; |
| import org.openide.util.HelpCtx; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.Mutex; |
| import org.openide.util.NbBundle; |
| import org.openide.util.WeakListeners; |
| import org.openide.util.WeakSet; |
| import org.openide.util.actions.SystemAction; |
| import org.openide.windows.CloneableOpenSupport; |
| import org.openide.windows.CloneableTopComponent; |
| import org.openide.DialogDescriptor; |
| import org.openide.text.DataEditorSupport; |
| |
| |
| /** |
| * Support for opening bundle of .properties files (OpenCookie) in table view editor. |
| * |
| * @author Petr Jiricka, Peter Zavadsky |
| */ |
| public class PropertiesOpen extends CloneableOpenSupport |
| implements OpenCookie, CloseCookie { |
| |
| private static final Logger LOG = Logger.getLogger(PropertiesOpen.class.getName()); |
| /** Main properties dataobject */ |
| @Deprecated |
| PropertiesDataObject propDataObject; |
| |
| |
| private List<PropertiesDataObject> dataObjectList; |
| |
| private BundleStructure bundleStructure; |
| /** Listener for modificationc on dataobject, adding and removing save cookie */ |
| PropertyChangeListener modifL; |
| |
| HashMap<PropertiesDataObject,PropertyChangeListener> weakModifiedListeners; |
| |
| /** UndoRedo manager for this properties open support */ |
| protected transient UndoRedo undoRedoManager; |
| |
| /** This object is used for marking all undoable edits performed as one atomic undoable action. */ |
| transient Object atomicUndoRedoFlag; |
| |
| private transient Object UPDATE_LOCK = new Object(); |
| |
| |
| /** Constructor */ |
| @Deprecated |
| public PropertiesOpen(PropertiesDataObject propDataObject) { |
| super(new Environment(propDataObject)); |
| |
| this.propDataObject = propDataObject; |
| |
| //PENDING Add Listeners for all DataObject from this OpenSupport |
| this.propDataObject.addPropertyChangeListener(WeakListeners.propertyChange(modifL = |
| new ModifiedListener(), this.propDataObject)); |
| } |
| |
| public PropertiesOpen(BundleStructure structure) { |
| super(new Environment(structure)); |
| |
| this.bundleStructure = structure; |
| //Listeners added later in addDataObject method |
| // addModifiedListeners(); |
| dataObjectList = new ArrayList<PropertiesDataObject>(); |
| weakModifiedListeners = new HashMap<PropertiesDataObject, PropertyChangeListener>(); |
| modifL = new ModifiedListener(); |
| } |
| |
| protected void removeModifiedListener(PropertiesDataObject dataObject) { |
| PropertyChangeListener l = weakModifiedListeners.remove(dataObject); |
| if (l!=null) { |
| dataObject.removePropertyChangeListener(l); |
| } |
| PropertiesCloneableTopComponent topComp = (PropertiesCloneableTopComponent) allEditors.getArbitraryComponent(); |
| if (topComp != null) { |
| topComp.dataObjectRemoved(dataObject); |
| } |
| dataObjectList.remove(dataObject); |
| } |
| |
| protected void addDataObject(PropertiesDataObject dataObject) { |
| PropertyChangeListener l = weakModifiedListeners.get(dataObject); |
| if (l != null) { |
| dataObject.removePropertyChangeListener(l); |
| } else { |
| l = WeakListeners.propertyChange(modifL, dataObject); |
| weakModifiedListeners.put(dataObject,l); |
| } |
| dataObject.addPropertyChangeListener(l); |
| ((Environment)env).addListener(dataObject); |
| if (((Environment)env).isModified()) |
| try { |
| ((Environment) env).markModified(dataObject); |
| if (dataObject.getCookie(SaveCookie.class) == null) { |
| dataObject.getCookieSet0().add((Cookie) modifL); |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| PropertiesCloneableTopComponent topComp = (PropertiesCloneableTopComponent) allEditors.getArbitraryComponent(); |
| if (topComp != null) { |
| topComp.dataObjectAdded(dataObject); |
| } |
| if (!dataObjectList.contains(dataObject)) { |
| dataObjectList.add(dataObject); |
| } |
| } |
| /** |
| * Tests whether all data is saved, and if not, prompts the user to save. |
| * |
| * @return {@code true} if everything can be closed |
| */ |
| @Override |
| protected boolean canClose() { |
| PropertiesDataObject dataObject; |
| SaveCookie saveCookie = null; |
| HashMap<SaveCookie,PropertiesDataObject> map = new HashMap<SaveCookie,PropertiesDataObject>(); |
| for (PropertiesFileEntry e : bundleStructure.getEntries()) { |
| dataObject = (PropertiesDataObject) e.getDataObject(); |
| saveCookie = dataObject.getCookie(SaveCookie.class); |
| //Need to find all saveCookie |
| if (saveCookie != null) map.put(saveCookie, dataObject); |
| } |
| if (map.isEmpty()) { |
| return true; |
| } |
| stopEditing(); |
| if (!shouldAskSave()) { |
| return true; |
| } |
| |
| /* Create and display a confirmation dialog - Save/Discard/Cancel: */ |
| String title = NbBundle.getMessage(PropertiesOpen.class, |
| "CTL_Question"); //NOI18N |
| String question = NbBundle.getMessage(PropertiesOpen.class, |
| "MSG_SaveFile", //NOI18N |
| bundleStructure.getNthEntry(0).getName()); |
| String optionSave = NbBundle.getMessage(PropertiesOpen.class, |
| "CTL_Save"); //NOI18N |
| String optionDiscard = NbBundle.getMessage(PropertiesOpen.class, |
| "CTL_Discard"); //NOI18N |
| NotifyDescriptor descr = new DialogDescriptor( |
| question, |
| title, //title |
| true, //modal |
| new Object[] {optionSave, |
| optionDiscard, |
| NotifyDescriptor.CANCEL_OPTION}, |
| optionSave, //default option |
| DialogDescriptor.DEFAULT_ALIGN, //alignment of the options |
| null, //help context |
| (ActionListener) null); |
| descr.setMessageType(NotifyDescriptor.QUESTION_MESSAGE); |
| Object answer = DialogDisplayer.getDefault().notify(descr); |
| |
| /* Save the file if the answer was "Save": */ |
| if (answer == optionSave) { |
| try { |
| for (SaveCookie save : map.keySet()) { |
| save.save(); |
| map.get(save).updateModificationStatus(); |
| } |
| } |
| catch (IOException e) { |
| ErrorManager.getDefault().notify(e); |
| return false; |
| } |
| } |
| dataObject = null; |
| for (int i=0;i<bundleStructure.getEntryCount();i++) { |
| dataObject = (PropertiesDataObject) bundleStructure.getNthEntry(i).getDataObject(); |
| dataObject.updateModificationStatus(); |
| } |
| |
| return (answer == optionSave || answer == optionDiscard); |
| } |
| |
| private void stopEditing() { |
| saveEditorValues(true); |
| } |
| |
| /** |
| * Saves values of cells being edited and optionally stops the cell editing. |
| * |
| * @param saveValueOnly if {@code true}, just the cell values will be |
| * saved to the model but the editor remains active |
| */ |
| private void saveEditorValues(boolean stopEditing) { |
| Enumeration en = allEditors.getComponents(); |
| if (en.hasMoreElements() == false) { |
| PropertiesCloneableTopComponent topComp = (PropertiesCloneableTopComponent) allEditors.getArbitraryComponent(); |
| if (topComp != null) { |
| BundleEditPanel bep = (BundleEditPanel) topComp.getComponent(0); |
| if (stopEditing) { |
| bep.stopEditing(); |
| } else { |
| bep.saveEditorValue(false); |
| } |
| } |
| } |
| while (en.hasMoreElements()) { |
| Object o = en.nextElement(); |
| if (o instanceof PropertiesCloneableTopComponent) { |
| BundleEditPanel bep = (BundleEditPanel)((PropertiesCloneableTopComponent)o).getComponent(0); |
| if (stopEditing) { |
| bep.stopEditing(); |
| } else { |
| bep.saveEditorValue(false); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * A method to create a new component. |
| * @return the cloneable top component for this support |
| */ |
| @Override |
| protected CloneableTopComponent createCloneableTopComponent() { |
| // return new PropertiesCloneableTopComponent(propDataObject); |
| return new PropertiesCloneableTopComponent(bundleStructure); |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Message to display when an object is being opened. |
| * @return the message or null if nothing should be displayed |
| */ |
| @Override |
| protected String messageOpening() { |
| bundleStructure.updateEntries(); |
| PropertiesFileEntry primaryEntry = bundleStructure.getNthEntry(0); |
| String primaryEntryName = primaryEntry != null ? primaryEntry.getName() : ""; |
| FileObject fo = primaryEntry != null ? primaryEntry.getFile() : null; |
| String primaryEntryFile = fo != null ? fo.toString() : "" ; // #190125 |
| return NbBundle.getMessage(PropertiesOpen.class, |
| "LBL_ObjectOpen", // NOI18N |
| primaryEntryName, |
| primaryEntryFile); |
| } |
| |
| /** |
| * Overrides superclass abstract method. |
| * Message to display when an object has been opened. |
| * @return the message or null if nothing should be displayed |
| */ |
| @Override |
| protected String messageOpened() { |
| return NbBundle.getMessage(PropertiesOpen.class, "LBL_ObjectOpened"); // NOI18N |
| } |
| |
| /** @return whether has open table view component. */ |
| public synchronized boolean hasOpenedTableComponent() { |
| return !allEditors.isEmpty(); |
| } |
| |
| /** Gets UndoRedo manager for this OpenSupport. */ |
| public UndoRedo getUndoRedo () { |
| if (undoRedoManager == null) |
| undoRedoManager = new CompoundUndoRedoManager(bundleStructure); |
| return undoRedoManager; |
| } |
| |
| /** Helper method. Closes documents. */ |
| private synchronized void closeDocuments() { |
| for (int i = 0; i< bundleStructure.getEntryCount(); i++) { |
| PropertiesFileEntry nthEntry = bundleStructure.getNthEntry(i); |
| if (nthEntry != null) { |
| closeEntry(nthEntry); |
| } |
| } |
| } |
| |
| /** Helper method. Closes entry. */ |
| private void closeEntry(PropertiesFileEntry entry) { |
| PropertiesEditorSupport editorSupport = entry.getPropertiesEditor(); |
| if (editorSupport.hasOpenedEditorComponent()) { |
| // Has opened editor view for this entry -> don't close document. |
| return; |
| } else { |
| // Hasn't opened editor view for this entry -> close document. |
| editorSupport.forceNotifyClosed(); |
| |
| // #17221. Don't reparse invalid or virtual file. |
| if(entry.getFile().isValid() && !entry.getFile().isVirtual()) { |
| entry.getHandler().autoParse(); |
| } |
| } |
| } |
| |
| /** |
| * Helper method. Should be called only if the object has SaveCookie |
| * @return true if closing this editor whithout saving would result in loss of data |
| * because al least one of the modified files is not open in the code editor |
| */ |
| private boolean shouldAskSave() { |
| // for each entry : if there is a SaveCookie and no open editor component, return true. |
| // if passed for all entries, return false |
| BundleStructure structure = bundleStructure; |
| PropertiesFileEntry entry; |
| SaveCookie savec; |
| for (int i = 0; i < structure.getEntryCount(); i++) { |
| entry = structure.getNthEntry(i); |
| savec = entry.getCookie(SaveCookie.class); |
| if ((savec != null) && !entry.getPropertiesEditor().hasOpenedEditorComponent()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| /** Environment that connects the open support together with {@code DataObject}. */ |
| private static class Environment implements CloneableOpenSupport.Env, Serializable, |
| PropertyChangeListener, VetoableChangeListener { |
| |
| /** Generated Serialized Version UID */ |
| static final long serialVersionUID = -1934890789745432531L; |
| |
| /** Object to serialize and be connected to. */ |
| @Deprecated |
| private DataObject dataObject; |
| |
| private BundleStructure bundleStructure; |
| |
| /** Support for firing of property changes. */ |
| private transient PropertyChangeSupport propSupp; |
| |
| /** Support for firing of vetoable changes. */ |
| private transient VetoableChangeSupport vetoSupp; |
| |
| private transient HashMap<PropertiesDataObject, PropertyChangeListener> weakEnvPropListeners; |
| private transient HashMap<PropertiesDataObject, VetoableChangeListener> weakEnvVetoListeners; |
| |
| /** |
| * Constructor. Attaches itself as listener to |
| * the data object so, all property changes of the data object |
| * are also rethrown to own listeners. |
| * @param dataObject data object to be attached to |
| */ |
| @Deprecated |
| public Environment(PropertiesDataObject dataObject) { |
| this.dataObject = dataObject; |
| dataObject.addPropertyChangeListener(WeakListeners.propertyChange(this, dataObject)); |
| dataObject.addVetoableChangeListener(WeakListeners.vetoableChange(this, dataObject)); |
| } |
| |
| public Environment(BundleStructure structure) { |
| this.bundleStructure = structure; |
| PropertiesFileEntry entry = bundleStructure.getNthEntry(0); |
| if (entry != null) |
| dataObject = entry.getDataObject(); |
| //Listeners added later by addListener method |
| // addListeners(); |
| weakEnvPropListeners = new HashMap<PropertiesDataObject, PropertyChangeListener>(); |
| weakEnvVetoListeners = new HashMap<PropertiesDataObject, VetoableChangeListener>(); |
| } |
| |
| |
| private void addListener(PropertiesDataObject dataObj) { |
| PropertyChangeListener l =weakEnvPropListeners.get(dataObj); |
| VetoableChangeListener v = weakEnvVetoListeners.get(dataObj); |
| if (l != null) { |
| dataObj.removePropertyChangeListener(l); |
| } else { |
| l = WeakListeners.propertyChange(this, dataObj); |
| weakEnvPropListeners.put(dataObj, l); |
| } |
| if (v != null) { |
| dataObj.removeVetoableChangeListener(v); |
| } else { |
| v = WeakListeners.vetoableChange(this, dataObj); |
| weakEnvVetoListeners.put(dataObj, v); |
| } |
| dataObj.addPropertyChangeListener(l); |
| dataObj.addVetoableChangeListener(v); |
| } |
| |
| private void addListeners() { |
| BundleStructure structure = bundleStructure; |
| PropertiesDataObject dataObj; |
| weakEnvPropListeners = new HashMap<PropertiesDataObject, PropertyChangeListener>(); |
| weakEnvVetoListeners = new HashMap<PropertiesDataObject, VetoableChangeListener>(); |
| PropertyChangeListener l; |
| VetoableChangeListener v; |
| for(int i=0;i<structure.getEntryCount();i++) { |
| dataObj = (PropertiesDataObject) structure.getNthEntry(i).getDataObject(); |
| l = WeakListeners.propertyChange(this, dataObj); |
| weakEnvPropListeners.put(dataObj, l); |
| dataObj.addPropertyChangeListener(l); |
| v = WeakListeners.vetoableChange(this, dataObj); |
| weakEnvVetoListeners.put(dataObj, v); |
| dataObj.addVetoableChangeListener(v); |
| } |
| |
| } |
| |
| private void removeListeners() { |
| BundleStructure structure = bundleStructure; |
| PropertiesDataObject dataObj; |
| PropertyChangeListener l; |
| VetoableChangeListener v; |
| for(int i=0;i<structure.getEntryCount();i++) { |
| dataObj = (PropertiesDataObject) structure.getNthEntry(i).getDataObject(); |
| l = weakEnvPropListeners.remove(dataObj); |
| v = weakEnvVetoListeners.remove(dataObj); |
| if (l!=null) { |
| dataObj.removePropertyChangeListener(l); |
| } |
| if (v!=null) { |
| dataObj.removeVetoableChangeListener(v); |
| } |
| } |
| } |
| |
| /** Implements {@code CloneableOpenSupport.Env} interface. Adds property listener. */ |
| @Override |
| public void addPropertyChangeListener(PropertyChangeListener l) { |
| prop().addPropertyChangeListener(l); |
| } |
| |
| /** Implements {@code CloneableOpenSupport.Env} interface. Removes property listener. */ |
| @Override |
| public void removePropertyChangeListener(PropertyChangeListener l) { |
| prop().removePropertyChangeListener(l); |
| } |
| |
| /** Implements {@code CloneableOpenSupport.Env} interface. Adds veto listener. */ |
| @Override |
| public void addVetoableChangeListener(VetoableChangeListener l) { |
| veto().addVetoableChangeListener(l); |
| } |
| |
| /** Implements {@code CloneableOpenSupport.Env} interface. Removes veto listener. */ |
| @Override |
| public void removeVetoableChangeListener(VetoableChangeListener l) { |
| veto().removeVetoableChangeListener(l); |
| } |
| |
| /** |
| * Implements {@code CloneableOpenSupport} interface. |
| * Method that allows environment to find its cloneable open support. |
| * @return the support or null if the environemnt is not in valid |
| * state and the CloneableOpenSupport cannot be found for associated |
| * data object |
| */ |
| @Deprecated |
| //TODO PENDING Called from super class need to preserve |
| @Override |
| public CloneableOpenSupport findCloneableOpenSupport() { |
| if (dataObject != null) |
| return (CloneableOpenSupport) dataObject.getCookie(OpenCookie.class); |
| return null; |
| } |
| |
| public CloneableOpenSupport findCloneableOpenSupport(PropertiesDataObject dataObject) { |
| if (dataObject != null) |
| return (CloneableOpenSupport)dataObject.getCookie(OpenCookie.class); |
| return null; |
| } |
| |
| /** |
| * Implements {@code CloneableOpenSupport.Env} interface. |
| * Test whether the support is in valid state or not. |
| * It could be invalid after deserialization when the object it |
| * referenced to does not exist anymore. |
| * @return true or false depending on its state |
| */ |
| @Deprecated |
| @Override |
| public boolean isValid() { |
| return dataObject.isValid(); |
| } |
| |
| public boolean isValid(PropertiesDataObject dataObject) { |
| return dataObject.isValid(); |
| } |
| |
| /** |
| * Implements {@code CloneableOpenSupport.Env} interface. |
| * Test whether the object is modified or not. |
| * @return true if the object is modified |
| */ |
| @Override |
| public boolean isModified() { |
| //if one dataObject is modified assume that everything modified |
| PropertiesFileEntry entry; |
| for (int i=0; i < bundleStructure.getEntryCount();i++) { |
| entry = bundleStructure.getNthEntry(i); |
| if ((entry !=null) && (entry.getDataObject().isModified()) ) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean isModified(PropertiesDataObject dataObject) { |
| return dataObject.isModified(); |
| } |
| |
| /** |
| * Implements {@code CloneableOpenSupport.Env} interface. |
| * Support for marking the environement modified. |
| * @exception IOException if the environment cannot be marked modified |
| * (for example when the file is readonly), when such exception |
| * is the support should discard all previous changes |
| */ |
| @Deprecated |
| @Override |
| public void markModified() throws java.io.IOException { |
| dataObject.setModified(true); |
| } |
| |
| public void markModified(PropertiesDataObject dataObject) throws java.io.IOException { |
| dataObject.setModified(true); |
| } |
| /** |
| * Implements {@code CloneableOpenSupport.Env} interface. |
| * Reverse method that can be called to make the environment unmodified. |
| */ |
| @Deprecated |
| @Override |
| public void unmarkModified() { |
| dataObject.setModified(false); |
| } |
| |
| public void unmarkModified(PropertiesDataObject dataObject) { |
| dataObject.setModified(false); |
| } |
| |
| /** |
| * Implements {@code PropertyChangeListener} interface. |
| * Accepts property changes from {@code DataObject} and fires them to own listeners. |
| */ |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if(DataObject.PROP_MODIFIED.equals(evt.getPropertyName())) { |
| PropertiesDataObject dataObj = null; |
| if (evt.getSource() instanceof PropertiesDataObject) { |
| dataObj = (PropertiesDataObject) evt.getSource(); |
| } |
| if (dataObj!=null) { |
| if(dataObj.isModified()) { |
| dataObj.addVetoableChangeListener(this); |
| } else { |
| dataObj.removeVetoableChangeListener(this); |
| } |
| } |
| } else if(DataObject.PROP_VALID.equals(evt.getPropertyName ())) { |
| // We will handle the object invalidation here. |
| // Do not check it if old value is not true. |
| if (Boolean.FALSE.equals(evt.getOldValue())) { |
| return; |
| } |
| |
| PropertiesDataObject dataObj = null; |
| if (evt.getSource() instanceof PropertiesDataObject) { |
| dataObj = (PropertiesDataObject) evt.getSource(); |
| } |
| if (dataObj != null) { |
| PropertyChangeListener l = weakEnvPropListeners.remove(dataObj); |
| VetoableChangeListener v = weakEnvVetoListeners.remove(dataObj); |
| if (l!=null) { |
| dataObj.removePropertyChangeListener(l); |
| } |
| if (v!=null) { |
| dataObj.removeVetoableChangeListener(v); |
| } |
| // Mark the object as not being modified, so nobody |
| // will ask for save. |
| unmarkModified(dataObj); |
| bundleStructure.updateEntries(); |
| if (bundleStructure.getEntryCount() == 0) { |
| // Loosing validity. |
| PropertiesOpen support = (PropertiesOpen)findCloneableOpenSupport(); |
| if(support != null ) { |
| //bundleStructure.updateEntries(); |
| support.close(false); |
| } |
| } else { |
| bundleStructure.notifyOneFileChanged(dataObj.getPrimaryFile()); |
| } |
| } |
| } else if (DataObject.PROP_NAME.equals(evt.getPropertyName())) { |
| PropertiesDataObject dataObj = null; |
| if (evt.getSource() instanceof PropertiesDataObject) { |
| dataObj = (PropertiesDataObject) evt.getSource(); |
| } |
| if (dataObj != null) { |
| OpenCookie cookie = dataObj.getCookieSet0().getCookie(OpenCookie.class); |
| if (cookie != dataObj.getOpenSupport()) { |
| PropertyChangeListener l = weakEnvPropListeners.remove(dataObj); |
| VetoableChangeListener v = weakEnvVetoListeners.remove(dataObj); |
| if (l!=null) { |
| dataObj.removePropertyChangeListener(l); |
| } |
| if (v!=null) { |
| dataObj.removeVetoableChangeListener(v); |
| } |
| // remove old open cookie |
| if (cookie != null) { |
| // remove also open cookie factory, new open cookie will be added. |
| dataObj.getCookieSet0().remove(PropertiesOpen.class, dataObj); |
| dataObj.getCookieSet0().remove(cookie); |
| } |
| //Adds new OpenCookie to this dataObj |
| dataObj.getCookieSet0().add(dataObj.getOpenSupport()); |
| if (dataObj.getBundleStructure().getEntryCount()>1) { |
| dataObj.getBundleStructure().updateEntries(); |
| dataObj.getBundleStructure().notifyOneFileChanged(dataObj.getPrimaryFile()); |
| dataObj.getOpenSupport().open(); |
| } |
| } |
| } |
| } else if (DataObject.PROP_PRIMARY_FILE.equals(evt.getPropertyName())) { |
| //Here DataObject is valid and with new path |
| PropertiesDataObject dataObj = null; |
| if (evt.getSource() instanceof PropertiesDataObject) { |
| dataObj = (PropertiesDataObject) evt.getSource(); |
| } |
| if (dataObj != null) { |
| OpenCookie cookie = dataObj.getCookieSet0().getCookie(OpenCookie.class); |
| if (cookie != dataObj.getOpenSupport()) { |
| PropertyChangeListener l = weakEnvPropListeners.remove(dataObj); |
| VetoableChangeListener v = weakEnvVetoListeners.remove(dataObj); |
| if (l!=null) { |
| dataObj.removePropertyChangeListener(l); |
| } |
| if (v!=null) { |
| dataObj.removeVetoableChangeListener(v); |
| } |
| // remove old open cookie |
| if (cookie != null) { |
| // remove also open cookie factory, new open cookie will be added. |
| dataObj.getCookieSet0().remove(PropertiesOpen.class, dataObj); |
| dataObj.getCookieSet0().remove(cookie); |
| } |
| //Adds new OpenCookie to this dataObj |
| dataObj.getCookieSet0().add(dataObj.getOpenSupport()); |
| if (dataObj.getBundleStructure().getEntryCount()>1) { |
| dataObj.getBundleStructure().updateEntries(); |
| dataObj.getBundleStructure().notifyOneFileChanged(dataObj.getPrimaryFile()); |
| } |
| } else { |
| //shouldn't get here |
| } |
| } |
| } else { |
| firePropertyChange ( |
| evt.getPropertyName(), |
| evt.getOldValue(), |
| evt.getNewValue() |
| ); |
| } |
| } |
| |
| /** |
| * Implements <code>VetoAbleChangeListener</code> interface. |
| * Accepts vetoable changes and fires them to own listeners. |
| */ |
| @Override |
| public void vetoableChange(PropertyChangeEvent evt) throws PropertyVetoException { |
| fireVetoableChange ( |
| evt.getPropertyName(), |
| evt.getOldValue(), |
| evt.getNewValue() |
| ); |
| } |
| |
| /** Fires property change. |
| * @param name the name of property that changed |
| * @param oldValue old value |
| * @param newValue new value |
| */ |
| private void firePropertyChange (String name, Object oldValue, Object newValue) { |
| prop().firePropertyChange(name, oldValue, newValue); |
| } |
| |
| /** Fires vetoable change. |
| * @param name the name of property that changed |
| * @param oldValue old value |
| * @param newValue new value |
| */ |
| private void fireVetoableChange (String name, Object oldValue, Object newValue) throws PropertyVetoException { |
| veto().fireVetoableChange(name, oldValue, newValue); |
| } |
| |
| /** Lazy gets property change support. */ |
| private PropertyChangeSupport prop() { |
| synchronized (this) { |
| if (propSupp == null) { |
| propSupp = new PropertyChangeSupport(this); |
| } |
| } |
| return propSupp; |
| } |
| |
| /** Lazy gets vetoable change support. */ |
| private VetoableChangeSupport veto() { |
| synchronized (this) { |
| if (vetoSupp == null) { |
| vetoSupp = new VetoableChangeSupport(this); |
| } |
| } |
| return vetoSupp; |
| } |
| } // End of inner class Environment. |
| |
| |
| /** Inner class. Listens to modifications and updates save cookie. */ |
| private final class ModifiedListener implements SaveCookie, PropertyChangeListener { |
| |
| |
| /** Gives notification that the DataObject was changed. |
| * @param ev PropertyChangeEvent |
| */ |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| // Data object changed, reset the UndoRedo manager. |
| if (DataObject.PROP_MODIFIED.equals(evt.getPropertyName())) { |
| if (evt.getSource() instanceof PropertiesDataObject) { |
| PropertiesDataObject dataObject = (PropertiesDataObject) evt.getSource(); |
| if (!dataObject.isValid()) { |
| if (!((Boolean)evt.getNewValue()).booleanValue()) { |
| removeSaveCookie(dataObject); |
| } |
| } else |
| if(bundleStructure.getEntryByFileName(dataObject.getName())!=null) { |
| ((CompoundUndoRedoManager) PropertiesOpen.this.getUndoRedo()).reset(bundleStructure); |
| if (((Boolean)evt.getNewValue()).booleanValue()) { |
| addSaveCookie(dataObject); |
| } else { |
| removeSaveCookie(dataObject); |
| } |
| } |
| } |
| } |
| } |
| |
| /** Implements {@code SaveCookie} interface. */ |
| @Override |
| public void save() throws IOException { |
| /* |
| * At first, save the value of the cell being edited, |
| * without making any UI changes. |
| */ |
| saveEditorValues(false); |
| |
| // do saving job |
| saveDocument(); |
| |
| /* Update the UI: */ |
| Mutex.EVENT.writeAccess(new Runnable() { |
| @Override |
| public void run() { |
| stopEditing(); |
| } |
| }); |
| } |
| |
| /** Save the document in this thread. |
| * Create "orig" document for the case that the save would fail. |
| * @exception IOException on I/O error |
| */ |
| public void saveDocument() throws IOException { |
| BundleStructure structure = bundleStructure; |
| SaveCookie save; |
| for (int i=0; i<structure.getEntryCount();i++) { |
| PropertiesFileEntry pfe = structure.getNthEntry(i); |
| if(pfe != null) { // #184927 |
| save = pfe.getCookie(SaveCookie.class); |
| if (save != null) { |
| save.save(); |
| } |
| } |
| } |
| } |
| |
| /** Adds save cookie to the dataobject. */ |
| @Deprecated |
| private void addSaveCookie() { |
| if(propDataObject.getCookie(SaveCookie.class) == null) { |
| propDataObject.getCookieSet0().add(this); |
| } |
| } |
| |
| private void addSaveCookie(PropertiesDataObject propDataObject) { |
| if(propDataObject.getCookie(SaveCookie.class) == null) { |
| propDataObject.getCookieSet0().add(this); |
| } |
| } |
| |
| /** Removes save cookie from the dataobject. */ |
| @Deprecated |
| private void removeSaveCookie() { |
| if(propDataObject.getCookie(SaveCookie.class) == this) { |
| propDataObject.getCookieSet0().remove(this); |
| } |
| } |
| |
| private void removeSaveCookie(PropertiesDataObject propDataObject) { |
| if(propDataObject.getCookie(SaveCookie.class) == this) { |
| propDataObject.getCookieSet0().remove(this); |
| } |
| } |
| } // End of inner class ModifiedListener. |
| |
| |
| /** Inner class for opening at a given key. */ |
| public class PropertiesOpenAt implements OpenCookie { |
| |
| /** Entry the key belongs to. */ |
| private PropertiesFileEntry entry; |
| |
| /** Key where to open at. */ |
| private String key; |
| |
| |
| /** Construcor. */ |
| PropertiesOpenAt(PropertiesFileEntry entry, String key) { |
| this.entry = entry; |
| this.key = key; |
| } |
| |
| |
| /** Setter for key property. */ |
| public void setKey(String key) { |
| this.key = key; |
| } |
| |
| /** Implements {@code OpenCookie}. Opens document. */ |
| @Override |
| public void open() { |
| // Instead of PropertiesOpen.super.open() so we get reference to TopComponent. |
| // Note: It is strange for me that calling PropetiesOpen.this.openCloneableTopComponent throw s exception at run-time. |
| final PropertiesCloneableTopComponent editor = (PropertiesCloneableTopComponent)PropertiesOpen.super.openCloneableTopComponent(); |
| editor.requestActive(); |
| |
| BundleStructure bs = bundleStructure; |
| bs.updateEntries(); |
| // Find indexes. |
| int entryIndex = bs.getEntryIndexByFileName(entry.getFile().getName()); |
| int rowIndex = bs.getKeyIndexByName(key); |
| |
| if ((entryIndex != -1) && (rowIndex != -1)) { |
| final int row = rowIndex; |
| final int column = entryIndex + 1; |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| JTable table = ((BundleEditPanel)editor.getComponent(0)).getTable(); |
| // Autoscroll to cell if possible and necessary. |
| if (table.getAutoscrolls()) { |
| Rectangle cellRect = table.getCellRect(row, column, false); |
| if (cellRect != null) { |
| table.scrollRectToVisible(cellRect); |
| } |
| } |
| |
| // Update selection & edit. |
| //XXX This caused to open properties for editing with wrong values |
| // table.getColumnModel().getSelectionModel().setSelectionInterval(row, column); |
| // table.getSelectionModel().setSelectionInterval(row, column); |
| |
| table.editCellAt(row, column); |
| } |
| }); |
| } |
| } |
| } // End of inner class PropertiesOpenAt. |
| |
| |
| |
| /** Cloneable top component which represents table view of resource bundles. */ |
| public static class PropertiesCloneableTopComponent extends CloneableTopComponent { |
| |
| /** Reference to underlying {@code PropertiesDataObject}. */ |
| @Deprecated |
| private PropertiesDataObject propDataObject; |
| |
| private List<PropertiesDataObject> dataObjectsList; |
| /** Listener for changes on {@code propDataObject} name and cookie properties. |
| * Changes display name of components accordingly. */ |
| private transient PropertyChangeListener nameUpdaterListener; |
| |
| private transient static HashMap<PropertiesDataObject,PropertyChangeListener> weakNameUpdateListeners; |
| |
| /** Generated serial version UID. */ |
| static final long serialVersionUID =2836248291419024296L; |
| |
| private final static transient Object UPDATE_LOCK = new Object(); |
| /** Default constructor for deserialization. */ |
| public PropertiesCloneableTopComponent() { |
| } |
| |
| @Override |
| protected void componentHidden() { |
| ((BundleEditPanel)getComponent(0)).getTable().firePropertyChange("componentHidden", 0, 1); //NOI18N |
| super.componentHidden(); |
| } |
| |
| /** Constructor. |
| * @param propDataObject data object we belong to */ |
| @Deprecated |
| public PropertiesCloneableTopComponent (PropertiesDataObject propDataObject) { |
| this.propDataObject = propDataObject; |
| |
| initialize(); |
| } |
| |
| private MultiBundleStructure bundleStructure; |
| |
| public PropertiesCloneableTopComponent(BundleStructure structure) { |
| this.bundleStructure = (MultiBundleStructure) structure; |
| propDataObject = (PropertiesDataObject) bundleStructure.getNthEntry(0).getDataObject(); |
| dataObjectsList = new ArrayList<PropertiesDataObject>(); |
| for (int i=0; i<bundleStructure.getEntryCount();i++) { |
| dataObjectsList.add((PropertiesDataObject)bundleStructure.getNthEntry(i).getDataObject()); |
| } |
| weakNameUpdateListeners = new HashMap<PropertiesDataObject, PropertyChangeListener>(); |
| initialize(); |
| } |
| /** |
| */ |
| @Override |
| public void open() { |
| if (discard()) { |
| return; |
| } |
| super.open(); |
| } |
| |
| @Override |
| public void requestActive() { |
| super.requestActive(); |
| getComponent(0).requestFocusInWindow(); |
| } |
| |
| @Override |
| public boolean canClose () { |
| ((BundleEditPanel)getComponent(0)).stopEditing(); |
| return super.canClose(); |
| } |
| |
| /** Initializes this instance. Used by construction and deserialization. */ |
| private void initialize() { |
| initComponents(); |
| setupActions(); |
| BundleStructure structure = bundleStructure; |
| PropertiesDataObject dataObject; |
| |
| Node[] node = new Node[structure.getEntryCount()]; |
| nameUpdaterListener = new NameUpdater(); |
| |
| for( int i=0; i<structure.getEntryCount();i++) { |
| dataObject = dataObjectsList.get(i); |
| node[i] = dataObject.getNodeDelegate(); |
| weakNameUpdateListeners.put(dataObject,WeakListeners.propertyChange(nameUpdaterListener, dataObject)); |
| dataObject.addPropertyChangeListener(weakNameUpdateListeners.get(dataObject)); |
| } |
| |
| setActivatedNodes(node); |
| |
| updateName(); |
| } |
| |
| protected void dataObjectRemoved(PropertiesDataObject dataObject) { |
| PropertyChangeListener l = weakNameUpdateListeners.remove(dataObject); |
| if (l != null) { |
| dataObject.removePropertyChangeListener(l); |
| } |
| if (!dataObjectsList.remove(dataObject)) { |
| LOG.log(Level.WARNING, |
| "{0} not in the list", //NOI18N |
| dataObject.getName()); |
| } |
| } |
| |
| /** |
| * Called from PropertiesOpen when new DataObject added |
| * @param dataObject to add listener to |
| */ |
| protected void dataObjectAdded(PropertiesDataObject dataObject) { |
| if (weakNameUpdateListeners.get(dataObject)!=null) { |
| dataObject.removePropertyChangeListener(weakNameUpdateListeners.get(dataObject)); |
| } else { |
| weakNameUpdateListeners.put(dataObject, WeakListeners.propertyChange(nameUpdaterListener, dataObject)); |
| } |
| dataObject.addPropertyChangeListener(weakNameUpdateListeners.get(dataObject)); |
| if (dataObjectsList.indexOf(dataObject) == -1) { |
| dataObjectsList.add(dataObject); |
| } |
| if (EventQueue.isDispatchThread()) { |
| updateName(); |
| updateDisplayName(); |
| } |
| } |
| |
| /* Based on class DataNode.PropL. */ |
| final class NameUpdater implements PropertyChangeListener, |
| FileStatusListener, |
| Runnable { |
| |
| /** */ |
| private static final int NO_ACTION = 0; |
| /** */ |
| private static final int ACTION_UPDATE_NAME = 1; |
| /** */ |
| private static final int ACTION_UPDATE_DISPLAY_NAME = 2; |
| |
| /** weak version of this listener */ |
| private FileStatusListener weakL; |
| /** previous filesystem we were attached to */ |
| private FileSystem previous; |
| |
| /** */ |
| private final int action; |
| |
| /** |
| */ |
| NameUpdater() { |
| this(NO_ACTION); |
| updateStatusListener(); |
| } |
| |
| /** |
| */ |
| NameUpdater(int action) { |
| this.action = action; |
| } |
| |
| /** Updates listening on a status of filesystem. */ |
| private void updateStatusListener() { |
| if (previous != null) { |
| previous.removeFileStatusListener(weakL); |
| } |
| try { |
| PropertiesFileEntry entry = bundleStructure.getNthEntry(0); |
| if (entry == null) { |
| bundleStructure.updateEntries(); |
| entry = bundleStructure.getNthEntry(0); |
| if (entry == null) { |
| previous = null; |
| return; |
| } |
| } |
| previous = entry.getFile().getFileSystem(); |
| if (weakL == null) { |
| weakL = org.openide.filesystems.FileUtil |
| .weakFileStatusListener(this, previous); |
| } |
| previous.addFileStatusListener(weakL); |
| } catch (FileStateInvalidException ex) { |
| previous = null; |
| } |
| } |
| |
| /** |
| * Notifies listener about change in annotataion of a few files. |
| */ |
| @Override |
| public void annotationChanged(FileStatusEvent ev) { |
| if (!ev.isNameChange()) { |
| return; |
| } |
| |
| boolean thisChanged = false; |
| for (int i=0;i<bundleStructure.getEntryCount();i++) { |
| PropertiesFileEntry pfe = bundleStructure.getNthEntry(i); |
| if(pfe != null && ev.hasChanged(pfe.getFile())) { // #172691 |
| thisChanged = true; |
| break; |
| } |
| } |
| if (thisChanged) { |
| Mutex.EVENT.writeAccess( |
| new NameUpdater(ACTION_UPDATE_DISPLAY_NAME)); |
| } |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent e) { |
| //PENDING Add correct propDataObject |
| if (bundleStructure.getEntryCount() == 0) {return;} |
| if (e.getSource() instanceof PropertiesDataObject) { |
| PropertiesDataObject DO = (PropertiesDataObject) e.getSource(); |
| try { |
| if ((DO == Util.findPrimaryDataObject(DO)) && (!DO.isValid())) { |
| return; |
| } |
| } catch (DataObjectNotFoundException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| final String property = e.getPropertyName(); |
| if (property == null) { |
| return; |
| } |
| if (property.equals(DataObject.PROP_NAME)) { |
| Mutex.EVENT.writeAccess( |
| new NameUpdater(ACTION_UPDATE_NAME)); |
| } else if (property.equals(DataObject.PROP_PRIMARY_FILE)) { |
| updateStatusListener(); |
| Mutex.EVENT.writeAccess( |
| new NameUpdater(ACTION_UPDATE_NAME)); |
| } else if (property.equals(DataObject.PROP_COOKIE) |
| || property.equals(DataObject.PROP_FILES)) { |
| Mutex.EVENT.writeAccess( |
| new NameUpdater(ACTION_UPDATE_DISPLAY_NAME)); |
| } |
| } |
| |
| @Override |
| public void run() { |
| assert EventQueue.isDispatchThread(); |
| |
| if (action == ACTION_UPDATE_NAME) { |
| updateName(); |
| } else if (action == ACTION_UPDATE_DISPLAY_NAME) { |
| updateDisplayName(); |
| } else { |
| assert false; |
| } |
| } |
| |
| } |
| |
| /** |
| * Sets up action Find that it is activated/deactivated appropriately |
| * and so that it does what it should do. |
| */ |
| private void setupActions() { |
| JTable bundleTable = ((BundleEditPanel) getComponent(0)).getTable(); |
| FindAction findAction = SystemAction.get(FindAction.class); |
| Action action = FindPerformer.getFindPerformer(bundleTable); |
| getActionMap().put(findAction.getActionMapKey(), action); |
| } |
| |
| /** |
| */ |
| private void updateName() { |
| assert EventQueue.isDispatchThread(); |
| if (bundleStructure.getNthEntry(0)==null) |
| bundleStructure.updateEntries(); |
| final String name = bundleStructure.getNthEntry(0).getName(); |
| final String displayName = displayName(); |
| final String htmlDisplayName = htmlDisplayName(); |
| final String toolTip = messageToolTip(); |
| |
| Enumeration<CloneableTopComponent> en = getReference().getComponents(); |
| while (en.hasMoreElements()) { |
| CloneableTopComponent tc = en.nextElement(); |
| tc.setName(name); |
| tc.setDisplayName(displayName); |
| tc.setHtmlDisplayName(htmlDisplayName); |
| tc.setToolTipText(toolTip); |
| } |
| } |
| |
| /** |
| */ |
| private void updateDisplayName() { |
| assert EventQueue.isDispatchThread(); |
| |
| final String displayName = displayName(); |
| final String htmlDisplayName = htmlDisplayName(); |
| final String toolTip = messageToolTip(); |
| |
| Enumeration<CloneableTopComponent> en = getReference().getComponents(); |
| while (en.hasMoreElements()) { |
| CloneableTopComponent tc = en.nextElement(); |
| tc.setDisplayName(displayName); |
| tc.setHtmlDisplayName(htmlDisplayName); |
| tc.setToolTipText(toolTip); |
| } |
| } |
| |
| private boolean isModified() { |
| PropertiesFileEntry entry; |
| for (int i=0;i<bundleStructure.getEntryCount();i++) { |
| entry = bundleStructure.getNthEntry(i); |
| if(entry != null && entry.getDataObject().getLookup().lookup(SaveCookie.class) != null) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Builds a display name for this component. |
| * |
| * @return the created display name |
| * @see #htmlDisplayName |
| */ |
| private String displayName() { |
| //PENDING change to avoid call getNthEntry, in some cases it will throw an exception |
| if (bundleStructure.getNthEntry(0)==null) { |
| bundleStructure.updateEntries(); |
| } |
| String nameBase = bundleStructure.getNthEntry(0).getDataObject().getNodeDelegate().getDisplayName(); |
| return DataEditorSupport.annotateName(nameBase, false, isModified(), false); |
| } |
| |
| /** |
| * Builds a HTML display name for this component. |
| * |
| * @return the created display name |
| * @see #displayName() |
| */ |
| private String htmlDisplayName() { |
| if (bundleStructure.getNthEntry(0)==null) { |
| bundleStructure.updateEntries(); |
| } |
| final Node node = bundleStructure.getNthEntry(0).getDataObject().getNodeDelegate(); |
| String displayName = node.getHtmlDisplayName(); |
| if (displayName != null) { |
| if (!displayName.startsWith("<html>")) { //NOI18N |
| displayName = "<html>" + displayName; //NOI18N |
| } |
| } else { |
| displayName = node.getDisplayName(); |
| } |
| return DataEditorSupport.annotateName(displayName, true, isModified(), false); |
| } |
| |
| /** Gets string for tooltip. */ |
| private String messageToolTip() { |
| FileObject fo = bundleStructure.getNthEntry(0).getFile(); |
| return DataEditorSupport.toolTip(fo, isModified(), false); |
| } |
| |
| /** |
| * |
| * Overrides superclass method. When closing last view, also close the document. |
| * @return {@code true} if close succeeded |
| */ |
| @Override |
| protected boolean closeLast () { |
| if (!bundleStructure.getOpenSupport().canClose ()) { |
| // if we cannot close the last window |
| return false; |
| } |
| bundleStructure.getOpenSupport().closeDocuments(); |
| PropertyChangeListener l; |
| for (PropertiesDataObject dataObject:dataObjectsList) { |
| l = weakNameUpdateListeners.get(dataObject); |
| if (l!=null) { |
| dataObject.removePropertyChangeListener(l); |
| weakNameUpdateListeners.remove(dataObject); |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Is called from the superclass {@code clone} method to create new component from this one. |
| * This implementation only clones the object by calling super.clone method. |
| * @return the copy of this object |
| */ |
| @Override |
| protected CloneableTopComponent createClonedObject () { |
| return new PropertiesCloneableTopComponent(bundleStructure); |
| } |
| |
| /** Gets {@code Icon}. */ |
| @Override |
| public Image getIcon () { |
| return ImageUtilities.loadImage("org/netbeans/modules/properties/propertiesEditorMode.gif"); // NOI18N |
| } |
| |
| /** Gets help context. */ |
| @Override |
| public HelpCtx getHelpCtx () { |
| return new HelpCtx(Util.HELP_ID_MODIFYING); |
| } |
| |
| @Override |
| protected String preferredID() { |
| return getName(); |
| } |
| |
| @Override |
| public int getPersistenceType() { |
| return PERSISTENCE_ONLY_OPENED; |
| } |
| |
| /** |
| * Gets compound UndoRedo manager from all UndozRedo managers from all editor supports. |
| */ |
| @Override |
| public UndoRedo getUndoRedo () { |
| return bundleStructure.getOpenSupport().getUndoRedo(); |
| } |
| |
| /** Inits the subcomponents. Sets layout for this top component and adds {@code BundleEditPanel} to it. |
| * @see BundleEditPanel */ |
| private void initComponents() { |
| GridBagLayout gridbag = new GridBagLayout(); |
| setLayout(gridbag); |
| |
| GridBagConstraints c = new GridBagConstraints(); |
| c.fill = GridBagConstraints.BOTH; |
| c.weightx = 1.0; |
| c.weighty = 1.0; |
| c.gridwidth = GridBagConstraints.REMAINDER; |
| JPanel panel = new BundleEditPanel(bundleStructure, new PropertiesTableModel(bundleStructure)); |
| gridbag.setConstraints(panel, c); |
| add(panel); |
| } |
| |
| /** This component should be discarded if the associated environment |
| * is not valid. |
| */ |
| private boolean discard () { |
| return bundleStructure == null; |
| } |
| |
| |
| /** |
| * Serialize this top component. |
| * Subclasses wishing to store state must call the super method, then write to the stream. |
| * @param out the stream to serialize to |
| */ |
| @Override |
| public void writeExternal (ObjectOutput out) throws IOException { |
| super.writeExternal(out); |
| out.writeObject(bundleStructure.getNthEntry(0).getDataObject()); |
| } |
| |
| /** |
| * Deserialize this top component. |
| * Subclasses wishing to store state must call the super method, then read from the stream. |
| * @param in the stream to deserialize from |
| */ |
| @Override |
| public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException { |
| super.readExternal(in); |
| |
| propDataObject = (PropertiesDataObject)in.readObject(); |
| bundleStructure = (MultiBundleStructure) propDataObject.getBundleStructure(); |
| dataObjectsList = new ArrayList<PropertiesDataObject>(); |
| for (int i=0;i<bundleStructure.getEntryCount();i++) { |
| dataObjectsList.add((PropertiesDataObject) bundleStructure.getNthEntry(i).getDataObject()); |
| } |
| weakNameUpdateListeners = new HashMap<PropertiesDataObject, PropertyChangeListener>(); |
| initialize(); |
| } |
| } // End of nested class PropertiesCloneableTopComponent. |
| |
| /** |
| * {@code UndoRedo} manager for {@code PropertiesOpen} support. It contains weak references |
| * to all UndoRedo managers from all PropertiesEditor supports (for each entry of dataobject one manager). |
| * It uses it's "timeStamp" methods to find out which one of these managers comes to play. |
| */ |
| private static class CompoundUndoRedoManager implements UndoRedo { |
| |
| /** Set of weak references to all "underlying" editor support undoredo managers. */ |
| private WeakSet<Manager> managers = new WeakSet<Manager>(5); |
| |
| // Constructor |
| |
| /** Collects all UndoRedo managers from all editor support of all entries. */ |
| @Deprecated |
| public CompoundUndoRedoManager(PropertiesDataObject obj) { |
| init(obj); |
| } |
| public CompoundUndoRedoManager(BundleStructure structure) { |
| init(structure); |
| } |
| |
| /** Initialize set of managers. */ |
| @Deprecated |
| private void init(PropertiesDataObject obj) { |
| BundleStructure structure = obj.getBundleStructure(); |
| PropertiesEditorSupport editorSupport = null; |
| for(int i=0; i< structure.getEntryCount(); i++) { |
| editorSupport = structure.getNthEntry(i).getPropertiesEditor(); |
| if (editorSupport != null) { |
| managers.add(editorSupport.getUndoRedoManager()); |
| } |
| } |
| } |
| |
| private void init(BundleStructure structure) { |
| PropertiesFileEntry entry; |
| for(int i=0; i< structure.getEntryCount(); i++) { |
| entry = structure.getNthEntry(i); |
| if (entry != null) |
| managers.add(entry.getPropertiesEditor().getUndoRedoManager()); |
| } |
| } |
| |
| /** Resets the managers. Used when data object has changed. */ |
| @Deprecated |
| public synchronized void reset(PropertiesDataObject obj) { |
| managers.clear(); |
| init(obj); |
| } |
| |
| public synchronized void reset(BundleStructure structure) { |
| managers.clear(); |
| init(structure); |
| } |
| |
| /** Gets manager which undo edit comes to play.*/ |
| private UndoRedo getNextUndo() { |
| UndoRedo chosenManager = null; |
| long time = 0L; // time to compare with |
| long timeManager; // time of next undo of actual manager |
| |
| for (Iterator<Manager> it = managers.iterator(); it.hasNext(); ) { |
| PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next(); |
| timeManager = manager.getTimeStampOfEditToBeUndone(); |
| if(timeManager > time) { |
| time = timeManager; |
| chosenManager = manager; |
| } |
| } |
| return chosenManager; |
| } |
| |
| /** Gets manager which redo edit comes to play.*/ |
| private UndoRedo getNextRedo() { |
| UndoRedo chosenManager = null; |
| long time = 0L; // time to compare with |
| long timeManager; // time of next redo of actual manager |
| |
| for (Iterator<Manager> it = managers.iterator(); it.hasNext(); ) { |
| PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next(); |
| timeManager = manager.getTimeStampOfEditToBeRedone(); |
| if(timeManager > time) { |
| time = timeManager; |
| chosenManager = manager; |
| } |
| } |
| return chosenManager; |
| } |
| |
| /** Implements {@code UndoRedo}. Test whether at least one of managers can Undo. |
| * @return {@code true} if undo is allowed |
| */ |
| @Override |
| public synchronized boolean canUndo () { |
| for (Manager manager : managers) { |
| if (manager.canUndo()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Implements {@code UndoRedo}. Test whether at least one of managers can Redo. |
| * @return {@code true} if redo is allowed |
| */ |
| @Override |
| public synchronized boolean canRedo () { |
| for (Manager manager : managers) { |
| if (manager.canRedo()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** Implements {@code UndoRedo}. Undo an edit. It finds a manager which next undo edit has the highest |
| * time stamp and makes undo on it. |
| * @exception CannotUndoException if it fails |
| */ |
| @Override |
| public synchronized void undo () throws CannotUndoException { |
| PropertiesEditorSupport.UndoRedoStampFlagManager chosenManager = (PropertiesEditorSupport.UndoRedoStampFlagManager)getNextUndo(); |
| |
| if (chosenManager == null) { |
| throw new CannotUndoException(); |
| } else { |
| Object atomicFlag = chosenManager.getAtomicFlagOfEditToBeUndone(); |
| if (atomicFlag == null) {// not linked with other edits as one atomic action |
| chosenManager.undo(); |
| } else { // atomic undo compound from more edits in underlying managers |
| boolean undone; |
| do { // the atomic action can consists from more undo edits from same manager |
| undone = false; |
| for (Iterator<Manager> it = managers.iterator(); it.hasNext(); ) { |
| PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next(); |
| if(atomicFlag.equals(manager.getAtomicFlagOfEditToBeUndone())) { |
| manager.undo(); |
| undone = true; |
| } |
| } |
| } while(undone); |
| } |
| } |
| } |
| |
| /** Implements {@code UndoRedo}. Redo a previously undone edit. It finds a manager which next undo edit has the highest |
| * time stamp and makes undo on it. |
| * @exception CannotRedoException if it fails |
| */ |
| @Override |
| public synchronized void redo () throws CannotRedoException { |
| PropertiesEditorSupport.UndoRedoStampFlagManager chosenManager = (PropertiesEditorSupport.UndoRedoStampFlagManager)getNextRedo(); |
| |
| if (chosenManager == null) { |
| throw new CannotRedoException(); |
| } else { |
| Object atomicFlag = chosenManager.getAtomicFlagOfEditToBeRedone(); |
| if (atomicFlag == null) {// not linked with other edits as one atomic action |
| chosenManager.redo(); |
| } else { // atomic redo compound from more edits in underlying managers |
| boolean redone; |
| do { // the atomic action can consists from more redo edits from same manager |
| redone = false; |
| for (Iterator<Manager> it = managers.iterator(); it.hasNext(); ) { |
| PropertiesEditorSupport.UndoRedoStampFlagManager manager = (PropertiesEditorSupport.UndoRedoStampFlagManager)it.next(); |
| if(atomicFlag.equals(manager.getAtomicFlagOfEditToBeRedone())) { |
| manager.redo(); |
| redone = true; |
| } |
| } |
| } while(redone); |
| } |
| } |
| } |
| |
| /** Implements {@code UndoRedo}. Empty implementation. Does nothing. |
| * @param l the listener to add |
| */ |
| @Override |
| public void addChangeListener (ChangeListener l) { |
| // PENDING up to now listen on separate managers |
| } |
| |
| /** Implements {@code UndoRedo}. Empty implementation. Does nothing. |
| * @param l the listener to remove |
| * @see #addChangeListener |
| */ |
| @Override |
| public void removeChangeListener (ChangeListener l) { |
| // PENDING |
| } |
| |
| /** Implements {@code UndoRedo}. Get a human-presentable name describing the |
| * undo operation. |
| * @return the name |
| */ |
| @Override |
| public synchronized String getUndoPresentationName () { |
| UndoRedo chosenManager = getNextUndo(); |
| |
| if (chosenManager == null) { |
| return "Undo"; // NOI18N // AbstractUndoableEdit.UndoName is not accessible |
| } else { |
| return chosenManager.getUndoPresentationName(); |
| } |
| } |
| |
| /** Implements {@code UndoRedo}. Get a human-presentable name describing the |
| * redo operation. |
| * @return the name |
| */ |
| @Override |
| public synchronized String getRedoPresentationName () { |
| UndoRedo chosenManager = getNextRedo(); |
| if (chosenManager == null) { |
| return "Redo"; // NOI18N // AbstractUndoableEdit.RedoName is not accessible |
| } else { |
| return chosenManager.getRedoPresentationName(); |
| } |
| } |
| |
| } // End of nested class CompoundUndoRedoManager. |
| |
| } |