/*
* ServerWindow.java
* JCollider
*
* Copyright (c) 2004-2010 Hanns Holger Rutz. All rights reserved.
*
* This software is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either
* version 2, june 1991 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License (gpl.txt) along with this software; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* For further information, please contact Hanns Holger Rutz at
* contact@sciss.de , or visit http://www.sciss.de/jcollider
*
*
* JCollider is closely modelled after SuperCollider Language,
* often exhibiting a direct translation from Smalltalk to Java.
* SCLang is a software originally developed by James McCartney,
* which has become an Open Source project.
* See http://www.audiosynth.com/ for details.
*
*
* Changelog:
* 10-Sep-05 created
*/
package de.sciss.jcollider.gui;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Locale;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.WindowConstants;
import de.sciss.jcollider.Constants;
import de.sciss.jcollider.JCollider;
import de.sciss.jcollider.Server;
import de.sciss.jcollider.ServerEvent;
import de.sciss.jcollider.ServerListener;
/**
* A quick hack to provide a GUI
* element looking similar to SCLang's
* server window. Additionally, you
* can opt to create a text console
* for scsynth text output when booting
* the local server.
*
* @author Hanns Holger Rutz
* @version 0.32, 25-Feb-08
*/
public class ServerPanel
extends JPanel
implements ServerListener, Constants
{
/**
* <code>Font</code> used for the text console.
* You can set this field prior to instantiating
* the <code>ServerPanel</code> to use a
* different font.
*/
public static Font fntConsole;
/**
* <code>Font</code> used for the GUI elements (buttons).
* You can set this field prior to instantiating
* the <code>ServerPanel</code> to use a
* different font.
*/
public static Font fntGUI = new Font( "Helvetica", Font.PLAIN, 12 );
/**
* <code>Font</code> used for server name box.
* You can set this field prior to instantiating
* the <code>ServerPanel</code> to use a
* different font.
*/
public static Font fntBigName = new Font( "Helvetica", Font.BOLD, 16 );
/**
* Constructor flag: create a console panel.
*/
public static final int CONSOLE = 0x01;
/**
* Constructor flag: create a boot/quit button
*/
public static final int BOOTQUIT = 0x02;
// public static final int KILL = 0x04;
/**
* Constructor flag: create a server name box
*/
public static final int BIGNAME = 0x08;
// public static final int MAKEDEFAULT = 0x10;
// public static final int RECORD = 0x20;
/**
* Constructor flag: create a status panel
* for server CPU usage and number of nodes.
*/
public static final int COUNTS = 0x40;
/**
* Constructor flag: create a button to
* turning OSC dumping on/off.
*/
public static final int DUMP = 0x80;
/**
* Constructor flag: shortcut for creating
* those GUI elements that SClang provides.
*/
public static final int MIMIC = 0x7E;
private static final int BUTTONS = 0xBE;
protected final Server server;
protected final ServerPanel enc_this = this;
private JTextField lbBigName = null;
private ActionBoot actionBoot = null;
private JFrame ourFrame;
private JScrollPane ggScroll;
private boolean showHide;
// private javax.swing.Timer showHideTimer;
private JLabel lbCntAvgCPU, lbCntPeakCPU, lbCntUGens, lbCntSynths, lbCntGroups, lbCntSynthDefs;
private static final String COUNT_NA = "?";
private static final MessageFormat msgCntPercent = new MessageFormat( "{0,number,#.#} %", Locale.US );
private static final Color colrRunning = new Color( 0x58, 0xB0, 0x8D );
private static final Color colrBooting = new Color( 0xCC, 0xFF, 0x33 );
private static final Color colrStopped = new Color( 0x60, 0x60, 0x60 );
static {
fntConsole = JCollider.isMacOS ? new Font( "Monaco", Font.PLAIN, 10 ) :
new Font( "Monospaced", Font.PLAIN, 11 );
}
/**
* Creates a new <code>ServerPanel</code> for
* the given server and with GUI elements specified
* by the flags.
*
* @param server the server to which this panel is
* connected
* @param flags a mask of flags that define which
* GUI elements should be created,
* e.g. <code>CONSOLE</code>, <code>BOOTQUIT</code> etc.
*/
public ServerPanel( Server server, int flags )
{
super();
this.server = server;
final BoxLayout lay = new BoxLayout( this, BoxLayout.Y_AXIS );
setLayout( lay );
if( (flags & CONSOLE) != 0 ) {
add( createConsole() );
}
if( (flags & BUTTONS) != 0 ) {
add( createButtons( flags ));
}
if( (flags & COUNTS) != 0 ) {
add( createCountsPanel() );
}
// JCollider.setDeepFont( this, fntGUI );
server.addListener( this );
}
/**
* Call this method if the server
* isn't used any more or the panel's
* parent window is disposed. This
* will free any resources occupied by
* the panel and remove listeners.
*/
public void dispose()
{
server.removeListener( this );
}
private JComponent createConsole()
{
final LogTextArea lta = new LogTextArea( 12, 40, false, null );
final InputMap imap = lta.getInputMap();
final ActionMap amap = lta.getActionMap();
imap.put( KeyStroke.getKeyStroke( KeyEvent.VK_DELETE, 0 ), "clear" );
amap.put( "clear", lta.getClearAction() );
lta.setFont( fntConsole );
Server.setPrintStream( lta.getLogStream() );
ggScroll = lta.placeMeInAPane();
// showHideTimer = new javax.swing.Timer( 100, new ActionListener() {
// public void actionPerformed( ActionEvent e )
// {
// final boolean isVisible = ggScroll.isVisible();
// final int currentHeight = isVisible ? ggScroll.getHeight() : 0;
// final int frameWidth = ourFrame.getWidth();
// final int frameHeight = ourFrame.getHeight();
//
// if( showHide ) {
// if( !isVisible ) {
// ourFrame.setSize( frameWidth, frameHeight + 32 );
// ggScroll.setVisible( true );
// } else {
// if( currentHeight < 256 ) {
// ourFrame.setSize( frameWidth, frameHeight + 32 );
// } else {
// showHideTimer.stop();
// }
// }
// } else {
// if( currentHeight > 32 ) {
// ourFrame.setSize( frameWidth, frameHeight - 32 );
// } else {
// ggScroll.setVisible( false );
// showHideTimer.stop();
// }
// }
// }
// });
return ggScroll;
}
private JComponent createCountsPanel()
{
final JPanel p = new JPanel( new GridLayout( 3, 4, 0, 4 ));
lbCntAvgCPU = new JLabel();
lbCntPeakCPU = new JLabel();
lbCntUGens = new JLabel();
lbCntSynths = new JLabel();
lbCntGroups = new JLabel();
lbCntSynthDefs = new JLabel();
p.add( new JLabel( JCollider.getResourceString( "countsAvgCPU" ) + " : ", SwingConstants.RIGHT ));
p.add( lbCntAvgCPU );
p.add( new JLabel( JCollider.getResourceString( "countsPeakCPU" ) + " : ", SwingConstants.RIGHT ));
p.add( lbCntPeakCPU );
p.add( new JLabel( JCollider.getResourceString( "countsUGens" ) + " : ", SwingConstants.RIGHT ));
p.add( lbCntUGens );
p.add( new JLabel( JCollider.getResourceString( "countsSynths" ) + " : ", SwingConstants.RIGHT ));
p.add( lbCntSynths );
p.add( new JLabel( JCollider.getResourceString( "countsGroups" ) + " : ", SwingConstants.RIGHT ));
p.add( lbCntGroups );
p.add( new JLabel( JCollider.getResourceString( "countsSynthDefs" ) + " : ", SwingConstants.RIGHT ));
p.add( lbCntSynthDefs );
p.setBorder( BorderFactory.createEmptyBorder( 2, 8, 8, 8 ));
JCollider.setDeepFont( p, fntGUI );
p.setMaximumSize( p.getPreferredSize() );
updateCounts();
return p;
}
private JComponent createButtons( int flags )
{
// final Box b = Box.createHorizontalBox();
final JToolBar tb = new JToolBar();
AbstractButton but;
Insets insets;
tb.setBorderPainted( false );
tb.setFloatable( false );
if( (flags & BOOTQUIT) != 0 ) {
actionBoot = new ActionBoot();
but = new JButton( actionBoot );
but.setFont( fntGUI );
// b.add( but );
insets = but.getMargin();
but.setMargin( new Insets( insets.top + 2, insets.left + 4, insets.bottom + 2, insets.right + 4 ));
tb.add( but );
}
if( (flags & BIGNAME) != 0 ) {
lbBigName = new JTextField( 8 );
lbBigName.setFont( fntBigName );
lbBigName.setMaximumSize( lbBigName.getPreferredSize() );
lbBigName.setText( server.getName() );
lbBigName.setEditable( false );
lbBigName.setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ));
lbBigName.setBackground( Color.black );
lbBigName.setHorizontalAlignment( SwingConstants.CENTER );
updateBigName();
// b.add( lbBigName );
tb.add( lbBigName );
}
// if( (flags & MAKEDEFAULT) != 0 ) {
// b.add( new JButton( new actionMakeDefaultClass() ));
// }
if( (flags & DUMP) != 0 ) {
but = new JToggleButton( new ActionDump() );
but.setFont( fntGUI );
insets = but.getMargin();
but.setMargin( new Insets( insets.top + 2, insets.left + 4, insets.bottom + 2, insets.right + 4 ));
// b.add( but );
tb.add( but );
}
// b.add( Box.createHorizontalGlue() );
// tb.add( Box.createHorizontalGlue() );
// return b;
return tb;
}
protected void updateBigName()
{
if( lbBigName == null ) return;
if( server.isRunning() ) {
lbBigName.setForeground( colrRunning );
} else if( server.isBooting() ) {
lbBigName.setForeground( colrBooting );
} else {
lbBigName.setForeground( colrStopped );
}
}
private void updateCounts()
{
if( lbCntAvgCPU == null ) return;
if( server.isRunning() ) {
final Server.Status status = server.getStatus();
final Float[] cntArgs = new Float[ 1 ];
cntArgs[ 0 ] = new Float( status.avgCPU );
lbCntAvgCPU.setText( msgCntPercent.format( cntArgs ));
cntArgs[ 0 ] = new Float( status.peakCPU );
lbCntPeakCPU.setText( msgCntPercent.format( cntArgs ));
lbCntUGens.setText( String.valueOf( status.numUGens ));
lbCntSynths.setText( String.valueOf( status.numSynths ));
lbCntGroups.setText( String.valueOf( status.numGroups ));
lbCntSynthDefs.setText( String.valueOf( status.numSynthDefs ));
} else {
lbCntAvgCPU.setText( COUNT_NA );
lbCntPeakCPU.setText( COUNT_NA );
lbCntUGens.setText( COUNT_NA );
lbCntSynths.setText( COUNT_NA );
lbCntGroups.setText( COUNT_NA );
lbCntSynthDefs.setText( COUNT_NA );
}
}
protected void showHideConsole( boolean show )
{
// if( (ourFrame == null) || (showHideTimer == null) || (this.showHide == show) ) return;
if( (ourFrame == null) || (this.showHide == show) ) return;
this.showHide = show;
ggScroll.setVisible( show );
ourFrame.pack();
// showHideTimer.start();
}
/**
* Creates a window containing
* a <code>ServerPanel</code> for the given
* server. Uses default flags (<code>MIMIC</code>).
*
* @param server the server to which the
* panel shall be connected
*
* @return a frame containing the panel. This
* frame is already made visible. The default
* close operation is <code>DO_NOTHING_ON_CLOSE</code>,
* so you will want to attach a <code>WindowListener</code>
* which deals with closing and cleanup.
*/
public static JFrame makeWindow( Server server )
{
return makeWindow( server, MIMIC );
}
/**
* Creates a window with custom flags.
* See the one argument method for details.
*
* @param server the server to which the
* panel shall be connected
* @param flags a mask of flags that define which
* GUI elements should be created,
* e.g. <code>CONSOLE</code>, <code>BOOTQUIT</code> etc.
*
* @see #makeWindow( Server )
*/
public static JFrame makeWindow( Server server, int flags )
{
final ServerPanel sp = new ServerPanel( server, flags );
final JFrame f = new JFrame( server.getName() + " server" );
final Container cp = f.getContentPane();
sp.ourFrame = f;
if( sp.ggScroll != null ) sp.ggScroll.setVisible( false );
f.setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE );
cp.setLayout( new BorderLayout() );
cp.add( sp, BorderLayout.CENTER );
f.pack();
f.setVisible( true );
f.toFront();
return f;
}
// -------------- ServerListener interface --------------
/**
* This class implements the <code>ServerListener</code>
* interface to be notified about the server
* booting and quitting, and for tracking
* the status.
*/
public void serverAction( ServerEvent e )
{
switch( e.getID() ) {
case ServerEvent.RUNNING:
if( actionBoot != null ) {
actionBoot.booted();
}
updateBigName();
break;
case ServerEvent.STOPPED:
if( actionBoot != null ) {
actionBoot.terminated();
}
updateBigName();
updateCounts();
break;
case ServerEvent.COUNTS:
updateCounts();
break;
default:
break;
}
}
// -------------- internal classes --------------
private class ActionBoot
extends AbstractAction
{
private boolean booted;
protected ActionBoot()
{
super( JCollider.getResourceString( "buttonBoot" ));
booted = server.isRunning() || server.isBooting();
}
public void actionPerformed( ActionEvent e )
{
try {
if( booted ) {
server.quit();
} else {
server.boot();
updateBigName();
showHideConsole( true );
}
}
catch( IOException e1 ) {
JCollider.displayError( enc_this, e1, getValue( NAME ).toString() );
}
}
protected void terminated()
{
booted = false;
putValue( NAME, JCollider.getResourceString( "buttonBoot" ));
showHideConsole( false );
}
protected void booted()
{
booted = true;
putValue( NAME, JCollider.getResourceString( "buttonQuit" ));
}
} // class actionBootClass
private class ActionDump
extends AbstractAction
{
private boolean dumping;
protected ActionDump()
{
super( JCollider.getResourceString( "buttonDumpOSC" ));
dumping = server.getDumpMode() != kDumpOff;
}
public void actionPerformed( ActionEvent e )
{
final AbstractButton b = (AbstractButton) e.getSource();
dumping = b.isSelected();
try {
server.dumpOSC( dumping ? kDumpText : kDumpOff );
}
catch( IOException e1 ) {
JCollider.displayError( enc_this, e1, getValue( NAME ).toString() );
}
}
} // class actionDumpClass
}