package greenfoot.core;
import greenfoot.Actor;
import greenfoot.ActorVisitor;
import greenfoot.World;
import greenfoot.WorldVisitor;
import greenfoot.core.Simulation.SimulationRunnable;
import greenfoot.event.SimulationListener;
import greenfoot.event.WorldEvent;
import greenfoot.event.WorldListener;
import greenfoot.gui.input.KeyboardManager;
import greenfoot.gui.input.mouse.MousePollingManager;
import greenfoot.gui.input.mouse.WorldLocator;
import greenfoot.platforms.WorldHandlerDelegate;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import bluej.debugmgr.objectbench.ObjectBenchInterface;
import threadchecker.OnThread;
import threadchecker.Tag;
| The worldhandler handles the connection between the World and the
| VMCommsSimulation.
|
| @author Poul Henriksen
|
@OnThread(Tag.Simulation)
public class WorldHandler
implements SimulationListener{
| A flag to check whether a world has been set. Can be tested/cleared by callers.
|
@OnThread(value = Tag.Any, requireSynchronized = true)
private boolean worldIsSet;
@OnThread(Tag.Any)
private volatile World world;
private int dragBeginX;
private int dragBeginY;
@OnThread(Tag.Any)
private final KeyboardManager keyboardManager;
@OnThread(Tag.Any)
private static WorldHandler instance;
@OnThread(Tag.Any)
private final List<WorldListener> worldListeners = new ArrayList<>();
@OnThread(Tag.Any)
private WorldHandlerDelegate handlerDelegate;
@OnThread(Tag.Any)
private final MousePollingManager mousePollingManager;
private int dragOffsetX;
private int dragOffsetY;
private Actor dragActor;
private boolean dragActorMoved;
private int dragId;
| Initialise the WorldHandler singleton.
|
| @param worldCanvas the WorldCanvas to connect to
| @param helper the handler delegate for operations
|
@OnThread(Tag.Any)
public static synchronized void initialise(WorldHandlerDelegate helper)
{
instance = new WorldHandler(helper);
}
| Initialiser for unit testing.
|
@OnThread(Tag.Any)
public static synchronized void initialise()
{
instance = new WorldHandler();
}
| Return the singleton instance.
|
@OnThread(Tag.Any)
public synchronized static WorldHandler getInstance()
{
return instance;
}
| Constructor used for unit testing.
|
@OnThread(Tag.Any)
private WorldHandler()
{
instance = this;
keyboardManager = new KeyboardManager();
mousePollingManager = new MousePollingManager(null);
handlerDelegate = new WorldHandlerDelegate() {
@Override
public void discardWorld(World world)
{
}
@Override
public void instantiateNewWorld(String className, Runnable runIfError)
{
}
@Override
public void setWorld(World oldWorld, World newWorld)
{
}
@Override
public void objectAddedToWorld(Actor actor)
{
}
@Override
public String ask(String prompt)
{
return "";
}
@Override
public void paint(World drawWorld, boolean forcePaint)
{
}
@Override
public void notifyStoppedWithError()
{
}
};
}
| Creates a new worldHandler and sets up the connection between worldCanvas
| and world.
|
| @param handlerDelegate
|
@OnThread(Tag.Any)
private WorldHandler(WorldHandlerDelegate handlerDelegate)
{
instance = this;
this.handlerDelegate = handlerDelegate;
mousePollingManager = new MousePollingManager(null);
keyboardManager = new KeyboardManager();
}
| Get the keyboard manager.
|
@OnThread(Tag.Any)
public KeyboardManager getKeyboardManager()
{
return keyboardManager;
}
| Get the mouse manager.
|
@OnThread(Tag.Any)
public MousePollingManager getMouseManager()
{
return mousePollingManager;
}
| Drag operation starting.
|
@OnThread(Tag.Simulation)
public void startDrag(Actor actor, Point p, int dragId)
{
dragActor = actor;
dragActorMoved = false;
dragBeginX = ActorVisitor.getX(actor) * world.getCellSize() + world.getCellSize() / 2;
dragBeginY = ActorVisitor.getY(actor) * world.getCellSize() + world.getCellSize() / 2;
dragOffsetX = dragBeginX - p.x;
dragOffsetY = dragBeginY - p.y;
this.dragId = dragId;
drag(actor, p);
}
public boolean isDragging()
{
return dragActor != null;
}
| Returns an object from the given world at the given pixel location. If multiple objects
| exist at the one location, this method returns the top-most one according to
| paint order.
|
| @param x
| The x-coordinate
| @param y
| The y-coordinate
|
private static Actor getObject(World world, int x, int y)
{
if (world == null)
{
return null;
}
Collection<?> objectsThere = WorldVisitor.getObjectsAtPixel(world, x, y);
if (objectsThere.isEmpty())
{
return null;
}
Iterator<?> iter = objectsThere.iterator();
Actor topmostActor = (Actor) iter.next();
int seq = ActorVisitor.getLastPaintSeqNum(topmostActor);
while (iter.hasNext())
{
Actor actor = (Actor) iter.next();
int actorSeq = ActorVisitor.getLastPaintSeqNum(actor);
if (actorSeq > seq)
{
topmostActor = actor;
seq = actorSeq;
}
}
return topmostActor;
}
|
| @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
|
public void mouseExited(MouseEvent e)
{
if (dragActor != null) {
dragActorMoved = false;
Simulation.getInstance().runLater(new SimulationRunnable() {
private Actor dragActor = WorldHandler.this.dragActor;
private int dragBeginX = WorldHandler.this.dragBeginX;
private int dragBeginY = WorldHandler.this.dragBeginY;
@Override
public void run()
{
ActorVisitor.setLocationInPixels(dragActor, dragBeginX, dragBeginY);
repaint();
}
});
}
}
| Request a repaint of the world.
|
public void repaint()
{
paint(true);
}
| Request a repaint of the world.
| Call only from the simulation thread.
|
public void repaintAndWait()
{
repaint();
}
| Instantiate a new world and do any initialisation needed to activate that
| world.
|
| @param className The fully qualified name of the world class to instantiate
| if a specific class is wanted. If null, use the most recently
| instantiated world class.
|
@OnThread(Tag.Any)
public void instantiateNewWorld(String className)
{
handlerDelegate.instantiateNewWorld(className, () -> worldInstantiationError());
}
| Notify that construction of a new world has started. Note that this method
| has a special breakpoint set by GreenfootDebugHandler, so do not remove/rename
| without also editing that code.
|
public void setInitialisingWorld(World world)
{
handlerDelegate.initialisingWorld(world.getClass().getName());
}
| Removes the current world. This can be called from any thread.
|
@OnThread(Tag.Any)
public void discardWorld()
{
final World discardedWorld;
synchronized (this)
{
if (world == null)
{
return;
}
handlerDelegate.discardWorld(world);
discardedWorld = world;
world = null;
}
Simulation.getInstance().runLater(() -> {
fireWorldRemovedEvent(discardedWorld);
});
}
| Check whether a world has been set (via {}link #setWorld()}) since the "world is set" flag was last cleared.
|*/
@OnThread(Tag.Any)
public synchronized boolean checkWorldSet()
{
return worldIsSet;
}
/**
* Clear the "world is set" flag.
*/
@OnThread(Tag.Any)
public synchronized void clearWorldSet()
{
worldIsSet = false;
}
/**
* Sets a new world.
*
* @param world The new world. Must not be null.
| @param byUserCode Was this world set by a call to Greenfoot.setWorld (which thus would
| have come from the user's code)? If false, it means it was set by our own
| internal code, e.g. initialisation during standalone, or GUI interactions
| in the IDE.
|
@OnThread(Tag.Any)
public synchronized void setWorld(final World world, boolean byUserCode)
{
worldIsSet = true;
handlerDelegate.setWorld(this.world, world);
mousePollingManager.setWorldLocator(new WorldLocator() {
@Override
@OnThread(Tag.Simulation)
public Actor getTopMostActorAt(int x, int y)
{
return getObject(world, x, y);
}
@Override
@OnThread(Tag.Any)
public int getTranslatedX(int x)
{
return WorldVisitor.toCellFloor(world, x);
}
@Override
@OnThread(Tag.Any)
public int getTranslatedY(int y)
{
return WorldVisitor.toCellFloor(world, y);
}
});
this.world = world;
Simulation.getInstance().runLater(() -> {
fireWorldCreatedEvent(world);
});
worldChanged(byUserCode);
}
| This is a special method which will have a breakpoint set by the GreenfootDebugHandler
| class. Do not remove or rename without also changing that class.
|
| @param byUserCode Was this world set by a call to Greenfoot.setWorld (which thus would
| have come from the user's code)? If false, it means it was set by our own
| internal code, e.g. initialisation during standalone, or GUI interactions
| in the IDE. This param is marked unused but actually
| GreenfootDebugHandler will inspect it via JDI
|
@OnThread(Tag.Any)
private void worldChanged(boolean byUserCode)
{
}
| This is a special method which will have a breakpoint set by the GreenfootDebugHandler
| class. Do not remove or rename without also changing that class.
| It is called where there is an error instantiated the world class
| (as a result of a user interactive creation, not user code)
|
@OnThread(Tag.Any)
private void worldInstantiationError()
{
}
| Return the currently active world.
|
public synchronized World getWorld()
{
return world;
}
| Checks if there is a world set.
|
| This is not the same as checking if getWorld() is null, because getWorld()
| can return a world being initialised. This method checks if a world has
| actually been set.
|
public synchronized boolean hasWorld()
{
return world != null;
}
| Handle drop of actors. Handles QuickAdd
|
| When existing actors are dragged around in the world, that uses drag -- drop is *not* called for those
|
public boolean drop(Object o, Point p)
{
final World world = this.world;
int maxHeight = WorldVisitor.getHeightInPixels(world);
int maxWidth = WorldVisitor.getWidthInPixels(world);
final int x = (int) p.getX();
final int y = (int) p.getY();
if (x >= maxWidth || y >= maxHeight || x < 0 || y < 0) {
return false;
}
p.public else if(o instanceof Actor && ActorVisitor.getWorld((Actor) o) == null)
{
Actor actor = (Actor) o;
addActorAtPixel(actor, x, y);
return true;
}
p.public else if(o instanceof Actor)
{
final Actor actor = (Actor) o;
if (ActorVisitor.getWorld(actor) == null) {
return false;
}
Simulation.getInstance().runLater(() -> ActorVisitor.setLocationInPixels(actor, x, y));
dragActorMoved = true;
return true;
}
else {
return false;
}
}
| Handle drag on actors that are already in the world.
|
| This must be called on the simulation thread.
|
@OnThread(Tag.Simulation)
public boolean drag(Object o, Point p)
{
World world = this.world;
if (o instanceof Actor && world != null)
{
int x = WorldVisitor.toCellFloor(world, (int) p.getX() + dragOffsetX);
int y = WorldVisitor.toCellFloor(world, (int) p.getY() + dragOffsetY);
final Actor actor = (Actor) o;
try
{
int oldX = ActorVisitor.getX(actor);
int oldY = ActorVisitor.getY(actor);
if (oldX != x || oldY != y)
{
if (x < WorldVisitor.getWidthInCells(world) && y < WorldVisitor.getHeightInCells(world)
&& x >= 0 && y >= 0)
{
ActorVisitor.setLocationInPixels(actor,
(int) p.getX() + dragOffsetX,
(int) p.getY() + dragOffsetY);
dragActorMoved = true;
repaint();
}
else
{
ActorVisitor.setLocationInPixels(actor, dragBeginX, dragBeginY);
x = WorldVisitor.toCellFloor(getWorld(), dragBeginX);
y = WorldVisitor.toCellFloor(getWorld(), dragBeginY);
dragActorMoved = false;
repaint();
return false;
}
}
}
catch (IndexOutOfBoundsException e) {
}
catch (IllegalStateException e)
{
}
return true;
}
else
{
return false;
}
}
| Add an actor at the given pixel co-ordinates. The co-ordinates are translated
| into cell co-ordinates, and the actor is added at those cell co-ordinates, if they
| are within the world.
|
| @return true if the Actor was added into the world; false if the co-ordinates were
| outside the world.
|
@OnThread(Tag.Any)
public boolean addActorAtPixel(final Actor actor, int xPixel, int yPixel)
{
final World world = this.world;
final int x = WorldVisitor.toCellFloor(world, xPixel);
final int y = WorldVisitor.toCellFloor(world, yPixel);
if (x < WorldVisitor.getWidthInCells(world) && y < WorldVisitor.getHeightInCells(world)
&& x >= 0 && y >= 0) {
Simulation.getInstance().runLater(() -> {
world.addObject(actor, x, y);
Simulation.getInstance().paintRemote(true);
});
return true;
}
else {
return false;
}
}
@OnThread(Tag.Simulation)
protected void fireWorldCreatedEvent(World newWorld)
{
WorldEvent worldEvent = new WorldEvent(newWorld);
for (WorldListener worldListener : worldListeners)
{
worldListener.worldCreated(worldEvent);
}
}
@OnThread(Tag.Simulation)
public void fireWorldRemovedEvent(World discardedWorld)
{
WorldEvent worldEvent = new WorldEvent(discardedWorld);
for (WorldListener worldListener : worldListeners)
{
worldListener.worldRemoved(worldEvent);
}
}
| Add a worldListener to listen for when a worlds are created and removed.
| Events will be delivered on the Simulation thread.
|
| @param l Listener to add
|
@OnThread(Tag.Any)
public void addWorldListener(WorldListener l)
{
worldListeners.add(0, l);
}
| Used to indicate the start of a simulation round. For use in the
| collision checker. Called from the simulation thread.
|
| @see greenfoot.collision.CollisionChecker#startSequence()
|
private void startSequence()
{
World world = this.world;
if (world != null) {
WorldVisitor.startSequence(world);
mousePollingManager.newActStarted();
}
}
| Completes the current drag if it is the given drag ID
|
@OnThread(Tag.Any)
public void finishDrag(int dragId)
{
Simulation.getInstance().runLater(() -> {
if (this.dragId == dragId)
{
if (dragActorMoved)
{
int ax = ActorVisitor.getX(dragActor);
int ay = ActorVisitor.getY(dragActor);
ActorVisitor.setLocationInPixels(dragActor, dragBeginX, dragBeginY);
dragActor.setLocation(ax, ay);
}
dragActor = null;
}
});
}
@OnThread(Tag.Simulation)
public void simulationChangedSync(SyncEvent e)
{
if (e == SyncEvent.NEW_ACT_ROUND) {
startSequence();
}
else if (e == SyncEvent.STARTED)
{
mousePollingManager.startedRunning();
}
}
@Override
public @OnThread(Tag.Any) void simulationChangedAsync(AsyncEvent e)
{
}
| Get the object bench if it exists. Otherwise return null.
|
public ObjectBenchInterface getObjectBench()
{
if (handlerDelegate instanceof ObjectBenchInterface) {
return (ObjectBenchInterface) handlerDelegate;
}
else {
return null;
}
}
| This is a hook called by the World whenever an actor gets added to it. When running in the IDE,
| this allows names to be assigned to the actors for interaction recording purposes.
|
public void objectAddedToWorld(Actor object)
{
handlerDelegate.objectAddedToWorld(object);
}
| Ask a question, with a given prompt, to the user (i.e. implement Greenfoot.ask()).
| Must be called on the simulation thread.
|
public String ask(String prompt)
{
return handlerDelegate.ask(prompt);
}
| Continue an actor drag operation. Can be called from any thread.
|
| @param dragId The identifier of the drag operation (must match the
| identifier used in {}link #startDrag}).
| @param x The x-coordinate in pixels of the drag location
| @param y The y-coordinate in pixels of the drag location
|
@OnThread(Tag.Any)
public void continueDragging(int dragId, int x, int y)
{
Simulation.getInstance().runLater(() -> {
if (dragId == this.dragId)
{
drag(dragActor, new Point(x, y));
Simulation.getInstance().paintRemote(true);
}
});
}
| The simulation had some user code which threw an exception
| that was not caught by the user code.
|
public void notifyStoppedWithError()
{
handlerDelegate.notifyStoppedWithError();
}
| Repaint the world.
| @param forcePaint Force paint (ignore any optimisations to not paint frames too often, etc)
|
public void paint(boolean forcePaint)
{
handlerDelegate.paint(world, forcePaint);
}
| The focus has changed on the world display, so tell the keyboard manager.
| @param focused true if gained focus, false if lost focus.
|
@OnThread(Tag.Any)
public void worldFocusChanged(boolean focused)
{
if (focused)
{
keyboardManager.focusGained();
}
else
{
keyboardManager.focusLost();
}
}
| Notify that the world construction has completed.
|
public void finishedInitialisingWorld()
{
handlerDelegate.finishedInitialisingWorld();
}
}
top,
use,
map,
class WorldHandler
. initialise
. initialise
. getInstance
. WorldHandler
. discardWorld
. instantiateNewWorld
. setWorld
. objectAddedToWorld
. ask
. paint
. notifyStoppedWithError
. WorldHandler
. getKeyboardManager
. getMouseManager
. startDrag
. isDragging
. getObject
. mouseExited
. run
. repaint
. repaintAndWait
. instantiateNewWorld
. setInitialisingWorld
. discardWorld
. setWorld
. getTopMostActorAt
. getTranslatedX
. getTranslatedY
. worldChanged
. worldInstantiationError
. getWorld
. hasWorld
. drop
. if
. if
. drag
. addActorAtPixel
. fireWorldCreatedEvent
. fireWorldRemovedEvent
. addWorldListener
. startSequence
. finishDrag
. simulationChangedSync
. simulationChangedAsync
. getObjectBench
. objectAddedToWorld
. ask
. continueDragging
. notifyStoppedWithError
. paint
. worldFocusChanged
. finishedInitialisingWorld
780 neLoCode
+ 92 LoComm