Index: core/output2/src/org/netbeans/core/output2/Controller.java =================================================================== RCS file: /cvs/core/output2/src/org/netbeans/core/output2/Controller.java,v retrieving revision 1.47 diff -u -r1.47 Controller.java --- core/output2/src/org/netbeans/core/output2/Controller.java 1 Jul 2006 08:51:57 -0000 1.47 +++ core/output2/src/org/netbeans/core/output2/Controller.java 14 Aug 2006 06:55:12 -0000 @@ -82,6 +82,16 @@ }); } + public static void ensureViewIn (final OutputWindow window, final NbIO io, + final boolean reuse) { + Mutex.EVENT.readAccess(new Runnable() { + public void run() { + IOEvent evt = new IOEvent (io, window, IOEvent.CMD_CREATE, reuse); + NbIO.post(evt); + } + }); + } + private static final int ACTION_COPY = 0; private static final int ACTION_WRAP = 1; private static final int ACTION_SAVEAS = 2; Index: core/output2/src/org/netbeans/core/output2/IOComponentFactory.java =================================================================== RCS file: core/output2/src/org/netbeans/core/output2/IOComponentFactory.java diff -N core/output2/src/org/netbeans/core/output2/IOComponentFactory.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/output2/src/org/netbeans/core/output2/IOComponentFactory.java 14 Aug 2006 06:55:12 -0000 @@ -0,0 +1,60 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.netbeans.core.output2; + +import org.openide.util.Lookup; + +/** + * Factory for output window components with associated instances of + * IOProvider, for clients which want to embed the output window component + * inside their own component. + * + * @author Tim Boudreau + */ +public abstract class IOComponentFactory { + + //XXX this should be in an API package, but to avoid creating that package + //in CVS just to generate a diff, it's here for now. Just move it to + //e.g. org.netbeans.api.output or similar when ready. + + protected IOComponentFactory() { + } + + /** + * Get an object which can provide its own output component + * and InputOutput instances that talk to it. + */ + public static IOComponentFactory getDefault() { + IOComponentFactory result = (IOComponentFactory) Lookup.getDefault().lookup ( + IOComponentFactory .class); + if (result == null) { + result = new DefaultImpl(); + } + return result; + } + + /** + * Create a new IOComponentManager - call this method when a new output + * component is needed with its associated IOProvider for getting input + * and output streams that read from/write to the output component. + */ + public abstract IOComponentManager newComponentManager(); + + private static final class DefaultImpl extends IOComponentFactory { + public IOComponentManager newComponentManager() { + return new NbIOProvider (new OutputWindow()).getController(); + } + } +} Index: core/output2/src/org/netbeans/core/output2/IOComponentManager.java =================================================================== RCS file: core/output2/src/org/netbeans/core/output2/IOComponentManager.java diff -N core/output2/src/org/netbeans/core/output2/IOComponentManager.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/output2/src/org/netbeans/core/output2/IOComponentManager.java 14 Aug 2006 06:55:12 -0000 @@ -0,0 +1,100 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.netbeans.core.output2; + +import java.awt.Component; +import javax.swing.Action; +import org.openide.windows.IOProvider; +import org.openide.windows.InputOutput; + +/** + * Object which is associates a single output component (which can contain + * multiple tabs with different output - really it just provides another + * instance of the component in the output window) with an IOProvider + * instance - which is a factory for InputOutput objects whose standard out + * and standard error streams will write to text components inside the + * output component. Typical usage is for cases such as the JUnit results + * window, where it is desirable to have output viewable alongside test results, + * and so an output component is needed which does not live in the regular + * output window. + *

+ * Typical usage: + *

+ * class SomeContainer extends javax.swing.JPanel; //or whatever
+ * IOComponentManager mgr;
+ * public SomeContainer() {
+ *     mgr = IOComponentFactory.getDefault().newComponentManager();
+ *     Component outputComp = mgr.getOutputComponent();
+ *     add (outputComp, <some layout constraint> );
+ * }   
+ * 
+ * public void writeSomeOutput (String tabName, String s) {
+ *     mgr.getInputOutput (tabName, true).getOut().println (s);
+ * }
+ * 
+ * There are two major reasons to use this interface as opposed to simply + * using a JEditorPane or similar: + *
    + *
  1. The output window is designed to scale to extremely large amounts of + * output without performance problems; standard Swing text UIs are not.
  2. + *
  3. Since it uses input and output streams, the output window API is + * naturally suited to working with external processes which already use + * streams to communicate.
  4. + *
+ * + * @author Tim Boudreau + */ +public abstract class IOComponentManager { + //XXX this should be in an API package, but to avoid creating that package + //in CVS just to generate a diff, it's here for now. Just move it to + //e.g. org.netbeans.api.output or similar when ready. + + protected IOComponentManager() { + } + /** + * Get the output component where this object's IOProvider will create + * output tabs and write output. This method will always return the same + * output window instance as long as the previously returned output window + * instance has not been garbage collected. Whether or not it will be + * strongly referenced from within the output window module is up to the + * implementation. + * + * @return a component in which output text components will be created on + * demand when InputOutput instances are requested from the associated + * IOProvider. + */ + public abstract Component getOutputComponent(); + /** + * Get the IOProvider associated with this output component - the factory + * for InputOutput objects whose streams can read from and write to + * output window components inside the container returned by + * getOutputComponent(). + */ + public abstract IOProvider getIOProvider(); + + /** + * Convenience method which delegates to getIOProvider().getIO(). + */ + public final InputOutput getInputOutput (String name, boolean reuse) { + return getIOProvider().getIO (name, reuse); + } + + /** + * Convenience method which delegates to getIOProvider().getIO(). + */ + public final InputOutput getIO (String name, Action[] toolbarActions) { + return getIOProvider().getIO (name, toolbarActions); + } +} Index: core/output2/src/org/netbeans/core/output2/IOEvent.java =================================================================== RCS file: /cvs/core/output2/src/org/netbeans/core/output2/IOEvent.java,v retrieving revision 1.6 diff -u -r1.6 IOEvent.java --- core/output2/src/org/netbeans/core/output2/IOEvent.java 1 Jul 2006 08:51:58 -0000 1.6 +++ core/output2/src/org/netbeans/core/output2/IOEvent.java 14 Aug 2006 06:55:13 -0000 @@ -151,12 +151,25 @@ * Data associated with this event (used by set toolbar actions) */ private Object data = null; + + private OutputWindow dest; /** * Used by unit tests to ensure all pending events have been processed before * continuing. */ static int pendingCount = 0; + IOEvent(NbIO source, OutputWindow dest, int command, boolean value) { + this (source, command, value); + this.dest = dest; + } + + IOEvent(NbIO source, OutputWindow dest, int command, Object data) { + this (source, command, data); + this.dest = dest; + } + + /** * Create an IOEvent with the specified source, command and boolean state for the command. * @@ -250,15 +263,12 @@ } public void dispatch() { - //The only thing needed to make this package fully reentrant (capable of - //supporting multiple output windows, FWIW) is to replace this line with - //iterating a registry of OutputContainers, and calling eventDispatched on each. - + OutputWindow win = dest == null ? OutputWindow.DEFAULT : dest; //Null check below so that if the module has been disabled (the only way //this can be null), the dead module doesn't reopen its output window - if (OutputWindow.DEFAULT != null) { + if (win != null) { //Can be null after CMD_DETACH - OutputWindow.DEFAULT.eventDispatched(this); + win.eventDispatched(this); } pendingCount--; } Index: core/output2/src/org/netbeans/core/output2/NbIO.java =================================================================== RCS file: /cvs/core/output2/src/org/netbeans/core/output2/NbIO.java,v retrieving revision 1.13 diff -u -r1.13 NbIO.java --- core/output2/src/org/netbeans/core/output2/NbIO.java 1 Jul 2006 08:51:58 -0000 1.13 +++ core/output2/src/org/netbeans/core/output2/NbIO.java 14 Aug 2006 06:55:13 -0000 @@ -31,6 +31,8 @@ import java.awt.*; import java.io.IOException; import java.io.Reader; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import org.openide.util.Exceptions; /** Implementation of InputOutput. Implements calls as a set of @@ -205,13 +207,27 @@ post (this, IOEvent.CMD_RESET, true); } + private Reference outputWindowRef; + void setOutputWindow (OutputWindow window) { + OutputWindow win = getOutputWindow(); + if (win != null && win != window) { + throw new IllegalStateException ("Output window already assigned"); + } + this.outputWindowRef = new WeakReference (window); + } + + OutputWindow getOutputWindow() { + return outputWindowRef != null ? (OutputWindow) + outputWindowRef.get() : null; + } + private static void post (NbIO io, int command, boolean val) { - IOEvent evt = new IOEvent (io, command, val); + IOEvent evt = new IOEvent (io, io.getOutputWindow(), command, val); post (evt); } private static void post (NbIO io, int command, Object data) { - IOEvent evt = new IOEvent (io, command, data); + IOEvent evt = new IOEvent (io, io.getOutputWindow(), command, data); post (evt); } Index: core/output2/src/org/netbeans/core/output2/NbIOProvider.java =================================================================== RCS file: /cvs/core/output2/src/org/netbeans/core/output2/NbIOProvider.java,v retrieving revision 1.9 diff -u -r1.9 NbIOProvider.java --- core/output2/src/org/netbeans/core/output2/NbIOProvider.java 1 Jul 2006 08:51:58 -0000 1.9 +++ core/output2/src/org/netbeans/core/output2/NbIOProvider.java 14 Aug 2006 06:55:13 -0000 @@ -19,6 +19,7 @@ package org.netbeans.core.output2; +import java.awt.Component; import java.io.IOException; import javax.swing.Action; import org.openide.util.Exceptions; @@ -37,6 +38,14 @@ private static final String STDOUT = NbBundle.getMessage(NbIOProvider.class, "LBL_STDOUT"); //NOI18N + private OutputWindow outputWindow; + + public NbIOProvider() {} + + public NbIOProvider (OutputWindow window) { + this.outputWindow = window; + } + public OutputWriter getStdOut() { if (Controller.log) { Controller.log("NbIOProvider.getStdOut"); @@ -44,7 +53,11 @@ InputOutput stdout = getIO (STDOUT, false, null); NbWriter out = ((NbIO)stdout).writer(); - Controller.ensureViewInDefault ((NbIO) stdout, true); + if (outputWindow == null) { + Controller.ensureViewInDefault ((NbIO) stdout, true); + } else { + Controller.ensureViewIn (outputWindow, (NbIO) stdout, true); + } //ensure it is not closed if (out != null && out.isClosed()) { try { @@ -78,8 +91,13 @@ if (result == null || newIO) { result = new NbIO(name, toolbarActions); + result.setOutputWindow (outputWindow); namesToIos.add (name, result); - Controller.ensureViewInDefault (result, newIO); + if (outputWindow != null) { + Controller.ensureViewIn(outputWindow, result, newIO); + } else { + Controller.ensureViewInDefault (result, newIO); + } } else { // mkleint ignore actions if reuse of tabs. // result.setToolbarActions(toolbarActions); @@ -99,6 +117,22 @@ */ static void setWeak(boolean value) { namesToIos.setWeak(value); + } + + IOComponentManager getController() { + return new IOComponentControllerImpl(); + } + + private final class IOComponentControllerImpl extends IOComponentManager { + public Component getOutputComponent() { + assert outputWindow != null : "Cannot create component controller " + //NOI18N + "over default provider"; //NOI18N + return outputWindow; + } + + public IOProvider getIOProvider() { + return NbIOProvider.this; + } } } Index: core/output2/test/unit/src/org/netbeans/core/output2/IOComponentFactoryTest.java =================================================================== RCS file: core/output2/test/unit/src/org/netbeans/core/output2/IOComponentFactoryTest.java diff -N core/output2/test/unit/src/org/netbeans/core/output2/IOComponentFactoryTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/output2/test/unit/src/org/netbeans/core/output2/IOComponentFactoryTest.java 14 Aug 2006 06:55:14 -0000 @@ -0,0 +1,125 @@ +/* + * The contents of this file are subject to the terms of the Common Development + * and Distribution License (the License). You may not use this file except in + * compliance with the License. + * + * You can obtain a copy of the License at http://www.netbeans.org/cddl.html + * or http://www.netbeans.org/cddl.txt. + * + * When distributing Covered Code, include this CDDL Header Notice in each file + * and include the License file at http://www.netbeans.org/cddl.txt. + * If applicable, add the following below the CDDL Header, with the fields + * enclosed by brackets [] replaced by your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + */ +package org.netbeans.core.output2; + +import java.awt.BorderLayout; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import javax.swing.JFrame; +import javax.swing.JSplitPane; +import javax.swing.SwingUtilities; +import junit.framework.TestCase; + +/** + * + * @author Tim Boudreau + */ +public class IOComponentFactoryTest extends TestCase { + + public IOComponentFactoryTest(String testName) { + super(testName); + } + + protected void setUp() throws Exception { + Controller.log = true; + } + + protected void tearDown() throws Exception { + } + + public void testGetDefault() { + System.out.println("testGetDefault"); + IOComponentFactory fac = IOComponentFactory.getDefault(); + assertNotNull (fac); + } + + public void testNewComponentManager() throws Exception { + System.out.println("testNewComponentManager"); + IOComponentFactory fac = IOComponentFactory.getDefault(); + IOComponentManager one = fac.newComponentManager(); + assertNotNull (one); + IOComponentManager two = fac.newComponentManager(); + assertNotNull (two); + + OutputWindow winA = (OutputWindow) one.getOutputComponent(); + OutputWindow winB = (OutputWindow) two.getOutputComponent(); + + assertNotSame(winA, winB); + + NbIO ioA = (NbIO) one.getInputOutput("one", true); + NbIO ioB = (NbIO) two.getInputOutput("two", true); + assertNotSame (ioA, ioB); + + JFrame jf = new JFrame(); + jf.getContentPane().setLayout (new BorderLayout()); + JSplitPane jsp = new JSplitPane(); + WaitWindow ww = new WaitWindow (jf); + jf.getContentPane().add (jsp, BorderLayout.CENTER); + jf.setBounds (20, 20, 500, 500); + jf.setVisible(true); + ww.waitWindow(); + + jsp.setLeftComponent(winA); + jsp.setRightComponent(winB); + + NbWriter owA = (NbWriter) ioA.getOut(); + OutWriter realA = owA.out(); + for (int i=0; i < 5; i++) { + owA.println("Hello from A"); + } + owA.flush(); + sleep(); + assertEquals (5, realA.getLines().getLineCount()); + + NbWriter owB = (NbWriter) ioB.getOut(); + OutWriter realB = owB.out(); + assertEquals (0, realB.getLines().getLineCount()); + for (int i=0; i < 15; i++) { + owB.println("Hello from B"); + } + assertEquals (15, realB.getLines().getLineCount()); + + assertSame (jsp, SwingUtilities.getAncestorOfClass(JSplitPane.class, winA)); + assertSame (jsp, SwingUtilities.getAncestorOfClass(JSplitPane.class, winB)); + + Thread.currentThread().sleep (9000); + + } + + private void sleep() throws Exception { + Thread.currentThread().sleep(300); + } + + private static class WaitWindow extends WindowAdapter { + private JFrame frm; + WaitWindow (JFrame frm) { + this.frm = frm; + frm.addWindowListener (this); + } + public void windowOpened(WindowEvent e) { + synchronized (frm) { + frm.notifyAll(); + } + } + + public void waitWindow() throws Exception { + synchronized (frm) { + frm.wait (20000); + } + } + + } + +}