Skip to content

Commit 1236f3c

Browse files
ctruedenhinerm
authored andcommitted
Add a snazzy console GUI for stdout/stderr
1 parent 6820a80 commit 1236f3c

File tree

5 files changed

+247
-1
lines changed

5 files changed

+247
-1
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<parent>
66
<groupId>org.scijava</groupId>
77
<artifactId>pom-scijava</artifactId>
8-
<version>5.7.0</version>
8+
<version>6.1.0</version>
99
<relativePath />
1010
</parent>
1111

src/main/java/org/scijava/ui/swing/AbstractSwingUI.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.scijava.ui.awt.AWTDropTargetEventDispatcher;
5656
import org.scijava.ui.awt.AWTInputEventDispatcher;
5757
import org.scijava.ui.awt.AWTWindowEventDispatcher;
58+
import org.scijava.ui.swing.console.SwingConsolePane;
5859
import org.scijava.ui.swing.menu.SwingJMenuBarCreator;
5960
import org.scijava.ui.swing.menu.SwingJPopupMenuCreator;
6061
import org.scijava.ui.viewer.DisplayViewer;
@@ -87,6 +88,7 @@ public abstract class AbstractSwingUI extends AbstractUserInterface implements
8788
private SwingApplicationFrame appFrame;
8889
private SwingToolBar toolBar;
8990
private SwingStatusBar statusBar;
91+
private SwingConsolePane consolePane;
9092
private AWTClipboard systemClipboard;
9193

9294
// -- UserInterface methods --
@@ -106,6 +108,11 @@ public SwingStatusBar getStatusBar() {
106108
return statusBar;
107109
}
108110

111+
@Override
112+
public SwingConsolePane getConsolePane() {
113+
return consolePane;
114+
}
115+
109116
@Override
110117
public SystemClipboard getSystemClipboard() {
111118
return systemClipboard;
@@ -167,6 +174,7 @@ protected void createUI() {
167174

168175
toolBar = new SwingToolBar(getContext());
169176
statusBar = new SwingStatusBar(getContext());
177+
consolePane = new SwingConsolePane(getContext());
170178

171179
systemClipboard = new AWTClipboard();
172180

@@ -207,6 +215,8 @@ public void windowClosing(final WindowEvent evt) {
207215
dropTargetDispatcher.register(statusBar);
208216
dropTargetDispatcher.register(appFrame);
209217

218+
setupConsole();
219+
210220
appFrame.pack();
211221
appFrame.setVisible(true);
212222
}
@@ -234,4 +244,9 @@ protected JMenuBar createMenus() {
234244
*/
235245
protected abstract void setupAppFrame();
236246

247+
/**
248+
* Configures the console for subclass-specific settings (e.g., SDI or MDI).
249+
*/
250+
protected abstract void setupConsole();
251+
237252
}
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
/*
2+
* #%L
3+
* SciJava UI components for Java Swing.
4+
* %%
5+
* Copyright (C) 2010 - 2015 Board of Regents of the University of
6+
* Wisconsin-Madison.
7+
* %%
8+
* Redistribution and use in source and binary forms, with or without
9+
* modification, are permitted provided that the following conditions are met:
10+
*
11+
* 1. Redistributions of source code must retain the above copyright notice,
12+
* this list of conditions and the following disclaimer.
13+
* 2. Redistributions in binary form must reproduce the above copyright notice,
14+
* this list of conditions and the following disclaimer in the documentation
15+
* and/or other materials provided with the distribution.
16+
*
17+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
21+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27+
* POSSIBILITY OF SUCH DAMAGE.
28+
* #L%
29+
*/
30+
31+
package org.scijava.ui.swing.console;
32+
33+
import java.awt.BorderLayout;
34+
import java.awt.Color;
35+
import java.awt.Component;
36+
import java.awt.Dimension;
37+
import java.awt.Font;
38+
39+
import javax.swing.JPanel;
40+
import javax.swing.JScrollPane;
41+
import javax.swing.JTextPane;
42+
import javax.swing.text.BadLocationException;
43+
import javax.swing.text.Style;
44+
import javax.swing.text.StyleConstants;
45+
import javax.swing.text.StyledDocument;
46+
47+
import net.miginfocom.swing.MigLayout;
48+
49+
import org.scijava.Context;
50+
import org.scijava.console.OutputEvent;
51+
import org.scijava.console.OutputEvent.Source;
52+
import org.scijava.plugin.Parameter;
53+
import org.scijava.thread.ThreadService;
54+
import org.scijava.ui.UIService;
55+
import org.scijava.ui.console.AbstractConsolePane;
56+
import org.scijava.ui.console.ConsolePane;
57+
58+
/**
59+
* Swing implementation of {@link ConsolePane}.
60+
*
61+
* @author Curtis Rueden
62+
*/
63+
public class SwingConsolePane extends AbstractConsolePane<JPanel> {
64+
65+
@Parameter
66+
private ThreadService threadService;
67+
68+
private JPanel consolePanel;
69+
private JTextPane textPane;
70+
private StyledDocument doc;
71+
private Style stdoutLocal;
72+
private Style stderrLocal;
73+
private Style stdoutGlobal;
74+
private Style stderrGlobal;
75+
76+
/**
77+
* The console pane's containing window; e.g., a {@link javax.swing.JFrame} or
78+
* {@link javax.swing.JInternalFrame}.
79+
*/
80+
private Component window;
81+
82+
public SwingConsolePane(final Context context) {
83+
super(context);
84+
}
85+
86+
// -- SwingConsolePane methods --
87+
88+
/** Sets the window which should be shown when {@link #show()} is called. */
89+
public void setWindow(final Component window) {
90+
this.window = window;
91+
}
92+
93+
// -- ConsolePane methods --
94+
95+
@Override
96+
public void append(final OutputEvent event) {
97+
if (consolePanel == null) initConsolePanel();
98+
try {
99+
doc.insertString(doc.getLength(), event.getOutput(), getStyle(event));
100+
}
101+
catch (final BadLocationException exc) {
102+
throw new RuntimeException(exc);
103+
}
104+
}
105+
106+
@Override
107+
public void show() {
108+
if (window == null) return;
109+
threadService.queue(new Runnable() {
110+
111+
@Override
112+
public void run() {
113+
window.setVisible(true);
114+
}
115+
});
116+
}
117+
118+
// -- UIComponent methods --
119+
120+
@Override
121+
public JPanel getComponent() {
122+
if (consolePanel == null) initConsolePanel();
123+
return consolePanel;
124+
}
125+
126+
@Override
127+
public Class<JPanel> getComponentType() {
128+
return JPanel.class;
129+
}
130+
131+
// -- Helper methods - lazy initialization --
132+
133+
private synchronized void initConsolePanel() {
134+
if (consolePanel != null) return;
135+
136+
final JPanel panel = new JPanel();
137+
panel.setLayout(new MigLayout("", "[grow,fill]", "[grow,fill,align top]"));
138+
139+
textPane = new JTextPane();
140+
textPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12));
141+
textPane.setEditable(false);
142+
143+
doc = textPane.getStyledDocument();
144+
145+
stdoutLocal = createStyle("stdoutLocal", null, Color.black, null, null);
146+
stderrLocal = createStyle("stderrLocal", null, Color.red, null, null);
147+
stdoutGlobal = createStyle("stdoutGlobal", stdoutLocal, null, null, true);
148+
stderrGlobal = createStyle("stderrGlobal", stderrLocal, null, null, true);
149+
150+
// NB: We wrap the JTextPane in a JPanel to disable
151+
// the text pane's intelligent line wrapping behavior.
152+
// I.e.: we want console lines _not_ to wrap, but instead
153+
// for the scroll pane to show a horizontal scroll bar.
154+
// Thanks to: https://tips4java.wordpress.com/2009/01/25/no-wrap-text-pane/
155+
final JPanel textPanel = new JPanel();
156+
textPanel.setLayout(new BorderLayout());
157+
textPanel.add(textPane);
158+
159+
final JScrollPane scrollPane = new JScrollPane(textPanel);
160+
scrollPane.setPreferredSize(new Dimension(600, 600));
161+
panel.add(scrollPane);
162+
163+
consolePanel = panel;
164+
}
165+
166+
// -- Helper methods --
167+
168+
private Style createStyle(final String name, final Style parent,
169+
final Color foreground, final Boolean bold, final Boolean italic)
170+
{
171+
final Style style = textPane.addStyle(name, parent);
172+
if (foreground != null) StyleConstants.setForeground(style, foreground);
173+
if (bold != null) StyleConstants.setBold(style, bold);
174+
if (italic != null) StyleConstants.setItalic(style, italic);
175+
return style;
176+
}
177+
178+
private Style getStyle(final OutputEvent event) {
179+
final boolean stderr = event.getSource() == Source.STDERR;
180+
final boolean contextual = event.isContextual();
181+
if (stderr) return contextual ? stderrLocal : stderrGlobal;
182+
return contextual ? stdoutLocal : stdoutGlobal;
183+
}
184+
185+
// -- Main method --
186+
187+
/** A manual test drive of the Swing UI's console pane. */
188+
public static void main(final String[] args) throws Exception {
189+
final Context context = new Context();
190+
context.service(UIService.class).showUI();
191+
192+
System.out.println("Hello!");
193+
194+
final ThreadService threadService = context.service(ThreadService.class);
195+
threadService.run(new Runnable() {
196+
197+
@Override
198+
public void run() {
199+
System.out.println("This is a test of the emergency console system.");
200+
System.err.println("In a real emergency, your computer would explode.");
201+
}
202+
203+
}).get();
204+
205+
System.err.println("Goodbye!");
206+
}
207+
208+
}

src/main/java/org/scijava/ui/swing/mdi/SwingMdiUI.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import java.awt.GraphicsEnvironment;
3535
import java.awt.Rectangle;
3636

37+
import javax.swing.JInternalFrame;
3738
import javax.swing.JScrollPane;
39+
import javax.swing.WindowConstants;
3840

3941
import org.scijava.Priority;
4042
import org.scijava.display.Display;
@@ -116,6 +118,16 @@ protected void setupAppFrame() {
116118
appFrame.setBounds(getWorkSpaceBounds());
117119
}
118120

121+
@Override
122+
protected void setupConsole() {
123+
final JInternalFrame frame = new JInternalFrame("Console");
124+
desktopPane.add(frame);
125+
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
126+
frame.setContentPane(getConsolePane().getComponent());
127+
frame.pack();
128+
getConsolePane().setWindow(frame);
129+
}
130+
119131
// -- Helper methods --
120132

121133
private Rectangle getWorkSpaceBounds() {

src/main/java/org/scijava/ui/swing/sdi/SwingSDIUI.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232

3333
import java.awt.BorderLayout;
3434

35+
import javax.swing.JFrame;
3536
import javax.swing.JPanel;
37+
import javax.swing.WindowConstants;
3638

3739
import org.scijava.display.Display;
3840
import org.scijava.event.EventService;
@@ -108,4 +110,13 @@ protected void setupAppFrame() {
108110
pane.setLayout(new BorderLayout());
109111
}
110112

113+
@Override
114+
protected void setupConsole() {
115+
final JFrame frame = new JFrame("Console");
116+
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
117+
frame.setContentPane(getConsolePane().getComponent());
118+
frame.pack();
119+
getConsolePane().setWindow(frame);
120+
}
121+
111122
}

0 commit comments

Comments
 (0)