/*
* 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.windows;
import java.beans.*;
import org.openide.awt.StatusDisplayer;
import org.openide.util.*;
/** Simple support for an openable objects.
* Can be used either as an {@link org.openide.cookies.OpenCookie},
* {@link org.openide.cookies.ViewCookie}, or {@link org.openide.cookies.CloseCookie},
* depending on which cookies the subclass implements.
*
* @author Jaroslav Tulach
*/
public abstract class CloneableOpenSupport extends Object {
/** the environment that provides connection to outside world */
protected Env env;
/** All opened editors on this file.
* <em>Warning:</em> Treat this field like <code>final</code>.
* Internally the instance is used as <code>WeakListener</code>
* on <code>Env</code> validity changes.
* Changing the instance in subclasses would lead to breaking
* of that listening, thus to errorneous behaviour. */
protected CloneableTopComponent.Ref allEditors;
private static java.awt.Container container;
/** New support for a given environment.
* @param env environment to take all date from/to
*/
public CloneableOpenSupport(Env env) {
this.env = env;
Listener l = new Listener (env);
this.allEditors = l;
// attach property change listener to be informed about loosing validity
env.addPropertyChangeListener (WeakListener.propertyChange (
l, env
));
// attach vetoable change listener to be cancel loosing validity when modified
env.addVetoableChangeListener (WeakListener.vetoableChange (
l, env
));
}
/** Opens and focuses or just focuses already opened
* <code>CloneableTopComponent</code>.
* <p><b>Note: The actual processing of this method is scheduled into AWT thread
* in case it is called from other than the AWT thread.</b></p>
* @see org.openide.cookies.OpenCookie#open
* @see #openCloneableTopComponent
*/
public void open () {
//Bugfix #10688 open() is now run in AWT thread
Mutex.EVENT.writeAccess (new Runnable () {
public void run () {
CloneableTopComponent editor = openCloneableTopComponent();
editor.requestFocus();
}
});
}
/** Focuses existing component to view, or if none exists creates new.
* The default implementation simply calls {@link #open}.
* @see org.openide.cookies.ViewCookie#view
*/
public void view () {
open ();
}
/** Focuses existing component to view, or if none exists creates new.
* The default implementation simply calls {@link #open}.
* @see org.openide.cookies.EditCookie#edit
*/
public void edit () {
open ();
}
/** Closes all components.
* @return <code>true</code> if every component is successfully closed or <code>false</code> if the user cancelled the request
* @see org.openide.cookies.CloseCookie#close
*/
public boolean close () {
return close (true);
}
/** Closes all opened windows.
* @param ask true if we should ask user
* @return true if sucesfully closed
*/
protected boolean close (final boolean ask) {
if (allEditors.isEmpty ()) {
return true;
}
//Bugfix #10688 close() is now run in AWT thread
//also bugfix of 10714 - whole close (boolean) is run in AWT thread
Boolean ret = (Boolean) Mutex.EVENT.writeAccess (new Mutex.Action () {
public Object run () {
//synchronized (allEditors) {
synchronized (getLock()) {
// user canceled the action
if (ask && !canClose ()) {
return Boolean.FALSE;
}
java.util.Enumeration en = allEditors.getComponents ();
while (en.hasMoreElements ()) {
TopComponent c = (TopComponent)en.nextElement ();
if (!c.close ()) {
return Boolean.FALSE;
}
}
}
return Boolean.TRUE;
}
});
return ret.booleanValue();
}
/** Should test whether all data is saved, and if not, prompt the user
* to save.
* The default implementation returns <code>true</code>.
*
* @return <code>true</code> if everything can be closed
*/
protected boolean canClose () {
return true;
}
/** Simply open for an editor. */
protected final CloneableTopComponent openCloneableTopComponent() {
//synchronized (allEditors) {
synchronized (getLock()) {
CloneableTopComponent ret = allEditors.getArbitraryComponent ();
if(ret != null) {
ret.open();
return ret;
} else {
// no opened editor
String msg = messageOpening ();
if (msg != null) {
StatusDisplayer.getDefault().setStatusText(msg);
}
CloneableTopComponent editor = createCloneableTopComponent ();
editor.setReference (allEditors);
editor.open();
msg = messageOpened ();
if (msg == null) {
msg = ""; // NOI18N
}
StatusDisplayer.getDefault().setStatusText(msg);
return editor;
}
}
}
/** Creates lock object used in close and openCloneableTopComponent. */
private Object getLock() {
if (container == null) {
container = new java.awt.Container();
}
return container.getTreeLock();
}
/** A method to create a new component. Must be overridden in subclasses.
* @return the cloneable top component for this support
*/
protected abstract CloneableTopComponent createCloneableTopComponent ();
/** Message to display when an object is being opened.
* @return the message or null if nothing should be displayed
*/
protected abstract String messageOpening ();
/** Message to display when an object has been opened.
* @return the message or null if nothing should be displayed
*/
protected abstract String messageOpened ();
/** Abstract interface that is used by CloneableOpenSupport to
* talk to outside world.
*/
public static interface Env extends java.io.Serializable {
/** that is fired when the objects wants to mark itself as
* invalid, so all components should be closed.
*/
public static final String PROP_VALID = org.openide.loaders.DataObject.PROP_VALID;
/** that is fired when the objects wants to mark itself modified
* or not modified.
*/
public static final String PROP_MODIFIED = org.openide.loaders.DataObject.PROP_MODIFIED;
/** Adds property listener.
*/
public void addPropertyChangeListener (PropertyChangeListener l);
/** Removes property listener.
*/
public void removePropertyChangeListener (PropertyChangeListener l);
/** Adds veto listener.
*/
public void addVetoableChangeListener (VetoableChangeListener l);
/** Removes veto listener.
*/
public void removeVetoableChangeListener (VetoableChangeListener l);
/** 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
*/
public boolean isValid ();
/** Test whether the object is modified or not.
* @return true if the object is modified
*/
public boolean isModified ();
/** 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
*/
public void markModified () throws java.io.IOException;
/** Reverse method that can be called to make the environment
* unmodified.
*/
public void unmarkModified ();
/** Method that allows environment to find its
* cloneable open support.
*/
public CloneableOpenSupport findCloneableOpenSupport ();
}
/** Property change & veto listener. To react to dispose/delete of
* the data object.
*/
private static final class Listener extends CloneableTopComponent.Ref
implements PropertyChangeListener, VetoableChangeListener, Runnable {
/** generated Serialized Version UID */
static final long serialVersionUID = -1934890789745432531L;
/** environment to use as connection to outside world */
private Env env;
/** Constructor.
*/
public Listener (Env env) {
this.env = env;
}
/** Getter for the associated CloneableOpenSupport
* @return the support or null if none was found
*/
private CloneableOpenSupport support () {
return env.findCloneableOpenSupport ();
}
public void propertyChange (PropertyChangeEvent ev) {
if (Env.PROP_VALID.equals (ev.getPropertyName ())) {
// do not check it if old value is not true
if (Boolean.FALSE.equals (ev.getOldValue ())) return;
Mutex.EVENT.readAccess (this);
}
}
/** Closes the support in AWT thread.
*/
public void run () {
// loosing validity
CloneableOpenSupport os = support ();
if (os != null) {
// mark the object as not being modified, so nobody
// will ask for save
env.unmarkModified ();
os.close (false);
}
}
/** Forbids setValid (false) on data object when there is an
* opened editor.
*
* @param ev PropertyChangeEvent
*/
public void vetoableChange (PropertyChangeEvent ev)
throws PropertyVetoException {
if (Env.PROP_VALID.equals (ev.getPropertyName ())) {
// do not check it if old value is not true
if (Boolean.FALSE.equals (ev.getOldValue ())) return;
if (env.isModified ()) {
// if the object is modified
CloneableOpenSupport os = support ();
if (os != null && !os.canClose ()) {
// is modified and has not been sucessfully closed
throw new PropertyVetoException (
// [PENDING] this is not a very good detail message!
"", ev // NOI18N
);
}
}
}
}
/** Resolvable to connect to the right data object. This
* method is used for connectiong CloneableTopComponents via
* their CloneableTopComponent.Ref
*/
public Object readResolve () {
CloneableOpenSupport os = support ();
if (os == null) {
// problem! no replace!?
return this;
}
// use the editor support's CloneableTopComponent.Ref
return os.allEditors;
}
}
}