org.openide.windows.CloneableOpenSupport Maven / Gradle / Ivy
/*
* 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.openide.windows;
import org.openide.awt.StatusDisplayer;
import org.openide.util.*;
import java.beans.*;
/** 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 {
private static java.awt.Container container;
/** the environment that provides connection to outside world */
protected Env env;
/** All opened editors on this file.
* Warning: Treat this field like final
.
* Internally the instance is used as WeakListener
* on Env
validity changes.
* Changing the instance in subclasses would lead to breaking
* of that listening, thus to errorneous behaviour. */
protected CloneableTopComponent.Ref allEditors;
/** 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);
this.allEditors = l;
// attach property change listener to be informed about loosing validity
env.addPropertyChangeListener(org.openide.util.WeakListeners.propertyChange(l, env));
// attach vetoable change listener to be cancel loosing validity when modified
env.addVetoableChangeListener(org.openide.util.WeakListeners.vetoableChange(l, env));
}
/** Opens and focuses or just focuses already opened
* CloneableTopComponent
.
* Note: The actual processing of this method is scheduled into AWT thread
* in case it is called from other than the AWT thread.
* @see org.openide.cookies.OpenCookie#open
* @see #openCloneableTopComponent
*/
public void open() {
CloneableOpenSupport redirect = CloneableOpenSupportRedirector.findRedirect(this);
if (redirect != null) {
redirect.open();
this.afterRedirectImpl(redirect);
return;
}
//Bugfix #10688 open() is now run in AWT thread
Mutex.EVENT.writeAccess(
new Runnable() {
public void run() {
CloneableTopComponent editor = openCloneableTopComponent();
editor.requestActive();
}
}
);
}
/** 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 true
if every component is successfully closed or false
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 = Mutex.EVENT.writeAccess(
new Mutex.Action() {
public Boolean 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 true
.
*
* @return true
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 msgOpening = messageOpening();
if (msgOpening != null) {
StatusDisplayer.getDefault().setStatusText(msgOpening);
}
CloneableTopComponent editor = createCloneableTopComponent();
editor.setReference(allEditors);
editor.open();
String msgOpened = messageOpened();
if (msgOpened == null && msgOpening != null) {
// only reset when opening message was displayed
msgOpened = ""; // NOI18N
}
if (msgOpened != null) {
StatusDisplayer.getDefault().setStatusText(msgOpened);
}
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();
private void afterRedirectImpl(CloneableOpenSupport redirectedTo) {
// there is a common patern in user code:
// CloneableEditorSupport ces = ...;
// ces.edit();
// JEditorPane[] panes = ces.getOpenedPanes();
// if (panes != null) panes[0].setPosition(offset);
// in case when redirection has happened during edit() call
// 'ces' instance returns null for getOpenedPanes
// but we want panes to be available for 'ces' instance after redirection;
// remember editors from redirected instance to have correct opened panes
// for this instance as well
this.allEditors = redirectedTo.allEditors;
// this.env = redirect.env;
afterRedirect(redirectedTo);
}
/**
* Called to notify that another redirected CloneableOpenSupport was opened instead of this one.
* @param redirectedTo redirected instance which was opened instead of this one
* @since 6.70
*/
protected void afterRedirect(CloneableOpenSupport redirectedTo) {
}
/** 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 = "valid"; // NOI18N
/** that is fired when the objects wants to mark itself modified
* or not modified.
*/
public static final String PROP_MODIFIED = "modified"; // NOI18N
/** 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 java.io.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 final Env env;
// rerefence to prevent GC of COS created in readResolve() by call to support()
private final transient CloneableOpenSupport refCOS;
/** Constructor.
*/
public Listener(Env env, CloneableOpenSupport cos) {
this.env = env;
this.refCOS = cos;
}
/** 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 successfully 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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy