zeichenmaschine/src/schule/ngb/zm/Zeichenmaschine.java

601 lines
12 KiB
Java

package schule.ngb.zm;
import schule.ngb.zm.shapes.ShapesLayer;
import schule.ngb.zm.util.ImageLoader;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import java.awt.*;
import java.awt.Color;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.io.File;
import java.io.IOException;
/**
* Hauptklasse der Zeichenmaschine.
* <p>
* Projekte der Zeichenmaschine sollten als Unterklasse implementiert werden.
* Die Klasse übernimmt die Initialisierung eines Programmfensters und der
* nötigen Komponenten.
*/
public class Zeichenmaschine extends Constants implements MouseInputListener, KeyListener {
public static boolean IN_BLUEJ;
static {
IN_BLUEJ = System.getProperty("java.class.path").contains("bluej");
}
/*
* Attributes to be accessed by subclasses.
*/
protected Zeichenleinwand canvas;
protected ColorLayer background;
protected DrawingLayer drawing;
protected ShapesLayer shapes;
protected int tick = 0;
protected long runtime = 0L;
protected double delta = 0.0;
protected double mouseX = 0.0, mouseY = 0.0, pmouseX = 0.0, pmouseY = 0.0;
protected int width, height;
protected int screenWidth, screenHeight;
/*
* Interne Attribute zur Steuerung der Zeichenmaschine.
*/
private Object mouseLock = new Object();
private Object keyboardLock = new Object();
private JFrame frame;
private GraphicsEnvironment environment;
private GraphicsDevice displayDevice;
private boolean running = false, isDrawing = false, isUpdating = false;
private int framesPerSecond;
private Thread mainThread;
private boolean quitAfterTeardown = false, initialized = false;
public Zeichenmaschine() {
this(APP_NAME + " " + APP_VERSION);
}
public Zeichenmaschine( String title ) {
this(STD_WIDTH, STD_HEIGHT, title);
}
public Zeichenmaschine( int width, int height, String title ) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch( Exception e ) {
System.err.println("Error setting the look and feel: " + e.getMessage());
}
// Looking for the screen currently holding the mouse pointer
// that will be used as the screen device for this Zeichenmaschine
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = environment.getScreenDevices();
for( GraphicsDevice gd: devices ) {
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
displayDevice = gd;
break;
}
}
if( displayDevice == null ) {
displayDevice = environment.getDefaultScreenDevice();
}
this.width = width;
this.height = height;
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds();
this.screenWidth = (int)displayBounds.getWidth();
this.screenHeight = (int)displayBounds.getHeight();
frame = new JFrame(displayDevice.getDefaultConfiguration());
frame.setTitle(title);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
canvas = new Zeichenleinwand(width, height);
frame.add(canvas);
framesPerSecond = STD_FPS;
background = getBackgroundLayer();
drawing = getDrawingLayer();
shapes = getShapesLayer();
// TODO: When to call settings?
settings();
canvas.addMouseListener(this);
canvas.addMouseMotionListener(this);
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing( WindowEvent e ) {
if( running ) {
running = false;
quitAfterTeardown = true;
} else {
quit();
}
}
});
frame.pack();
frame.setResizable(false);
centerFrame();
frame.setVisible(true);
canvas.allocateBuffer();
running = true;
mainThread = new Zeichenthread();
mainThread.start();
//frame.requestFocusInWindow();
canvas.requestFocus();
initialized = true;
}
// TODO: Implement in conjunction with Zeichenfenster
public final void createFrame( String title ) {
}
public void show() {
if( !frame.isVisible() ) {
frame.setVisible(true);
}
}
public void hide() {
if( frame.isVisible() ) {
frame.setVisible(false);
}
}
public final void centerFrame() {
// TODO: Center on current display (not main display by default)
// TODO: Position at current BlueJ windows if IN_BLUEJ
//frame.setLocationRelativeTo(null);
//frame.setLocationRelativeTo(displayDevice.getFullScreenWindow());
java.awt.Rectangle bounds = displayDevice.getDefaultConfiguration().getBounds();
frame.setLocation(
(int)(bounds.x + (screenWidth-frame.getWidth())/2.0),
(int)(bounds.y + (screenHeight-frame.getHeight())/2.0)
);
}
public final void redraw() {
canvas.render();
//canvas.invalidate();
//frame.repaint();
//hide();
//show();
}
public final void stop() {
running = false;
}
public void quit() {
//quit(!IN_BLUEJ);
quit(true);
}
public void quit( boolean exit ) {
frame.setVisible(false);
canvas.dispose();
frame.dispose();
if( exit ) {
System.exit(0);
}
}
public final void setSize( int width, int height ) {
//frame.setSize(width, height);
if( canvas != null ) {
canvas.setSize(width, height);
}
this.width = width;
this.height = height;
frame.pack();
}
public final int getWidth() {
return width;
}
public final int getHeight() {
return height;
}
public final void setTitle( String pTitel ) {
frame.setTitle(pTitel);
}
public final Zeichenleinwand getCanvas() {
return canvas;
}
public final void addLayer( Layer layer ) {
canvas.addLayer(layer);
layer.setSize(getWidth(), getHeight());
}
public final ColorLayer getBackgroundLayer() {
ColorLayer layer = canvas.getLayer(ColorLayer.class);
if( layer == null ) {
layer = new ColorLayer(STD_BACKGROUND);
canvas.addLayer(0, layer);
}
return layer;
}
public final DrawingLayer getDrawingLayer() {
DrawingLayer layer = canvas.getLayer(DrawingLayer.class);
if( layer == null ) {
layer = new DrawingLayer(getWidth(), getHeight());
canvas.addLayer(1, layer);
}
return layer;
}
public final ShapesLayer getShapesLayer() {
ShapesLayer layer = canvas.getLayer(ShapesLayer.class);
if( layer == null ) {
layer = new ShapesLayer(getWidth(), getHeight());
canvas.addLayer(layer);
}
return layer;
}
public final int getFramesPerSecond() {
return framesPerSecond;
}
public final void setFramesPerSecond( int pFramesPerSecond ) {
framesPerSecond = pFramesPerSecond;
}
public void saveImage() {
JFileChooser jfc = new JFileChooser();
jfc.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
jfc.setMultiSelectionEnabled(false);
int status = jfc.showSaveDialog(frame);
if( status == JFileChooser.APPROVE_OPTION ) {
File outfile = jfc.getSelectedFile();
if( outfile.isDirectory() ) {
outfile = new File(outfile.getAbsolutePath() + File.separator + "zeichenmaschine.png");
}
saveImage(outfile.getAbsolutePath());
}
}
public void saveImage( String filepath ) {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
g.fillRect(0, 0, img.getWidth(), img.getHeight());
canvas.draw(g);
g.dispose();
try {
ImageLoader.saveImage(img, new File(filepath), true);
} catch ( IOException ex ) {
ex.printStackTrace();
}
}
public ImageLayer snapshot() {
BufferedImage img = new BufferedImage(canvas.getWidth(),canvas.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setColor(STD_BACKGROUND.getColor());
g.fillRect(0, 0, img.getWidth(), img.getHeight());
canvas.draw(g);
g.dispose();
/*
float factor = 0.8f;
float base = 255f * (1f - factor);
RescaleOp op = new RescaleOp(factor, base, null);
BufferedImage filteredImage
= new BufferedImage(img.getWidth(), img.getHeight(), img.getType());
op.filter(img, filteredImage);
*/
ImageLayer imgLayer = new ImageLayer(img);
if( canvas.getLayer(0) instanceof ColorLayer ) {
canvas.addLayer(1, imgLayer);
} else {
canvas.addLayer(0, imgLayer);
}
return imgLayer;
}
/*
* Methoden, die von Unterklassen überschrieben werden können / sollen.
*/
public void settings() {
}
public void setup() {
}
public void draw() {
}
public void teardown() {
}
public void update( double delta ) {
running = false;
}
public void delay( int ms ) {
if( ms <= 0 ) {
return;
}
long timer = 0L;
if( isDrawing ) {
// Immediately show the current drawing before waiting
// Measure the render time and subtract from the waiting ms
timer = System.nanoTime();
canvas.render();
timer = System.nanoTime() - timer;
}
try {
int sub = (int) Math.ceil(timer / 1000000.0);
if( sub >= ms ) {
return;
}
Thread.sleep(ms - sub, (int) (timer % 1000000L));
} catch( InterruptedException ex ) {
// Nothing
}
}
/*
* Mouse handling
*/
@Override
public void mouseClicked( MouseEvent e ) {
saveMousePosition(e.getPoint());
mouseClicked();
}
public void mouseClicked() {
}
@Override
public void mousePressed( MouseEvent e ) {
saveMousePosition(e.getPoint());
mousePressed();
}
public void mousePressed() {
}
@Override
public void mouseReleased( MouseEvent e ) {
saveMousePosition(e.getPoint());
mouseReleased();
}
public void mouseReleased() {
}
@Override
public void mouseEntered( MouseEvent e ) {
saveMousePosition(e.getPoint());
}
@Override
public void mouseExited( MouseEvent e ) {
saveMousePosition(e.getPoint());
}
@Override
public void mouseDragged( MouseEvent e ) {
saveMousePosition(e.getPoint());
mouseDragged();
}
public void mouseDragged() {
}
@Override
public void mouseMoved( MouseEvent e ) {
saveMousePosition(e.getPoint());
mouseMoved();
}
public void mouseMoved() {
}
private void saveMousePosition( Point pLocation ) {
//pmouseX = mouseX;
//pmouseY = mouseY;
/*synchronized(mouseLock) {
mouseX = pLocation.getX()-this.getRootPane().getX();
mouseY = pLocation.getY()-this.getRootPane().getY();
}*/
}
private void saveMousePosition() {
pmouseX = mouseX;
pmouseY = mouseY;
// TODO: Seems not right ...
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
java.awt.Point compLoc = canvas.getLocationOnScreen();
mouseX = mouseLoc.x - compLoc.x;
mouseY = mouseLoc.y - compLoc.y;
}
@Override
public void keyTyped( KeyEvent e ) {
keyTyped();
}
/*
* Keyboard handling
*/
public void keyTyped() {
}
@Override
public void keyPressed( KeyEvent e ) {
keyPressed();
}
public void keyPressed() {
}
@Override
public void keyReleased( KeyEvent e ) {
keyReleased();
}
public void keyReleased() {
}
class Zeichenthread extends Thread {
@Override
public final void run() {
// Wait for full initialization before start
while( !initialized ) {
delay(1);
}
// start of thread in ms
final long start = System.currentTimeMillis();
// current time in ns
long beforeTime = System.nanoTime();
// store for deltas
long overslept = 0L;
// internal counters for tick and runtime
int _tick = 0;
long _runtime = 0;
// public counters for access by subclasses
tick = 0;
runtime = 0;
// call setup of subclass
setup();
while( running ) {
// delta in seconds
delta = (System.nanoTime() - beforeTime) / 1000000000.0;
beforeTime = System.nanoTime();
saveMousePosition();
handleUpdate(delta);
handleDraw();
if( canvas != null ) {
canvas.render();
//canvas.invalidate();
//frame.repaint();
}
// delta time in ns
long afterTime = System.nanoTime();
long dt = afterTime - beforeTime;
long sleep = ((1000000000L / framesPerSecond) - dt) - overslept;
if( sleep > 0 ) {
try {
Thread.sleep(sleep / 1000000L, (int) (sleep % 1000000L));
} catch( InterruptedException e ) {
// Interrupt not relevant
}
overslept = (System.nanoTime() - afterTime) - sleep;
} else {
overslept = 0L;
}
_tick += 1;
_runtime = System.currentTimeMillis() - start;
tick = _tick;
runtime = _runtime;
}
teardown();
if( quitAfterTeardown ) {
quit();
}
}
public void handleUpdate( double delta ) {
if( isUpdating ) {
return;
}
isUpdating = true;
update(delta);
isUpdating = false;
}
public void handleDraw() {
if( isDrawing ) {
return;
}
isDrawing = true;
draw();
isDrawing = false;
}
}
}