001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.openide.windows;
042:
043: import org.openide.awt.StatusDisplayer;
044: import org.openide.util.*;
045:
046: import java.beans.*;
047:
048: /** Simple support for an openable objects.
049: * Can be used either as an {@link org.openide.cookies.OpenCookie},
050: * {@link org.openide.cookies.ViewCookie}, or {@link org.openide.cookies.CloseCookie},
051: * depending on which cookies the subclass implements.
052: *
053: * @author Jaroslav Tulach
054: */
055: public abstract class CloneableOpenSupport extends Object {
056: private static java.awt.Container container;
057:
058: /** the environment that provides connection to outside world */
059: protected Env env;
060:
061: /** All opened editors on this file.
062: * <em>Warning:</em> Treat this field like <code>final</code>.
063: * Internally the instance is used as <code>WeakListener</code>
064: * on <code>Env</code> validity changes.
065: * Changing the instance in subclasses would lead to breaking
066: * of that listening, thus to errorneous behaviour. */
067: protected CloneableTopComponent.Ref allEditors;
068:
069: /** New support for a given environment.
070: * @param env environment to take all date from/to
071: */
072: public CloneableOpenSupport(Env env) {
073: this .env = env;
074:
075: Listener l = new Listener(env);
076: this .allEditors = l;
077:
078: // attach property change listener to be informed about loosing validity
079: env.addPropertyChangeListener(org.openide.util.WeakListeners
080: .propertyChange(l, env));
081:
082: // attach vetoable change listener to be cancel loosing validity when modified
083: env.addVetoableChangeListener(org.openide.util.WeakListeners
084: .vetoableChange(l, env));
085: }
086:
087: /** Opens and focuses or just focuses already opened
088: * <code>CloneableTopComponent</code>.
089: * <p><b>Note: The actual processing of this method is scheduled into AWT thread
090: * in case it is called from other than the AWT thread.</b></p>
091: * @see org.openide.cookies.OpenCookie#open
092: * @see #openCloneableTopComponent
093: */
094: public void open() {
095: //Bugfix #10688 open() is now run in AWT thread
096: Mutex.EVENT.writeAccess(new Runnable() {
097: public void run() {
098: CloneableTopComponent editor = openCloneableTopComponent();
099: editor.requestActive();
100: }
101: });
102: }
103:
104: /** Focuses existing component to view, or if none exists creates new.
105: * The default implementation simply calls {@link #open}.
106: * @see org.openide.cookies.ViewCookie#view
107: */
108: public void view() {
109: open();
110: }
111:
112: /** Focuses existing component to view, or if none exists creates new.
113: * The default implementation simply calls {@link #open}.
114: * @see org.openide.cookies.EditCookie#edit
115: */
116: public void edit() {
117: open();
118: }
119:
120: /** Closes all components.
121: * @return <code>true</code> if every component is successfully closed or <code>false</code> if the user cancelled the request
122: * @see org.openide.cookies.CloseCookie#close
123: */
124: public boolean close() {
125: return close(true);
126: }
127:
128: /** Closes all opened windows.
129: * @param ask true if we should ask user
130: * @return true if sucesfully closed
131: */
132: protected boolean close(final boolean ask) {
133: if (allEditors.isEmpty()) {
134: return true;
135: }
136:
137: //Bugfix #10688 close() is now run in AWT thread
138: //also bugfix of 10714 - whole close (boolean) is run in AWT thread
139: Boolean ret = Mutex.EVENT
140: .writeAccess(new Mutex.Action<Boolean>() {
141: public Boolean run() {
142: //synchronized (allEditors) {
143: synchronized (getLock()) {
144: // user canceled the action
145: if (ask && !canClose()) {
146: return Boolean.FALSE;
147: }
148:
149: java.util.Enumeration en = allEditors
150: .getComponents();
151:
152: while (en.hasMoreElements()) {
153: TopComponent c = (TopComponent) en
154: .nextElement();
155:
156: if (!c.close()) {
157: return Boolean.FALSE;
158: }
159: }
160: }
161:
162: return Boolean.TRUE;
163: }
164: });
165:
166: return ret.booleanValue();
167: }
168:
169: /** Should test whether all data is saved, and if not, prompt the user
170: * to save.
171: * The default implementation returns <code>true</code>.
172: *
173: * @return <code>true</code> if everything can be closed
174: */
175: protected boolean canClose() {
176: return true;
177: }
178:
179: /** Simply open for an editor. */
180: protected final CloneableTopComponent openCloneableTopComponent() {
181: //synchronized (allEditors) {
182: synchronized (getLock()) {
183: CloneableTopComponent ret = allEditors
184: .getArbitraryComponent();
185:
186: if (ret != null) {
187: ret.open();
188:
189: return ret;
190: } else {
191: // no opened editor
192: String msg = messageOpening();
193:
194: if (msg != null) {
195: StatusDisplayer.getDefault().setStatusText(msg);
196: }
197:
198: CloneableTopComponent editor = createCloneableTopComponent();
199: editor.setReference(allEditors);
200: editor.open();
201:
202: msg = messageOpened();
203:
204: if (msg == null) {
205: msg = ""; // NOI18N
206: }
207:
208: StatusDisplayer.getDefault().setStatusText(msg);
209:
210: return editor;
211: }
212: }
213: }
214:
215: /** Creates lock object used in close and openCloneableTopComponent. */
216: private Object getLock() {
217: if (container == null) {
218: container = new java.awt.Container();
219: }
220:
221: return container.getTreeLock();
222: }
223:
224: /** A method to create a new component. Must be overridden in subclasses.
225: * @return the cloneable top component for this support
226: */
227: protected abstract CloneableTopComponent createCloneableTopComponent();
228:
229: /** Message to display when an object is being opened.
230: * @return the message or null if nothing should be displayed
231: */
232: protected abstract String messageOpening();
233:
234: /** Message to display when an object has been opened.
235: * @return the message or null if nothing should be displayed
236: */
237: protected abstract String messageOpened();
238:
239: /** Abstract interface that is used by CloneableOpenSupport to
240: * talk to outside world.
241: */
242: public static interface Env extends java.io.Serializable {
243: /** that is fired when the objects wants to mark itself as
244: * invalid, so all components should be closed.
245: */
246: public static final String PROP_VALID = "valid"; // NOI18N
247:
248: /** that is fired when the objects wants to mark itself modified
249: * or not modified.
250: */
251: public static final String PROP_MODIFIED = "modified"; // NOI18N
252:
253: /** Adds property listener.
254: */
255: public void addPropertyChangeListener(PropertyChangeListener l);
256:
257: /** Removes property listener.
258: */
259: public void removePropertyChangeListener(
260: PropertyChangeListener l);
261:
262: /** Adds veto listener.
263: */
264: public void addVetoableChangeListener(VetoableChangeListener l);
265:
266: /** Removes veto listener.
267: */
268: public void removeVetoableChangeListener(
269: VetoableChangeListener l);
270:
271: /** Test whether the support is in valid state or not.
272: * It could be invalid after deserialization when the object it
273: * referenced to does not exist anymore.
274: *
275: * @return true or false depending on its state
276: */
277: public boolean isValid();
278:
279: /** Test whether the object is modified or not.
280: * @return true if the object is modified
281: */
282: public boolean isModified();
283:
284: /** Support for marking the environement modified.
285: * @exception IOException if the environment cannot be marked modified
286: * (for example when the file is readonly), when such exception
287: * is the support should discard all previous changes
288: */
289: public void markModified() throws java.io.IOException;
290:
291: /** Reverse method that can be called to make the environment
292: * unmodified.
293: */
294: public void unmarkModified();
295:
296: /** Method that allows environment to find its
297: * cloneable open support.
298: */
299: public CloneableOpenSupport findCloneableOpenSupport();
300: }
301:
302: /** Property change & veto listener. To react to dispose/delete of
303: * the data object.
304: */
305: private static final class Listener extends
306: CloneableTopComponent.Ref implements
307: PropertyChangeListener, VetoableChangeListener, Runnable {
308: /** generated Serialized Version UID */
309: static final long serialVersionUID = -1934890789745432531L;
310:
311: /** environment to use as connection to outside world */
312: private Env env;
313:
314: /** Constructor.
315: */
316: public Listener(Env env) {
317: this .env = env;
318: }
319:
320: /** Getter for the associated CloneableOpenSupport
321: * @return the support or null if none was found
322: */
323: private CloneableOpenSupport support() {
324: return env.findCloneableOpenSupport();
325: }
326:
327: public void propertyChange(PropertyChangeEvent ev) {
328: if (Env.PROP_VALID.equals(ev.getPropertyName())) {
329: // do not check it if old value is not true
330: if (Boolean.FALSE.equals(ev.getOldValue())) {
331: return;
332: }
333:
334: Mutex.EVENT.readAccess(this );
335: }
336: }
337:
338: /** Closes the support in AWT thread.
339: */
340: public void run() {
341: // loosing validity
342: CloneableOpenSupport os = support();
343:
344: if (os != null) {
345: // mark the object as not being modified, so nobody
346: // will ask for save
347: env.unmarkModified();
348:
349: os.close(false);
350: }
351: }
352:
353: /** Forbids setValid (false) on data object when there is an
354: * opened editor.
355: *
356: * @param ev PropertyChangeEvent
357: */
358: public void vetoableChange(PropertyChangeEvent ev)
359: throws PropertyVetoException {
360: if (Env.PROP_VALID.equals(ev.getPropertyName())) {
361: // do not check it if old value is not true
362: if (Boolean.FALSE.equals(ev.getOldValue())) {
363: return;
364: }
365:
366: if (env.isModified()) {
367: // if the object is modified
368: CloneableOpenSupport os = support();
369:
370: if ((os != null) && !os.canClose()) {
371: // is modified and has not been sucessfully closed
372: throw new PropertyVetoException(
373: // [PENDING] this is not a very good detail message!
374: "", ev // NOI18N
375: );
376: }
377: }
378: }
379: }
380:
381: /** Resolvable to connect to the right data object. This
382: * method is used for connectiong CloneableTopComponents via
383: * their CloneableTopComponent.Ref
384: */
385: public Object readResolve() {
386: CloneableOpenSupport os = support();
387:
388: if (os == null) {
389: // problem! no replace!?
390: return this ;
391: }
392:
393: // use the editor support's CloneableTopComponent.Ref
394: return os.allEditors;
395: }
396: }
397: }
|