package org.expeditee.gio.swing; import java.awt.AlphaComposite; import java.awt.Component; import java.awt.Container; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsEnvironment; import java.awt.RenderingHints; import java.awt.Toolkit; import java.awt.event.ComponentListener; import java.awt.event.KeyListener; import java.awt.event.WindowListener; import java.awt.event.WindowStateListener; import java.awt.geom.AffineTransform; import java.awt.image.ImageObserver; import javax.swing.JFrame; import javax.swing.JLayeredPane; import javax.swing.JMenuBar; import javax.swing.JOptionPane; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; import javax.swing.TransferHandler; import org.expeditee.core.Clip; import org.expeditee.core.Colour; import org.expeditee.core.Cursor; import org.expeditee.core.Dimension; import org.expeditee.core.Fill; import org.expeditee.core.Font; import org.expeditee.core.GradientFill; import org.expeditee.core.Image; import org.expeditee.core.Point; import org.expeditee.core.Stroke; import org.expeditee.core.TextLayout; import org.expeditee.core.bounds.PolygonBounds; import org.expeditee.gio.GraphicsManager; import org.expeditee.gio.GraphicsSurfaceStack; import org.expeditee.gio.swing.SwingImageManager.BlockingImageObserver; import org.expeditee.gio.swing.SwingImageManager.SelfAnimatingImageObserver; import org.expeditee.gui.DisplayController; import org.expeditee.items.widgets.SwingWidget; import org.expeditee.items.widgets.Widget; // TODO: Make stack thread-safe public class SwingGraphicsManager extends GraphicsManager { private static SwingGraphicsManager _instance; public static SwingGraphicsManager getInstance() { if (_instance == null) { try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { _instance = new SwingGraphicsManager(); } }); } catch (Exception e) { System.err.println("Error while initialising GraphicsManager. Aborting..."); e.printStackTrace(System.err); System.exit(1); } // Block until the window is ready } return _instance; } private JFrame _jFrame; private GraphicsSurfaceStack _surfaceStack; private SwingGraphicsManager() { _jFrame = new JFrame() { private static final long serialVersionUID = 5179259234365906415L; // Override the paint() method so that Expeditee's graphics are drawn // when the window refreshes. @Override public void paint(Graphics g) { DisplayController.requestRefresh(false); } }; setWindowIcon(); /* * See Java bug ID 4016934. They say that window closed events are called once * the JFrame is disposed. */ _jFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // Expeditee handles its own repainting of AWT/Swing components RepaintManager.setCurrentManager(ExpediteeRepaintManager.getInstance()); // required to accept TAB key _jFrame.setFocusTraversalKeysEnabled(false); _jFrame.pack(); // Moved to here to work with JFXPanel // TODO: Is the above comment still relevant? cts16 _jFrame.getContentPane().setLayout(new AbsoluteLayout()); // Set visible must be just after DisplayIO.Init for the message box to be the // right size // TODO: Is the above comment still relevant? cts16 _jFrame.setVisible(true); // Create the surface stack _surfaceStack = new GraphicsSurfaceStack() { @Override public Graphics2D getSurfaceFromImage(Image image) { return SwingMiscManager.getIfUsingSwingImageManager().getImageGraphics(image); } @Override public void setSurfaceClip(Graphics2D surface, Clip clip) { if (surface == null) { return; } if (clip == null) { surface.setClip(null); return; } if (clip.isFullyClipped()) { return; } surface.setClip(SwingConversions.toSwingRectangle(clip.getBounds())); } }; refreshRootSurface(); } @Override protected GraphicsSurfaceStack getGraphicsSurfaceStack() { return _surfaceStack; } @Override public Dimension getScreenSize() { java.awt.Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); return SwingConversions.fromSwingDimension(size); } @Override public Point getWindowLocation() { return SwingConversions.fromSwingPoint(_jFrame.getContentPane().getLocationOnScreen()); } @Override public void setWindowLocation(Point p) { _jFrame.setLocation(p.getX(), p.getY()); } @Override public Dimension getWindowSize() { return SwingConversions.fromSwingDimension(_jFrame.getContentPane().getSize()); } @Override public void setWindowSize(Dimension d) { _jFrame.setSize(d.width, d.height); _jFrame.setPreferredSize(SwingConversions.toSwingDimension(d)); // Issue a command to graphics so that the JFrame draws itself white. _jFrame.getGraphics().clearRect(0, 0, d.width, d.height); } @Override public void requestFocus() { _jFrame.requestFocus(); } @Override public void setCompositeAlpha(float alpha) { _surfaceStack.getCurrentSurface().setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha)); } @Override public void setAntialiasing(boolean on) { setAntialiasing(_surfaceStack.getCurrentSurface(), on); } private void setAntialiasing(Graphics2D surface, boolean on) { if (surface == null) { return; } if (on) { surface.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } else { surface.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } } @Override public void setFont(Font font) { _surfaceStack.getCurrentSurface().setFont(SwingMiscManager.getIfUsingSwingFontManager().getInternalFont(font)); } @Override public void drawImage(Image image, Point topLeft, Dimension size, double angle, Point cropTopLeft, Dimension cropSize) { // Can't draw nothing if (image == null) { return; } // Can't draw nowhere if (topLeft == null) { return; } // If the cropped area is degenerate, abort if (cropSize != null && (cropSize.width <= 0 || cropSize.height <= 0)) { return; } // If the crop area is the entire image, pretend we are not cropping if (Point.ORIGIN.equals(cropTopLeft) && image.getSize().equals(cropSize)) { cropTopLeft = null; cropSize = null; } SwingImageManager manager = SwingMiscManager.getIfUsingSwingImageManager(); ImageObserver animator = null; if (image.isAnimated()) { animator = manager.getAnimator(image); if (animator != null) { ((SelfAnimatingImageObserver) animator).activate(); } } else { animator = new SwingImageManager.LoadCompletedImageObserver(); } // Default is the entire image Image croppedImage = image; // If we are cropping, do this now into a temporary image if (cropTopLeft != null && cropSize != null) { croppedImage = manager.createImage(cropSize.width, cropSize.height); manager.getInternalImage(croppedImage).getGraphics().drawImage(manager.getInternalImage(image), 0, 0, cropSize.width - 1, cropSize.height - 1, cropTopLeft.getX(), cropTopLeft.getY(), cropTopLeft.getX() + cropSize.width - 1, cropTopLeft.getY() + cropSize.height - 1, animator); animator = new BlockingImageObserver(); } // Transform the image AffineTransform tx = new AffineTransform(); if (size != null) { tx.scale(((double) size.width) / croppedImage.getWidth(), ((double) size.height) / croppedImage.getHeight()); } if (angle != 0.0) { tx.rotate(angle); } if (topLeft != null) { tx.translate(topLeft.getX(), topLeft.getY()); } // Draw the image to the current surface boolean drawn = _surfaceStack.getCurrentSurface().drawImage(manager.getInternalImage(croppedImage), tx, animator); // If the draw didn't succeed, try again after waiting for the image to load if (!drawn && animator instanceof BlockingImageObserver) { ((BlockingImageObserver) animator).attemptWait(); _surfaceStack.getCurrentSurface().drawImage(manager.getInternalImage(croppedImage), tx, animator); } } @Override public void drawRectangle(Point topLeft, Dimension size, double angle, Fill fill, Colour borderColour, Stroke borderStroke, Dimension cornerRadius) { Graphics2D g = (Graphics2D) _surfaceStack.getCurrentSurface().create(); if (angle != 0.0) { AffineTransform tx = new AffineTransform(); tx.rotate(angle); g.transform(tx); } if (fill != null) { if (fill instanceof GradientFill) { g.setPaint(SwingConversions.toSwingGradientPaint((GradientFill) fill)); } else { g.setColor(SwingConversions.toSwingColor(fill.getColour())); } if (cornerRadius != null) { g.fillRoundRect(topLeft.getX(), topLeft.getY(), size.width, size.height, cornerRadius.width, cornerRadius.height); } else { g.fillRect(topLeft.getX(), topLeft.getY(), size.width, size.height); } } if (borderColour != null && borderStroke != null) { g.setColor(SwingConversions.toSwingColor(borderColour)); g.setStroke(SwingConversions.toSwingStroke(borderStroke)); if (cornerRadius != null) { g.drawRoundRect(topLeft.getX(), topLeft.getY(), size.width, size.height, cornerRadius.width, cornerRadius.height); } else { g.drawRect(topLeft.getX(), topLeft.getY(), size.width, size.height); } } g.dispose(); } @Override public void drawOval(Point centre, Dimension diameters, double angle, Fill fill, Colour borderColour, Stroke borderStroke) { Graphics2D g = (Graphics2D) _surfaceStack.getCurrentSurface().create(); if (angle != 0.0) { AffineTransform tx = new AffineTransform(); tx.rotate(angle); g.transform(tx); } Point topLeft = new Point(centre.getX() - diameters.width / 2, centre.getY() - diameters.height / 2); if (fill != null) { if (fill instanceof GradientFill) { g.setPaint(SwingConversions.toSwingGradientPaint((GradientFill) fill)); } else { g.setColor(SwingConversions.toSwingColor(fill.getColour())); } g.fillOval(topLeft.getX(), topLeft.getY(), diameters.width, diameters.height); } if (borderColour != null && borderStroke != null) { g.setColor(SwingConversions.toSwingColor(borderColour)); g.setStroke(SwingConversions.toSwingStroke(borderStroke)); g.drawOval(topLeft.getX(), topLeft.getY(), diameters.width, diameters.height); } g.dispose(); } @Override public void drawPolygon(PolygonBounds points, Point offset, Dimension scale, double angle, Fill fill, Colour borderColour, Stroke borderStroke) { if (points == null || points.getPointCount() < 2) { return; } if (fill == null && (borderColour == null || borderStroke == null)) { return; } Graphics2D g = (Graphics2D) _surfaceStack.getCurrentSurface().create(); AffineTransform tx = new AffineTransform(); Point centre = points.getCentre(); tx.translate(-centre.getX(), -centre.getY()); if (angle != 0.0) { tx.rotate(angle); } if (scale != null) { double xScale = ((double) scale.width) / (points.getMaxX() - points.getMinX()); double yScale = ((double) scale.height) / (points.getMaxY() - points.getMinY()); tx.scale(xScale, yScale); } tx.translate(centre.getX(), centre.getY()); if (offset != null) { tx.translate(offset.getX(), offset.getY()); } g.transform(tx); if (fill != null) { if (fill instanceof GradientFill) { g.setPaint(SwingConversions.toSwingGradientPaint((GradientFill) fill)); } else { g.setColor(SwingConversions.toSwingColor(fill.getColour())); } g.fillPolygon(SwingConversions.toSwingPolygon(points)); } if (borderColour != null && borderStroke != null) { g.setColor(SwingConversions.toSwingColor(borderColour)); g.setStroke(SwingConversions.toSwingStroke(borderStroke)); if (points.isClosed()) { g.drawPolygon(SwingConversions.toSwingPolygon(points)); } else { int nPoints = points.getPointCount(); int[] xPoints = new int[nPoints]; int[] yPoints = new int[nPoints]; for (int i = 0; i < nPoints; i++) { Point point = points.getPoint(i); xPoints[i] = point.getX(); yPoints[i] = point.getY(); } g.drawPolyline(xPoints, yPoints, nPoints); } } g.dispose(); } @Override public void drawLine(int x1, int y1, int x2, int y2, Colour colour, Stroke stroke) { if (colour == null || stroke == null) { return; } Graphics2D g = (Graphics2D) _surfaceStack.getCurrentSurface().create(); g.setColor(SwingConversions.toSwingColor(colour)); g.setStroke(SwingConversions.toSwingStroke(stroke)); g.drawLine(x1, y1, x2, y2); g.dispose(); } @Override public void drawString(String string, Point position, Font font, Colour colour) { if (string == null || position == null || font == null || colour == null) { return; } Graphics2D g = (Graphics2D) _surfaceStack.getCurrentSurface().create(); g.setColor(SwingConversions.toSwingColor(colour)); g.setFont(SwingMiscManager.getIfUsingSwingFontManager().getInternalFont(font)); g.drawString(string, position.getX(), position.getY()); g.dispose(); } @Override public void drawTextLayout(TextLayout layout, Point position, Colour colour) { if (layout == null) { return; } SwingTextLayoutManager layoutManager = SwingMiscManager.getIfUsingSwingTextLayoutManager(); if (layoutManager == null) { drawString(layout.getLine(), position, layout.getFont(), colour); return; } java.awt.font.TextLayout swingLayout = layoutManager.getInternalLayout(layout); Graphics2D g = (Graphics2D) _surfaceStack.getCurrentSurface().create(); g.setColor(SwingConversions.toSwingColor(colour)); swingLayout.draw(g, position.getX(), position.getY()); } private Dimension _preFullscreenSize = null; private boolean _fullScreenTransitionPending = false; /** * Whether or not we are in the middle of transitioning to/from fullscreen. */ public boolean isFullscreenTransitionPending() { return _fullScreenTransitionPending; } /** * Should be called when the SwingInputManager is notified that the fullscreen * transition has finished. */ public void finishFullscreenTransition() { _fullScreenTransitionPending = false; } @Override public boolean canGoFullscreen() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().isFullScreenSupported(); } @Override public boolean isFullscreen() { return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice() .getFullScreenWindow() == _jFrame; } @Override public void goFullscreen() { if (!canGoFullscreen()) { System.err.println("Warning: GraphicsManager::goFullScreen() called when not available -- ignoring"); return; } _preFullscreenSize = getWindowSize(); _fullScreenTransitionPending = true; _jFrame.dispose(); _jFrame.setUndecorated(true); _jFrame.pack(); _jFrame.setVisible(true); GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(_jFrame); } @Override public void exitFullscreen() { _fullScreenTransitionPending = true; _jFrame.dispose(); _jFrame.setUndecorated(false); setWindowSize(_preFullscreenSize); GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(null); _jFrame.pack(); _jFrame.setVisible(true); } @Override public void setCursor(Cursor cursor) { if (cursor == null) { return; } java.awt.Cursor swingCursor; if (cursor.getType() == Cursor.CursorType.CUSTOM) { SwingImageManager imageManager = SwingMiscManager.getIfUsingSwingImageManager(); if (imageManager == null) { return; } swingCursor = Toolkit.getDefaultToolkit().createCustomCursor( imageManager.getInternalImage(cursor.getImage()), SwingConversions.toSwingPoint(cursor.getHotspot()), cursor.getName()); } else { swingCursor = new java.awt.Cursor(SwingConversions.toSwingCursorType(cursor.getType())); } _jFrame.setCursor(swingCursor); } @Override public Dimension getBestCursorSize(Dimension desiredSize) { Toolkit toolkit = Toolkit.getDefaultToolkit(); java.awt.Dimension best_cursor_dim = toolkit.getBestCursorSize(desiredSize.width, desiredSize.height); return SwingConversions.fromSwingDimension(best_cursor_dim); } @Override public void setWindowTitle(String title) { _jFrame.setTitle(title); } @Override public boolean showDialog(String title, String message) { int result = JOptionPane.showConfirmDialog(_jFrame, message, title, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); return result == JOptionPane.OK_OPTION; } @Override public Dimension getCurrentDrawingSurfaceSize() { Image currentImage = _surfaceStack.getCurrentImage(); if (currentImage != null) { return currentImage.getSize(); } else { return getWindowSize(); } } public void refreshRootSurface() { Graphics2D rootSurface = (Graphics2D) _jFrame.getContentPane().getGraphics(); setAntialiasing(rootSurface, true); final java.awt.Font rootSurfaceFont = rootSurface.getFont().deriveFont(40f); rootSurface.setFont(rootSurfaceFont); _surfaceStack.setRootSurface(rootSurface); } public JFrame getJFrame() { return _jFrame; } public Container getContentPane() { return _jFrame.getContentPane(); } public JLayeredPane getLayeredPane() { return _jFrame.getLayeredPane(); } public void setTransferHandler(TransferHandler newHandler) { _jFrame.setTransferHandler(newHandler); } public void addWindowListener(WindowListener l) { _jFrame.addWindowListener(l); } public void addWindowStateListener(WindowStateListener l) { _jFrame.addWindowStateListener(l); } public void addKeyListener(KeyListener l) { _jFrame.addKeyListener(l); } public void addComponentListener(ComponentListener l) { _jFrame.addComponentListener(l); } public void setGlassPane(Component glassPane) { _jFrame.setGlassPane(glassPane); } public Component getGlassPane() { return _jFrame.getGlassPane(); } public JMenuBar getJMenuBar() { return _jFrame.getJMenuBar(); } public FontMetrics getFontMetrics(java.awt.Font font) { return _jFrame.getFontMetrics(font); } public java.awt.Font getFont() { return _jFrame.getFont(); } public Graphics2D getCurrentSurface() { return _surfaceStack.getCurrentSurface(); } @Override public boolean addInteractiveWidget(Widget iw) { if (super.addInteractiveWidget(iw)) { _jFrame.getContentPane().add(((SwingWidget) iw).getComponent()); return true; } return false; } @Override public boolean removeInteractiveWidget(Widget iw) { if (super.removeInteractiveWidget(iw)) { _jFrame.getContentPane().remove(((SwingWidget) iw).getComponent()); return true; } return false; } @Override protected void setWindowIcon(Image image) { SwingImageManager imageManager = SwingMiscManager.getIfUsingSwingImageManager(); if (imageManager != null) { _jFrame.setIconImage(imageManager.getInternalImage(image)); } } @Override public int getFontHeight(final Font font) { final java.awt.Font awtFont = SwingFontManager.getInstance().getInternalFont(font); final FontMetrics fontMetrics = _jFrame.getGraphics().getFontMetrics(awtFont); return fontMetrics.getHeight(); } }