JFrame in eine eigene Klasse ausgelagert

This commit is contained in:
ngb
2022-07-21 22:01:38 +02:00
parent 5a27e18634
commit 04506f6e9c
2 changed files with 266 additions and 175 deletions

View File

@@ -1,6 +1,237 @@
package schule.ngb.zm; package schule.ngb.zm;
// TODO: Auslagern des Frames in diese Klasse (Trennung Swing-GUI/Canvas, Zeichenmaschine und Drawing-Thread) import schule.ngb.zm.util.Log;
public class Zeichenfenster { import schule.ngb.zm.util.Validator;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.logging.Level;
public class Zeichenfenster extends JFrame {
public static final void setLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch( Exception ex ) {
LOG.error(ex, "Couldn't set the look and feel: %s", ex.getMessage());
}
}
public static final GraphicsDevice getGraphicsDevice() {
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
// das Zeichenfenster dort zu zentrieren.
// TODO: (ngb) Wenn wir in BlueJ sind, sollte das Fenster neben dem Editor öffnen.
java.awt.Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] devices = environment.getScreenDevices();
GraphicsDevice displayDevice = null;
for( GraphicsDevice gd : devices ) {
if( gd.getDefaultConfiguration().getBounds().contains(mouseLoc) ) {
displayDevice = gd;
break;
}
}
// Keinen passenden Bildschirm gefunden. Wir nutzen den Standard.
if( displayDevice == null ) {
displayDevice = environment.getDefaultScreenDevice();
}
return displayDevice;
}
/**
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich
* dem Aktuellen sein, wenn das Fenster verschoben wurde).
*/
private GraphicsDevice displayDevice;
private int width, height;
/**
* Höhe und Breite der Zeichenmaschine, bevor sie mit
* {@link #setFullscreen(boolean)} in den Vollbild-Modus versetzt wurde.
* Wird verwendet, um die Fenstergröße wiederherzustellen, sobald der
* Vollbild-Modus verlassen wird.
*/
private int initialWidth, initialHeight;
/**
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)}
* in den Vollbildmodus versetzt wurde.
*/
private boolean fullscreen = false;
/**
* {@code KeyListener}, um den Vollbild-Modus mit der Escape-Taste zu
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
* hinzugefügt und entfernt.
*/
private KeyListener fullscreenExitListener = new KeyAdapter() {
@Override
public void keyPressed( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
// canvas.removeKeyListener(this);
setFullscreen(false);
}
}
};
private Zeichenleinwand canvas;
public Zeichenfenster( int width, int height, String title ) {
this(width, height, title, getGraphicsDevice());
}
public Zeichenfenster( int width, int height, String title, GraphicsDevice displayDevice ) {
super(Validator.requireNotNull(displayDevice).getDefaultConfiguration());
this.displayDevice = displayDevice;
// Konfiguration des Frames
this.setTitle(title);
this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// Das Icon des Fensters ändern
try {
if( Zeichenmaschine.MACOS ) {
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
Image icon = ImageIO.read(iconUrl);
// Dock Icon in macOS setzen
Taskbar taskbar = Taskbar.getTaskbar();
taskbar.setIconImage(icon);
} else {
ArrayList<Image> icons = new ArrayList<>(4);
for( int size = 32; size <= 512; size *= size ) {
icons.add(ImageIO.read(new File("icon_" + size + ".png")));
}
this.setIconImages(icons);
}
} catch( IllegalArgumentException | IOException e ) {
LOG.warn(e, "Could not load image icons: %s", e.getMessage());
} catch( SecurityException | UnsupportedOperationException macex ) {
// Dock Icon in macOS konnte nicht gesetzt werden :(
LOG.warn(macex, "Could not set dock icon: %s", macex.getMessage());
}
java.awt.Rectangle bounds = getScreenBounds();
this.width = bounds.width;
this.height = bounds.height;
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ...
this.pack();
this.setResizable(false);
this.centerFrame();
}
public void addCanvas( Zeichenleinwand canvas ) {
this.canvas = canvas;
this.add(canvas, 0);
this.width = canvas.getWidth();
this.height = canvas.getHeight();
this.pack();
}
public java.awt.Rectangle getScreenBounds() {
return displayDevice.getDefaultConfiguration().getBounds();
}
public java.awt.Rectangle getCanvasBounds() {
java.awt.Insets insets = getInsets();
return new java.awt.Rectangle(insets.top, insets.left, width, height);
}
/**
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
*/
public final void centerFrame() {
java.awt.Rectangle bounds = getScreenBounds();
this.setLocation(
(int) (bounds.x + (bounds.width - this.getWidth()) / 2.0),
(int) (bounds.y + (bounds.height - this.getHeight()) / 2.0)
);
}
@Override
public void setSize( int newWidth, int newHeight ) {
java.awt.Rectangle bounds = getScreenBounds();
java.awt.Insets insets = this.getInsets();
if( fullscreen ) {
initialWidth = Math.min(newWidth, bounds.width - insets.left - insets.right);
initialHeight = Math.min(newHeight, bounds.height - insets.top - insets.bottom);
setFullscreen(false);
} else {
width = Math.min(newWidth, bounds.width - insets.left - insets.right);
height = Math.min(newHeight, bounds.height - insets.top - insets.bottom);
if( canvas != null ) {
canvas.setSize(width, height);
}
// this.pack();
super.setSize(
width + insets.left + insets.right,
height + insets.top + insets.bottom
);
}
}
/**
* Aktiviert oder deaktiviert den Vollbildmodus für die Zeichenmaschine.
* <p>
* Der Vollbildmodus wird abhängig von {@code pEnable} entweder aktiviert
* oder deaktiviert. Wird die Zeichenmaschine in den Vollbildmodus versetzt,
* dann wird automatisch ein {@link KeyListener} aktiviert, der bei
* Betätigung der ESCAPE-Taste den Vollbildmodus verlässt. Wird der
* Vollbildmodus verlassen, wird die zuletzt gesetzte Fenstergröße
* wiederhergestellt.
*
* @param pEnable Wenn {@code true}, wird der Vollbildmodus aktiviert,
* ansonsten deaktiviert.
*/
public final void setFullscreen( boolean pEnable ) {
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html
if( displayDevice.isFullScreenSupported() ) {
if( pEnable && !fullscreen ) {
// Store width / height
initialWidth = getWidth();
initialHeight = getHeight();
// frame.setUndecorated(true);
this.setResizable(false); // Should be set anyway
displayDevice.setFullScreenWindow(this);
java.awt.Rectangle bounds = getScreenBounds();
this.setSize(bounds.width, bounds.height);
// Register ESC to exit fullscreen
canvas.addKeyListener(fullscreenExitListener);
fullscreen = true;
} else if( !pEnable && fullscreen ) {
fullscreen = false;
canvas.removeKeyListener(fullscreenExitListener);
displayDevice.setFullScreenWindow(null);
this.setSize(initialWidth, initialHeight);
this.pack();
// frame.setUndecorated(false);
}
}
}
public boolean isFullscreen() {
Window win = displayDevice.getFullScreenWindow();
return fullscreen && win.equals(this);
}
private static final Log LOG = Log.getLogger(Zeichenfenster.class);
} }

