/* * NodeTreePanel.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: * 03-Oct-05 created */ package de.sciss.jcollider.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.ScrollPaneConstants; import javax.swing.WindowConstants; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.TreePath; import de.sciss.jcollider.Group; import de.sciss.jcollider.Node; import de.sciss.jcollider.NodeWatcher; import de.sciss.jcollider.Server; import de.sciss.jcollider.Synth; import de.sciss.net.OSCBundle; import de.sciss.net.OSCMessage; /** * A panel that contains a tree view of the nodes as * monitored by a <code>NodeWatcher</code>. * * @author Hanns Holger Rutz * @version 0.31, 08-Oct-07 * * @see de.sciss.jcollider.gui.NodeTreeManager */ public class NodeTreePanel extends JPanel implements TreeSelectionListener, TreeModelListener { /** * Flag for the constructor: create a button * bar to message the nodes. */ public static final int BUTTONS = 0x01; private final NodeTreeManager ntm; private final JTree ggTree; private final Node rootNode; protected boolean selectionContainsSynths = false; protected boolean selectionContainsGroups = false; protected boolean selectionContainsPlaying = false; protected boolean selectionContainsPausing = false; protected final List collSelectedNodes = new ArrayList(); private ActionPauseResume actionPauseResume = null; private ActionFree actionFree = null; private ActionFreeAll actionFreeAll = null; private ActionDeepFree actionDeepFree = null; private ActionTrace actionTrace = null; /** * Creates a new <code>NodeTreePanel</code> for a given node watcher * and root element. See the <code>NodeTreeManager</code> constructor * for details. Don't forget to call the <code>dispose</code> method * when the component is not needed any more. * * @param nw the node watcher to use for monitoring * @param rootNode the root element to display in the tree * @param flags flags that control what kind of gadgets are created * (e.g. <code>BUTTONS</code>). * * @see NodeTreeManager#NodeTreeManager( NodeWatcher, Node ) */ public NodeTreePanel( NodeWatcher nw, Node rootNode, int flags ) { super( new BorderLayout() ); this.rootNode = rootNode; ntm = new NodeTreeManager( nw, rootNode ); ggTree = new JTree( ntm.getModel() ); ggTree.setShowsRootHandles( true ); ggTree.setCellRenderer( new TreeNodeRenderer() ); // ggTree.getSelectionModel().setSelectionMode( TreeSelectionModel.SINGLE_TREE_SELECTION ); ggTree.addTreeSelectionListener( this ); ntm.getModel().addTreeModelListener( this ); final JScrollPane ggScroll = new JScrollPane( ggTree, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER ); add( ggScroll, BorderLayout.CENTER ); if( (flags & BUTTONS) != 0 ) { add( createButtons( flags ), BorderLayout.SOUTH ); } } /** * Creates a panel with default gadgets. */ public NodeTreePanel( NodeWatcher nw, Node rootNode ) { this( nw, rootNode, BUTTONS ); } public NodeTreeManager getManager() { return ntm; } private JComponent createButtons( int flags ) { final JToolBar tb = new JToolBar(); AbstractButton but; tb.setFloatable( false ); actionPauseResume = new ActionPauseResume(); but = new JButton( actionPauseResume ); // but.setFont( fntGUI ); tb.add( but ); actionFree = new ActionFree(); but = new JButton( actionFree ); // but.setFont( fntGUI ); tb.add( but ); actionFreeAll = new ActionFreeAll(); but = new JButton( actionFreeAll ); // but.setFont( fntGUI ); tb.add( but ); actionDeepFree = new ActionDeepFree(); but = new JButton( actionDeepFree ); // but.setFont( fntGUI ); tb.add( but ); actionTrace = new ActionTrace(); but = new JButton( actionTrace ); // but.setFont( fntGUI ); tb.add( but ); return tb; } /** * Frees resources when the component is * not used any more. This will dispose * the internal <code>NodeTreeManager</code>. */ public void dispose() { ggTree.removeTreeSelectionListener( this ); ntm.getModel().removeTreeModelListener( this ); ntm.dispose(); } /** * Creates a window containing this panel. * Do not try to create more than one window * for one panel or to attach the panel to * more than one container. The returned * frame will be visible and brought to the * front. The frame's default close operation * is <code>JFrame.DO_NOTHING_ON_CLOSE</code>, * so you will have to install a <code>WindowListener</code> * if the user shall be able to close the window. */ public JFrame makeWindow() { final JFrame f = new JFrame( "[" + rootNode.getServer().getName() + "] tree for " + rootNode.toString() ); final Container cp = f.getContentPane(); f.setDefaultCloseOperation( WindowConstants.DO_NOTHING_ON_CLOSE ); f.addWindowListener( new WindowAdapter() { public void windowClosed( WindowEvent e ) { dispose(); } }); cp.setLayout( new BorderLayout() ); cp.add( this, BorderLayout.CENTER ); f.pack(); f.setVisible( true ); f.toFront(); return f; } private void updateActions() { final TreePath[] sel = ggTree.getSelectionPaths(); selectionContainsSynths = false; selectionContainsGroups = false; selectionContainsPlaying = false; selectionContainsPausing = false; collSelectedNodes.clear(); if( sel != null ) { Node node; for( int i = 0; i < sel.length; i++ ) { node = (Node) sel[ i ].getLastPathComponent(); collSelectedNodes.add( node ); if( !selectionContainsSynths && (node instanceof Synth) ) selectionContainsSynths = true; if( !selectionContainsGroups && (node instanceof Group) ) selectionContainsGroups = true; if( !selectionContainsPlaying && node.isPlaying() ) selectionContainsPlaying = true; if( !selectionContainsPausing && !node.isPlaying() ) selectionContainsPausing = true; } } if( actionPauseResume != null ) actionPauseResume.update(); if( actionFree != null ) actionFree.update(); if( actionFreeAll != null ) actionFreeAll.update(); if( actionDeepFree != null ) actionDeepFree.update(); if( actionTrace != null ) actionTrace.update(); } // --------------- TreeSelectionListener interface --------------- /** * This method is part of the <code>TreeSelectionListener</code> interface. * Do not call this method. */ public void valueChanged( TreeSelectionEvent e ) { updateActions(); } // --------------- TreeModelListener interface --------------- /** * This method is part of the <code>TreeModelListener</code> interface. * Do not call this method. */ public void treeNodesChanged( TreeModelEvent e ) { updateActions(); // some actions change behaviour when nodes are paused/ resumed } /** * This method is part of the <code>TreeModelListener</code> interface. * Do not call this method. */ public void treeNodesInserted( TreeModelEvent e ) { // updateActions(); // not necessary i think XXX } /** * This method is part of the <code>TreeModelListener</code> interface. * Do not call this method. */ public void treeNodesRemoved(TreeModelEvent e) { updateActions(); // because corresponding deselections are not fired } /** * This method is part of the <code>TreeModelListener</code> interface. * Do not call this method. */ public void treeStructureChanged(TreeModelEvent e) { updateActions(); // necessary? XXX } // --------------- internal classes --------------- private abstract class NodeAction extends AbstractAction { protected NodeAction( String name ) { super( name ); } // bisschen aufwendig, aber evtl. wird es // NodeWatcher geben, die mehrere Server abhoeren public void actionPerformed( ActionEvent e ) { final HashMap mapServersToBundles = new HashMap(); OSCBundle bndl; OSCMessage msg; Server server; Node node; for( int i = 0; i < collSelectedNodes.size(); i++ ) { node = (Node) collSelectedNodes.get( i ); msg = createMessage( node ); if( msg == null ) continue; server = node.getServer(); bndl = (OSCBundle) mapServersToBundles.get( server ); if( bndl == null ) { bndl = new OSCBundle(); mapServersToBundles.put( server, bndl ); } bndl.addPacket( msg ); } for( Iterator iter = mapServersToBundles.keySet().iterator(); iter.hasNext(); ) { server = (Server) iter.next(); try { server.sendBundle( (OSCBundle) mapServersToBundles.get( server )); } catch( IOException e1 ) { System.err.println( e1.getClass().getName() + " : " + e1.getLocalizedMessage() ); } } } protected abstract OSCMessage createMessage( Node node ); protected abstract void update(); } private class ActionPauseResume extends NodeAction { private static final String NAME_PAUSE = "Pause"; private static final String NAME_RESUME = "Resume"; private boolean runFlag = false; protected ActionPauseResume() { super( NAME_PAUSE ); setEnabled( false ); } protected OSCMessage createMessage( Node node ) { return node.runMsg( runFlag ); } protected void update() { runFlag = selectionContainsPausing; setEnabled( (selectionContainsSynths || selectionContainsGroups) && (selectionContainsPlaying != selectionContainsPausing) ); putValue( NAME, runFlag ? NAME_RESUME : NAME_PAUSE ); } } private class ActionFree extends NodeAction { protected ActionFree() { super( "Free" ); setEnabled( false ); } protected OSCMessage createMessage( Node node ) { return node.freeMsg(); } protected void update() { setEnabled( selectionContainsSynths || selectionContainsGroups ); } } private class ActionFreeAll extends NodeAction { protected ActionFreeAll() { super( "Free All" ); setEnabled( false ); } protected OSCMessage createMessage( Node node ) { if( node instanceof Group ) { return ((Group) node).freeAllMsg(); } else { return null; } } protected void update() { setEnabled( !selectionContainsSynths && selectionContainsGroups ); } } private class ActionDeepFree extends NodeAction { protected ActionDeepFree() { super( "Deep Free" ); setEnabled( false ); } protected OSCMessage createMessage( Node node ) { if( node instanceof Group ) { return ((Group) node).deepFreeMsg(); } else { return null; } } protected void update() { setEnabled( !selectionContainsSynths && selectionContainsGroups ); } } private class ActionTrace extends NodeAction { protected ActionTrace() { super( "Trace" ); setEnabled( false ); } protected OSCMessage createMessage( Node node ) { return node.traceMsg(); } protected void update() { setEnabled( selectionContainsSynths || selectionContainsGroups ); } } private static class TreeNodeRenderer extends DefaultTreeCellRenderer { // doch 'n bisschen krass // private static final Color colrPlaying = new Color( 0x00, 0x50, 0x30 ); private static final Color colrPlaying = Color.black; private static final Color colrPausing = new Color( 0x90, 0x90, 0x90 ); private static final Color colrDied = new Color( 0x90, 0x00, 0x30 ); protected TreeNodeRenderer() { super(); } public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus ) { // DefaultTreeCellRenderer will set up the JLabel properties super.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus ); final Node node = (Node) value; if( node.isRunning() ) { if( node.isPlaying() ) { this.setForeground( colrPlaying ); } else { this.setForeground( colrPausing ); } } else { this.setForeground( colrDied ); } return this; } } }