Revision: 484 http://skycastle.svn.sourceforge.net/skycastle/?rev=484&view=rev Author: zzorn Date: 2008-04-26 13:42:11 -0700 (Sat, 26 Apr 2008) Log Message: ----------- Added closing handling to SimpleFrame, and hooked up the view to the model preliminary, as well as implemented a test action that adds a random stroke. Works nicely, next is putting back better navigation actions, stroke input, and smaller view updates when the model is updated. Modified Paths: -------------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SkycastleSketch.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/command/CommandAction.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/SimpleFrame.java Added Paths: ----------- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/CloseHandler.java Added: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java (rev 0) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchController.java 2008-04-26 20:42:11 UTC (rev 484) @@ -0,0 +1,156 @@ +package org.skycastle.sketch; + +import org.skycastle.sketch.model.Sketch; +import org.skycastle.sketch.model.SketchImpl; +import org.skycastle.sketch.model.point.StrokePointImpl; +import org.skycastle.sketch.model.stroke.Stroke; +import org.skycastle.sketch.model.stroke.StrokeImpl; +import org.skycastle.util.command.*; + +import javax.swing.*; +import java.util.Random; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author Hans Häggström + */ +@SuppressWarnings( { "serial" } ) +public class SketchController +{ + + //====================================================================== + // Private Fields + + private final CommandStack myCommandStack = new CommandStackImpl(); + + + private final CommandAction myExitAction = new CommandAction( myCommandStack, + "Exit", + "Exit the program and loose any changes made" ) + { + + @Override + protected Command createCommand() + { + return new AbstractCommand( "Request Exit", false ) + { + + public void doCommand() + { + final int selection = JOptionPane.showConfirmDialog( mySketchView.getMainFrame(), + "Are you sure you want to exit " + APP_NAME + "?", + "Confirm Exit", + JOptionPane.YES_NO_OPTION ); + if ( selection == JOptionPane.YES_OPTION ) + { + // TODO: Close down the UI somehow gracefully? + + //noinspection CallToSystemExit + System.exit( 0 ); + } + } + + }; + } + + }; + + private SketchView mySketchView; + + private Sketch mySketch; + + //====================================================================== + // Private Constants + + private static final String APP_NAME = "Sketch Room"; + private final CommandAction myAddTestStrokeAction = new CommandAction( myCommandStack, + "Test Stroke", + "Adds a random test stroke to the Sketch" ) + { + @Override + protected Command createCommand() + { + final Stroke testStroke = createTestStroke(); + + return new AbstractCommand( "Test Stroke", true ) + { + + public void doCommand() + { + mySketch.getRootGroup().add( testStroke ); + } + + @Override + public void undoCommand() + { + mySketch.getRootGroup().remove( testStroke ); + } + }; + } + }; + + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + + private static final Logger LOGGER = Logger.getLogger( SketchController.class.getName() ); + + + /** + * Creates a new {@link org.skycastle.sketch.SketchController}. + */ + public SketchController() + { + LOGGER.log( Level.INFO, APP_NAME + " starting up." ); + + // Create model + mySketch = new SketchImpl(); + } + + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * Creates and opens the UI. + */ + void start() + { + mySketchView = new SketchView( APP_NAME ); + + mySketchView.setCloseAction( myExitAction ); + + mySketchView.addMenuAction( "File", myExitAction ); + mySketchView.addMenuAction( "Edit", myCommandStack.getUndoAction() ); + mySketchView.addMenuAction( "Edit", myCommandStack.getRedoAction() ); + mySketchView.addMenuAction( "Debug", myAddTestStrokeAction ); + + mySketchView.addToolbarAction( myAddTestStrokeAction ); + mySketchView.addToolbarAction( myCommandStack.getUndoAction() ); + mySketchView.addToolbarAction( myCommandStack.getRedoAction() ); + + mySketchView.setSketch( mySketch ); + } + + + private Stroke createTestStroke() + { + final StrokeImpl stroke = new StrokeImpl(); + + final Random random = new Random(); + float x = (float) ( random.nextGaussian() * 100 ); + float y = (float) ( random.nextGaussian() * 100 ); + + for ( int i = 0; i < 100; i++ ) + { + x += random.nextGaussian() * 10; + y += random.nextGaussian() * 10; + + stroke.addPoint( new StrokePointImpl( x, y ) ); + } + + return stroke; + } +} Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java 2008-04-26 19:14:45 UTC (rev 483) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SketchView.java 2008-04-26 20:42:11 UTC (rev 484) @@ -1,11 +1,26 @@ package org.skycastle.sketch; +import com.jme.scene.Node; import org.skycastle.opengl.Canvas3D; +import org.skycastle.opengl.navigationgestures.NavigationGesture; +import org.skycastle.sketch.model.Sketch; +import org.skycastle.sketch.model.group.Group; +import org.skycastle.sketch.model.group.GroupElement; +import org.skycastle.sketch.model.stroke.Stroke; +import org.skycastle.util.ParameterChecker; +import org.skycastle.util.listenable.collection.CollectionListener; +import org.skycastle.util.listenable.collection.ListenableCollection; +import org.skycastle.util.simpleui.CloseHandler; import org.skycastle.util.simpleui.SimpleFrame; import javax.swing.*; -import java.awt.BorderLayout; -import java.awt.Component; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.HashMap; +import java.util.Map; /** * A view, which presents the 3D view of the sketch, and provides input events. @@ -13,49 +28,246 @@ * @author Hans Häggström */ // TODO: Have both a navigation stack and an undo stack, that are independent -public class SketchView +public final class SketchView { - private Component myUiComponent; + + //====================================================================== + // Private Fields + + private final Map<String, Menu> myMenus = new HashMap<String, Menu>( 10 ); + private final SimpleFrame myMainFrame; + private Canvas3D myCanvas3D; private JToolBar myToolBar; + private MenuBar myMenuBar; + private Sketch mySketch; + private final Node myRoot3DNode; + //====================================================================== + // Public Methods + + //---------------------------------------------------------------------- + // Constructors + /** * Creates a new {@link org.skycastle.sketch.SketchView}. */ - public SketchView() + public SketchView( String applicationName ) { + ParameterChecker.checkNotNull( applicationName, "applicationName" ); myCanvas3D = new Canvas3D(); + myMenuBar = new MenuBar(); + myToolBar = new JToolBar(); + myCanvas3D.removeAllNavigationGestures(); + myRoot3DNode = new Node( "SketchView_RootNode" ); + myCanvas3D.set3DNode( myRoot3DNode ); final JPanel panel = new JPanel( new BorderLayout() ); panel.add( myCanvas3D.get3DView(), BorderLayout.CENTER ); - myToolBar = new JToolBar(); panel.add( myToolBar, BorderLayout.NORTH ); +/* + // Wrap toolbar in a border that gives it a bit of marigin on top, as the AWT menu overlaps it otherwsie. + myToolBar.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder( 0, 0, 0, 0, new Color( 128, 128,128)), + myToolBar.getBorder() )); +*/ - new SimpleFrame( panel ); + myMainFrame = new SimpleFrame( panel ); + myMainFrame.setMenuBar( myMenuBar ); + myMainFrame.setTitle( applicationName ); /* final StrokeRenderer strokeRenderer = new StrokeRenderer( createTestStroke() ); myCanvas3D.set3DNode( strokeRenderer ); */ + } + //---------------------------------------------------------------------- + // Other Public Methods + + /** + * @param closeAction an action that is called when the close button of the main window is pressed, or the + * application is attempted to be closed in some other way. + */ + public void setCloseAction( final Action closeAction ) + { + ParameterChecker.checkNotNull( closeAction, "closeAction" ); + + myMainFrame.setCloseHandler( new CloseHandler() + { + + public void onCloseButtonPressed( final Frame frameWithPressedButton ) + { + closeAction.actionPerformed( null ); + } + + } ); } + /** - * An action / command in the application. - * - * @param action + * @return the frame for the application. */ - void addAction( Action action ) + public Frame getMainFrame() { + return myMainFrame; + } + + /** + * @param action an action to add to the toolbar. + */ + public void addToolbarAction( final Action action ) + { + myToolBar.add( action ); } - public Component getUiComponent() + /** + * @param menuName the menu to add the action to. + * @param action an action to add to the menu. + */ + public void addMenuAction( final String menuName, final Action action ) { - return myUiComponent; + // We need to use old style heavy AWT components, to render on top of the OpenGL surface. + // So we do a lot of adaption here, rahter than expose any clunky interface. + // The drawback is that it looks horrible, and overlaps the toolbar on 50% of the startups. + // TODO: Do a BannerBar in the style of Office 2007 instead of a separate menu and toolbar? + // It could maybe be a component that supports dockable / added panels/palettes too, for different tools. + + final MenuItem menuItem = new MenuItem( action.getValue( Action.NAME ).toString() ); + menuItem.setEnabled( action.isEnabled() ); + + // Listen to menu invocation + menuItem.addActionListener( new ActionListener() + { + + public void actionPerformed( final ActionEvent e ) + { + action.actionPerformed( e ); + } + + } ); + + // Listen to action enabled state changes + action.addPropertyChangeListener( new PropertyChangeListener() + { + + public void propertyChange( final PropertyChangeEvent evt ) + { + if ( "enabled".equals( evt.getPropertyName() ) ) + { + menuItem.setEnabled( action.isEnabled() ); + } + } + + } ); + + // Add to menu + getOrCreateMenu( menuName ).add( menuItem ); } + + + /** + * @param addedNavigationGesture a navigation gesture that recieves mouse and pen input from the 3D work + * area. + */ + public void addNavigationGesture( final NavigationGesture addedNavigationGesture ) + { + myCanvas3D.addNavigationGesture( addedNavigationGesture ); + } + + + /** + * @param removedNavigationGesture the listener to remove. + */ + public void removeNavigationGesture( final NavigationGesture removedNavigationGesture ) + { + myCanvas3D.removeNavigationGesture( removedNavigationGesture ); + } + + + /** + * @return the currently visible {@link Sketch}. + */ + public Sketch getSketch() + { + return mySketch; + } + + + /** + * @param sketch the {@link Sketch} to show in the {@link SketchView}. + */ + public void setSketch( final Sketch sketch ) + { + ParameterChecker.checkNotNull( sketch, "sketch" ); + + mySketch = sketch; + + reRender( sketch ); + + sketch.getRootGroup().addGroupListener( new CollectionListener<GroupElement>() + { + public void onElementAdded( final ListenableCollection<GroupElement> groupElementListenableCollection, + final GroupElement addedElement ) + { + // TODO: Instead of this, just modify the 3D object for the stroke that was changed, + // or add a new one, or remove an removed one. + reRender( sketch ); + } + + public void onElementRemoved( final ListenableCollection<GroupElement> groupElementListenableCollection, + final GroupElement removedElement ) + { + // TODO: Instead of this, just modify the 3D object for the stroke that was changed, + // or add a new one, or remove an removed one. + reRender( sketch ); + } + } ); + } + + private void reRender( final Sketch sketch ) + { + // Clean out old nodes (NOTE: detach all is usually a bit buggy / weird.. ) + myRoot3DNode.detachAllChildren(); + + renderGroup( sketch.getRootGroup() ); + } + + private void renderGroup( final Group group ) + { + for ( final GroupElement groupElement : group.getElements() ) + { + // TODO: Replace with some more polymorphic way to create nodes.. + + if ( groupElement instanceof Stroke ) + { + final Stroke stroke = (Stroke) groupElement; + myRoot3DNode.attachChild( new StrokeRenderer( stroke ) ); + } + else if ( groupElement instanceof Group ) + { + renderGroup( (Group) groupElement ); + } + } + } + + //====================================================================== + // Private Methods + + private Menu getOrCreateMenu( final String menuName ) + { + Menu menu = myMenus.get( menuName ); + if ( menu == null ) + { + menu = new Menu( menuName ); + myMenus.put( menuName, menu ); + myMenuBar.add( menu ); + } + return menu; + } + } Modified: trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SkycastleSketch.java =================================================================== --- trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SkycastleSketch.java 2008-04-26 19:14:45 UTC (rev 483) +++ trunk/skycastle/modules/ui/src/main/java/org/skycastle/sketch/SkycastleSketch.java 2008-04-26 20:42:11 UTC (rev 484) @@ -6,12 +6,7 @@ import com.jme.scene.Spatial; import com.jme.scene.shape.Box; import com.jme.scene.state.LightState; -import org.skycastle.sketch.model.point.StrokePointImpl; -import org.skycastle.sketch.model.stroke.Stroke; -import org.skycastle.sketch.model.stroke.StrokeImpl; -import org.skycastle.util.simpleui.SimpleFrame; -import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; @@ -44,17 +39,15 @@ */ public static void main( String[] args ) { - LOGGER.log( Level.INFO, "Skycastle Sketch starting up." ); // Do not show logging output below the WARNING level (JME outputs a lot of debugging info at INFO level). // This way the console output is a bit more relevant. LOGGER.setLevel( Level.WARNING ); - final SketchView sketchView = new SketchView(); - // TODO: Register a model modifying input listener and navigating input listener to the view + final SketchController sketchController = new SketchController(); - new SimpleFrame( sketchView.getUiComponent() ); + sketchController.start(); /* Sketch3DUI sketch3DUI = new Sketch3DUI(); @@ -66,25 +59,6 @@ // Private Methods - private static Stroke createTestStroke() - { - final StrokeImpl stroke = new StrokeImpl(); - - float x = 0; - float y = 0; - final Random random = new Random(); - - for ( int i = 0; i < 1000; i++ ) - { - x += 10; - y += random.nextGaussian() * 3; - - stroke.addPoint( new StrokePointImpl( x, y ) ); - } - - return stroke; - } - /** * Create the 3D scene. In this case, this is just the rotating box. */ Modified: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/command/CommandAction.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/command/CommandAction.java 2008-04-26 19:14:45 UTC (rev 483) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/command/CommandAction.java 2008-04-26 20:42:11 UTC (rev 484) @@ -31,7 +31,12 @@ public final void actionPerformed( final ActionEvent e ) { - myCommandStack.invoke( createCommand() ); + final Command command = createCommand(); + + if ( command != null ) + { + myCommandStack.invoke( command ); + } } //====================================================================== @@ -98,7 +103,7 @@ /** * @return a {@link Command} for executing the action, containing both the parameters for the command and - * the logic to run, and optionally undo logic. + * the logic to run, and optionally undo logic, or null if no command should be run after all. */ protected abstract Command createCommand(); Added: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/CloseHandler.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/CloseHandler.java (rev 0) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/CloseHandler.java 2008-04-26 20:42:11 UTC (rev 484) @@ -0,0 +1,18 @@ +package org.skycastle.util.simpleui; + +import java.awt.Frame; + +/** + * Allows the implementor to decide what should be done when the close button on a frame is pressed. + * + * @author Hans Haggstrom + */ +public interface CloseHandler +{ + /** + * Called when the user presses the exit button on the frame. + * + * @param frameWithPressedButton the frame whose close button was pressed. + */ + void onCloseButtonPressed( Frame frameWithPressedButton ); +} Modified: trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/SimpleFrame.java =================================================================== --- trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/SimpleFrame.java 2008-04-26 19:14:45 UTC (rev 483) +++ trunk/skycastle/modules/utils/src/main/java/org/skycastle/util/simpleui/SimpleFrame.java 2008-04-26 20:42:11 UTC (rev 484) @@ -6,6 +6,8 @@ import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; /** * A simple to use frame, suitable for spikes and scripts. @@ -23,6 +25,8 @@ private final JPanel myMainPanel = new JPanel(); + private CloseHandler myCloseHandler = null; + //====================================================================== // Private Constants @@ -114,8 +118,26 @@ { super( title ); - setDefaultCloseOperation( EXIT_ON_CLOSE ); + // Allow overriding the close behaviour. + setDefaultCloseOperation( DO_NOTHING_ON_CLOSE ); + addWindowListener( new WindowAdapter() + { + @Override + public void windowClosing( WindowEvent winEvt ) + { + if ( myCloseHandler != null ) + { + myCloseHandler.onCloseButtonPressed( SimpleFrame.this ); + } + else + { + System.exit( 0 ); + } + } + + } ); + myMainPanel.setLayout( new BorderLayout() ); myMainPanel.setPreferredSize( new Dimension( width, height ) ); add( myMainPanel ); @@ -135,6 +157,26 @@ // Other Public Methods /** + * @return the current {@link CloseHandler}, or null if none defined. + */ + public final CloseHandler getCloseHandler() + { + return myCloseHandler; + } + + + /** + * @param closeHandler a {@link CloseHandler} that is notified when the close button of the {@link + * SimpleFrame} is clicked. If null (the default), the {@link SimpleFrame} exists on + * close. + */ + public final void setCloseHandler( final CloseHandler closeHandler ) + { + myCloseHandler = closeHandler; + } + + + /** * @return the main panel that contains the user specified UI component. */ public JPanel getMainPanel() This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.