View File

@@ -9,7 +9,6 @@ import schule.ngb.zm.util.ImageLoader;
import schule.ngb.zm.util.Log; import schule.ngb.zm.util.Log;
import schule.ngb.zm.util.tasks.TaskRunner; import schule.ngb.zm.util.tasks.TaskRunner;
import javax.imageio.ImageIO;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.MouseInputListener; import javax.swing.event.MouseInputListener;
import java.awt.*; import java.awt.*;
@@ -17,10 +16,7 @@ import java.awt.event.*;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.logging.Level;
/** /**
* Hauptklasse der Zeichenmaschine. * Hauptklasse der Zeichenmaschine.
@@ -115,47 +111,7 @@ public class Zeichenmaschine extends Constants {
/** /**
* Das Zeichenfenster der Zeichenmaschine * Das Zeichenfenster der Zeichenmaschine
*/ */
private JFrame frame; private Zeichenfenster frame;
/**
* Die Graphics-Umgebung für das aktuelle Fenster.
*/
private GraphicsEnvironment environment;
/**
* Das Anzeigefenster, auf dem die ZM gestartet wurde (muss nicht gleich
* dem Aktuellen sein, wenn das Fenster verschoben wurde).
*/
private GraphicsDevice displayDevice;
/**
* Speichert, ob die Zeichenmaschine mit {@link #setFullscreen(boolean)}
* in den Vollbildmodus versetzt wurde.
*/
private boolean fullscreen = false;
/**
* Höhe und Breite der Zeichenmaschine, bevor sie mit
* {@link #setFullscreen(boolean)} in den Vollbild-Modus versetzt wurde.
* Wird verwendet, um die Fenstergröße wiederherzustellen, sobald der
* Vollbild-Modus verlassen wird.
*/
private int initialWidth, initialHeight;
/**
* {@code KeyListener}, um den Vollbild-Modus mit der Escape-Taste zu
* verlassen. Wird von {@link #setFullscreen(boolean)} automatisch
* hinzugefügt und entfernt.
*/
private KeyListener fullscreenExitListener = new KeyAdapter() {
@Override
public void keyPressed( KeyEvent e ) {
if( e.getKeyCode() == KeyEvent.VK_ESCAPE ) {
// canvas.removeKeyListener(this);
setFullscreen(false);
}
}
};
// Aktueller Zustand der Zeichenmaschine. // Aktueller Zustand der Zeichenmaschine.
@@ -319,69 +275,19 @@ public class Zeichenmaschine extends Constants {
public Zeichenmaschine( int width, int height, String title, boolean run_once ) { public Zeichenmaschine( int width, int height, String title, boolean run_once ) {
LOG.info("Starting " + APP_NAME + " " + APP_VERSION); LOG.info("Starting " + APP_NAME + " " + APP_VERSION);
// Setzen des "Look&Feel" // Erstellen des Zeichenfensters
try { frame = createFrame(width, height, title);
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch( Exception ex ) {
LOG.log(Level.SEVERE, "Error setting the look and feel: " + ex.getMessage(), ex);
}
// Wir suchen den Bildschirm, der derzeit den Mauszeiger enthält, um
// das Zeichenfenster dort zu zentrieren.
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;
}
}
// Keinen passenden Bildschirm gefunden. Wir nutzen den Standard.
if( displayDevice == null ) {
displayDevice = environment.getDefaultScreenDevice();
}
// Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen. // Wir kennen nun den Bildschirm und können die Breite / Höhe abrufen.
this.canvasWidth = width; this.canvasWidth = width;
this.canvasHeight = height; this.canvasHeight = height;
java.awt.Rectangle displayBounds = displayDevice.getDefaultConfiguration().getBounds(); java.awt.Rectangle displayBounds = frame.getScreenBounds();
this.screenWidth = (int) displayBounds.getWidth(); this.screenWidth = (int) displayBounds.getWidth();
this.screenHeight = (int) displayBounds.getHeight(); this.screenHeight = (int) displayBounds.getHeight();
// Erstellen des Zeichenfensters
frame = new JFrame(displayDevice.getDefaultConfiguration());
frame.setTitle(title);
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
// Das Icon des Fensters ändern
try {
if( MACOS ) {
//Image icon = ImageIO.read(new File("resources/icon_512.png"));
URL iconUrl = Zeichenmaschine.class.getResource("icon_512.png");
Image icon = ImageIO.read(iconUrl);
// Dock Icon in macOS setzen
Taskbar taskbar = Taskbar.getTaskbar();
taskbar.setIconImage(icon);
} else {
ArrayList<Image> icons = new ArrayList<>(4);
for( int size = 32; size <= 512; size *= size ) {
icons.add(ImageIO.read(new File("icon_" + size + ".png")));
}
frame.setIconImages(icons);
}
} catch( IllegalArgumentException | IOException e ) {
LOG.warn(e, "Could not load image icons. Some icons may be missing.");
} catch( SecurityException | UnsupportedOperationException macex ) {
// Dock Icon in macOS konnte nicht gesetzt werden :(
LOG.warn(macex, "Could not set dock icon.");
}
// Erstellen der Leinwand // Erstellen der Leinwand
canvas = new Zeichenleinwand(width, height); canvas = new Zeichenleinwand(width, height);
frame.add(canvas); frame.addCanvas(canvas);
// Die drei Standardebenen merken, für den einfachen Zugriff aus unterklassen. // Die drei Standardebenen merken, für den einfachen Zugriff aus unterklassen.
background = getBackgroundLayer(); background = getBackgroundLayer();
@@ -394,7 +300,7 @@ public class Zeichenmaschine extends Constants {
// Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen // Settings der Unterklasse aufrufen, falls das Fenster vor dem Öffnen
// verändert werden soll. // verändert werden soll.
// TODO: When to call settings? // TODO: (ngb) Wann sollte settings() aufgerufen werden?
settings(); settings();
// Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören. // Listener hinzufügen, um auf Maus- und Tastatureingaben zu hören.
@@ -424,11 +330,7 @@ public class Zeichenmaschine extends Constants {
} }
}); });
// Fenster zusammenbauen, auf dem Bildschirm zentrieren ... // Fesnter anzeigen
frame.pack();
frame.setResizable(false);
centerFrame();
// ... und anzeigen!
frame.setVisible(true); frame.setVisible(true);
// Nach dem Anzeigen kann die Pufferstrategie erstellt werden. // Nach dem Anzeigen kann die Pufferstrategie erstellt werden.
@@ -453,24 +355,21 @@ public class Zeichenmaschine extends Constants {
* @param title * @param title
*/ */
// TODO: Implement in conjunction with Zeichenfenster // TODO: Implement in conjunction with Zeichenfenster
private final Zeichenfenster createFrame( String title ) { private final Zeichenfenster createFrame( int width, int height, String title ) {
return null; // Setzen des Look&Feel
// TODO: (ngb) Ist das überhaupt notwendig oder eh schon der Default?
Zeichenfenster.setLookAndFeel();
Zeichenfenster frame = new Zeichenfenster(width, height, title);
return frame;
} }
/** /**
* Zentriert das Zeichenfenster auf dem aktuellen Bildschirm. * Zentriert das Zeichenfenster auf dem aktuellen Bildschirm.
*/ */
public final void centerFrame() { public final void centerFrame() {
// TODO: Center on current display (not main display by default) frame.centerFrame();
// 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)
);
} }
/** /**
@@ -487,37 +386,25 @@ public class Zeichenmaschine extends Constants {
* ansonsten deaktiviert. * ansonsten deaktiviert.
*/ */
public final void setFullscreen( boolean pEnable ) { public final void setFullscreen( boolean pEnable ) {
// See https://docs.oracle.com/javase/tutorial/extra/fullscreen/index.html if( pEnable && !frame.isFullscreen() ) {
if( displayDevice.isFullScreenSupported() ) { frame.setFullscreen(true);
if( pEnable && !fullscreen ) { if( frame.isFullscreen() )
// frame.setUndecorated(true);
frame.setResizable(false); // Should be set anyway
displayDevice.setFullScreenWindow(frame);
// Update width / height
initialWidth = canvasWidth;
initialHeight = canvasHeight;
changeSize(screenWidth, screenHeight);
// Register ESC as exit fullscreen
canvas.addKeyListener(fullscreenExitListener);
fullscreen = true;
fullscreenChanged(); fullscreenChanged();
} else if( !pEnable && fullscreen ) { } else if( !pEnable && frame.isFullscreen() ) {
fullscreen = false; frame.setFullscreen(false);
if( !frame.isFullscreen() )
canvas.removeKeyListener(fullscreenExitListener);
displayDevice.setFullScreenWindow(null);
changeSize(initialWidth, initialHeight);
frame.pack();
// frame.setUndecorated(false);
fullscreenChanged(); fullscreenChanged();
}
} }
} }
/**
* Prüft, ob das Zeichenfenster im Vollbild läuft.
*
* @return {@code true}, wenn sich das Fesnter im Vollbildmodus befindet,
* {@code false} sonst.
*/
public boolean isFullscreen() { public boolean isFullscreen() {
Window win = displayDevice.getFullScreenWindow(); return frame.isFullscreen();
return fullscreen && win != null;
} }
/** /**
@@ -671,28 +558,6 @@ public class Zeichenmaschine extends Constants {
} }
} }
/**
* Interne Methode um die Größe der Zeichenfläche zu ändern.
* <p>
* Die Methode berücksichtigt nicht den Zustand des Fensters (z.B.
* Vollbildmodus) und geht davon aus, dass die aufrufende Methode
* sichergestellt hat, dass eine Änderung der Größe der Zeichenfläche
* zulässig und sinnvoll ist.
*
* @param newWidth Neue Breite der Zeichenleinwand.
* @param newHeight Neue Höhe der Zeichenleinwand.
* @see #setSize(int, int)
* @see #setFullscreen(boolean)
*/
private void changeSize( int newWidth, int newHeight ) {
canvasWidth = Math.min(Math.max(newWidth, 100), screenWidth);
canvasHeight = Math.min(Math.max(newHeight, 100), screenHeight);
if( canvas != null ) {
canvas.setSize(canvasWidth, canvasHeight);
}
}
/** /**
* Ändert die Größe der {@link Zeichenleinwand}. * Ändert die Größe der {@link Zeichenleinwand}.
* *
@@ -701,16 +566,11 @@ public class Zeichenmaschine extends Constants {
* @see Zeichenleinwand#setSize(int, int) * @see Zeichenleinwand#setSize(int, int)
*/ */
public final void setSize( int width, int height ) { public final void setSize( int width, int height ) {
if( fullscreen ) { frame.setSize(width, height);
initialWidth = Math.min(Math.max(width, 100), screenWidth);
initialHeight = Math.min(Math.max(height, 100), screenHeight);
setFullscreen(false);
} else {
changeSize(width, height);
//frame.setSize(width, height); java.awt.Rectangle canvasBounds = frame.getCanvasBounds();
frame.pack(); canvasWidth = (int) canvasBounds.getWidth();
} canvasHeight = (int) canvasBounds.getHeight();
} }
/** /**