2 * Jexer - Java Text User Interface
4 * The MIT License (MIT)
6 * Copyright (C) 2019 Kevin Lamonte
8 * Permission is hereby granted, free of charge, to any person obtaining a
9 * copy of this software and associated documentation files (the "Software"),
10 * to deal in the Software without restriction, including without limitation
11 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 * and/or sell copies of the Software, and to permit persons to whom the
13 * Software is furnished to do so, subject to the following conditions:
15 * The above copyright notice and this permission notice shall be included in
16 * all copies or substantial portions of the Software.
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 * DEALINGS IN THE SOFTWARE.
26 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
29 package jexer
.backend
;
31 import java
.awt
.Color
;
32 import java
.awt
.Cursor
;
34 import java
.awt
.Graphics
;
35 import java
.awt
.Insets
;
36 import java
.awt
.Point
;
37 import java
.awt
.Toolkit
;
38 import java
.awt
.event
.ComponentListener
;
39 import java
.awt
.event
.KeyListener
;
40 import java
.awt
.event
.MouseListener
;
41 import java
.awt
.event
.MouseMotionListener
;
42 import java
.awt
.event
.MouseWheelListener
;
43 import java
.awt
.event
.WindowListener
;
44 import java
.awt
.image
.BufferedImage
;
45 import java
.awt
.image
.BufferStrategy
;
46 import java
.io
.IOException
;
47 import javax
.imageio
.ImageIO
;
48 import javax
.swing
.JComponent
;
49 import javax
.swing
.JFrame
;
50 import javax
.swing
.SwingUtilities
;
53 * Wrapper for integrating with Swing, because JFrame and JComponent have
54 * separate hierarchies.
56 class SwingComponent
{
58 // ------------------------------------------------------------------------
59 // Variables --------------------------------------------------------------
60 // ------------------------------------------------------------------------
63 * If true, use triple buffering when drawing to a JFrame.
65 public static boolean tripleBuffer
= true;
68 * The frame reference, if we are drawing to a JFrame.
73 * The component reference, if we are drawing to a JComponent.
75 private JComponent component
;
78 * An optional border in pixels to add.
80 private static final int BORDER
= 1;
83 * Adjustable Insets for this component. This has the effect of adding a
84 * black border around the drawing area.
86 Insets adjustInsets
= new Insets(BORDER
+ 5, BORDER
, BORDER
, BORDER
);
88 // ------------------------------------------------------------------------
89 // Constructors -----------------------------------------------------------
90 // ------------------------------------------------------------------------
93 * Construct using a JFrame.
95 * @param frame the JFrame to draw to
97 public SwingComponent(final JFrame frame
) {
103 * Construct using a JComponent.
105 * @param component the JComponent to draw to
107 public SwingComponent(final JComponent component
) {
108 this.component
= component
;
112 // ------------------------------------------------------------------------
113 // SwingComponent ---------------------------------------------------------
114 // ------------------------------------------------------------------------
117 * Get the BufferStrategy object needed for triple-buffering.
119 * @return the BufferStrategy
120 * @throws IllegalArgumentException if this function is called when
121 * not rendering to a JFrame
123 public BufferStrategy
getBufferStrategy() {
125 return frame
.getBufferStrategy();
127 throw new IllegalArgumentException("BufferStrategy not used " +
128 "for JComponent access");
133 * Get the JFrame reference.
135 * @return the frame, or null if this is drawing to a JComponent
137 public JFrame
getFrame() {
142 * Get the JComponent reference.
144 * @return the component, or null if this is drawing to a JFrame
146 public JComponent
getComponent() {
151 * Setup to render to an existing JComponent.
153 public void setupComponent() {
154 component
.setBackground(Color
.black
);
156 if (System
.getProperty("jexer.Swing.mouseImage") != null) {
157 component
.setCursor(getMouseImage());
158 } else if (System
.getProperty("jexer.Swing.mouseStyle") != null) {
159 component
.setCursor(getMouseCursor());
160 } else if (System
.getProperty("jexer.textMouse",
161 "true").equals("false")
163 // If the user has suppressed the text mouse, don't kill the X11
165 component
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
167 // Kill the X11 cursor
168 // Transparent 16 x 16 pixel cursor image.
169 BufferedImage cursorImg
= new BufferedImage(16, 16,
170 BufferedImage
.TYPE_INT_ARGB
);
171 // Create a new blank cursor.
172 Cursor blankCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
173 cursorImg
, new Point(0, 0), "blank cursor");
174 component
.setCursor(blankCursor
);
177 // Be capable of seeing Tab / Shift-Tab
178 component
.setFocusTraversalKeysEnabled(false);
182 * Setup to render to an existing JFrame.
184 public void setupFrame() {
185 frame
.setTitle("Jexer Application");
186 frame
.setBackground(Color
.black
);
189 if (System
.getProperty("jexer.Swing.mouseImage") != null) {
190 frame
.setCursor(getMouseImage());
191 } else if (System
.getProperty("jexer.Swing.mouseStyle") != null) {
192 frame
.setCursor(getMouseCursor());
193 } else if (System
.getProperty("jexer.textMouse",
194 "true").equals("false")
196 // If the user has suppressed the text mouse, don't kill the X11
198 frame
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
200 // Kill the X11 cursor
201 // Transparent 16 x 16 pixel cursor image.
202 BufferedImage cursorImg
= new BufferedImage(16, 16,
203 BufferedImage
.TYPE_INT_ARGB
);
204 // Create a new blank cursor.
205 Cursor blankCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
206 cursorImg
, new Point(0, 0), "blank cursor");
207 frame
.setCursor(blankCursor
);
210 // Be capable of seeing Tab / Shift-Tab
211 frame
.setFocusTraversalKeysEnabled(false);
213 // Setup triple-buffering
215 frame
.setIgnoreRepaint(true);
216 frame
.createBufferStrategy(3);
221 * Load an image named in jexer.Swing.mouseImage as the mouse cursor.
222 * The image must be on the classpath.
226 private Cursor
getMouseImage() {
227 Cursor cursor
= Cursor
.getDefaultCursor();
228 String filename
= System
.getProperty("jexer.Swing.mouseImage");
229 assert (filename
!= null);
232 ClassLoader loader
= Thread
.currentThread().
233 getContextClassLoader();
235 java
.net
.URL url
= loader
.getResource(filename
);
237 // User named a file, but it's not on the classpath. Bail
242 BufferedImage cursorImage
= ImageIO
.read(url
);
243 java
.awt
.Dimension cursorSize
= Toolkit
.getDefaultToolkit().
245 cursorImage
.getWidth(), cursorImage
.getHeight());
247 cursor
= Toolkit
.getDefaultToolkit().createCustomCursor(cursorImage
,
248 new Point((int) Math
.min(cursorImage
.getWidth() / 2,
249 cursorSize
.getWidth() - 1),
250 (int) Math
.min(cursorImage
.getHeight() / 2,
251 cursorSize
.getHeight() - 1)),
253 } catch (IOException e
) {
261 * Get the appropriate mouse cursor based on jexer.Swing.mouseStyle.
265 private Cursor
getMouseCursor() {
266 Cursor cursor
= Cursor
.getDefaultCursor();
267 String style
= System
.getProperty("jexer.Swing.mouseStyle");
268 assert (style
!= null);
270 style
= style
.toLowerCase();
272 if (style
.equals("none")) {
273 // Transparent 16 x 16 pixel cursor image.
274 BufferedImage cursorImg
= new BufferedImage(16, 16,
275 BufferedImage
.TYPE_INT_ARGB
);
276 // Create a new blank cursor.
277 cursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
278 cursorImg
, new Point(0, 0), "blank cursor");
279 } else if (style
.equals("default")) {
280 cursor
= Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
);
281 } else if (style
.equals("hand")) {
282 cursor
= Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
);
283 } else if (style
.equals("text")) {
284 cursor
= Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
);
285 } else if (style
.equals("move")) {
286 cursor
= Cursor
.getPredefinedCursor(Cursor
.MOVE_CURSOR
);
287 } else if (style
.equals("crosshair")) {
288 cursor
= Cursor
.getPredefinedCursor(Cursor
.CROSSHAIR_CURSOR
);
295 * Set the window title.
297 * @param title the new title
299 public void setTitle(final String title
) {
301 frame
.setTitle(title
);
306 * Paints this component.
308 * @param g the graphics context to use for painting
310 public void paint(Graphics g
) {
319 * Repaints this component.
321 public void repaint() {
330 * Repaints the specified rectangle of this component.
332 * @param x the x coordinate
333 * @param y the y coordinate
334 * @param width the width
335 * @param height the height
337 public void repaint(int x
, int y
, int width
, int height
) {
339 frame
.repaint(x
, y
, width
, height
);
341 component
.repaint(x
, y
, width
, height
);
346 * If a border has been set on this component, returns the border's
347 * insets; otherwise calls super.getInsets.
349 * @return the value of the insets property
351 public Insets
getInsets() {
352 Insets swingInsets
= null;
354 swingInsets
= frame
.getInsets();
356 swingInsets
= component
.getInsets();
358 Insets result
= new Insets(swingInsets
.top
+ adjustInsets
.top
,
359 swingInsets
.left
+ adjustInsets
.left
,
360 swingInsets
.bottom
+ adjustInsets
.bottom
,
361 swingInsets
.right
+ adjustInsets
.right
);
366 * Returns the current width of this component.
368 * @return the current width of this component
370 public int getWidth() {
372 return frame
.getWidth();
374 return component
.getWidth();
379 * Returns the current height of this component.
381 * @return the current height of this component
383 public int getHeight() {
385 return frame
.getHeight();
387 return component
.getHeight();
392 * Gets the font of this component.
394 * @return this component's font; if a font has not been set for this
395 * component, the font of its parent is returned
397 public Font
getFont() {
399 return frame
.getFont();
401 return component
.getFont();
406 * Sets the font of this component.
408 * @param f the font to become this component's font; if this parameter
409 * is null then this component will inherit the font of its parent
411 public void setFont(final Font f
) {
415 component
.setFont(f
);
420 * Shows or hides this Window depending on the value of parameter b.
422 * @param b if true, make visible, else make invisible
424 public void setVisible(final boolean b
) {
428 component
.setVisible(b
);
433 * Creates a graphics context for this component. This method will return
434 * null if this component is currently not displayable.
436 * @return a graphics context for this component, or null if it has none
438 public Graphics
getGraphics() {
440 return frame
.getGraphics();
442 return component
.getGraphics();
447 * Releases all of the native screen resources used by this Window, its
448 * subcomponents, and all of its owned children. That is, the resources
449 * for these Components will be destroyed, any memory they consume will
450 * be returned to the OS, and they will be marked as undisplayable.
452 public void dispose() {
456 component
.getParent().remove(component
);
461 * Resize the component to match the font dimensions.
463 * @param width the new width in pixels
464 * @param height the new height in pixels
466 public void setDimensions(final int width
, final int height
) {
467 if (SwingUtilities
.isEventDispatchThread()) {
468 // We are in the Swing thread and can safely set the size.
470 // Figure out the thickness of borders and use that to set the
473 Insets insets
= getInsets();
474 frame
.setSize(width
+ insets
.left
+ insets
.right
,
475 height
+ insets
.top
+ insets
.bottom
);
477 Insets insets
= getInsets();
478 component
.setSize(width
+ insets
.left
+ insets
.right
,
479 height
+ insets
.top
+ insets
.bottom
);
484 SwingUtilities
.invokeLater(new Runnable() {
486 // Figure out the thickness of borders and use that to set
489 Insets insets
= getInsets();
490 frame
.setSize(width
+ insets
.left
+ insets
.right
,
491 height
+ insets
.top
+ insets
.bottom
);
493 Insets insets
= getInsets();
494 component
.setSize(width
+ insets
.left
+ insets
.right
,
495 height
+ insets
.top
+ insets
.bottom
);
502 * Adds the specified component listener to receive component events from
503 * this component. If listener l is null, no exception is thrown and no
504 * action is performed.
506 * @param l the component listener
508 public void addComponentListener(ComponentListener l
) {
510 frame
.addComponentListener(l
);
512 component
.addComponentListener(l
);
517 * Adds the specified key listener to receive key events from this
518 * component. If l is null, no exception is thrown and no action is
521 * @param l the key listener.
523 public void addKeyListener(KeyListener l
) {
525 frame
.addKeyListener(l
);
527 component
.addKeyListener(l
);
532 * Adds the specified mouse listener to receive mouse events from this
533 * component. If listener l is null, no exception is thrown and no action
536 * @param l the mouse listener
538 public void addMouseListener(MouseListener l
) {
540 frame
.addMouseListener(l
);
542 component
.addMouseListener(l
);
547 * Adds the specified mouse motion listener to receive mouse motion
548 * events from this component. If listener l is null, no exception is
549 * thrown and no action is performed.
551 * @param l the mouse motion listener
553 public void addMouseMotionListener(MouseMotionListener l
) {
555 frame
.addMouseMotionListener(l
);
557 component
.addMouseMotionListener(l
);
562 * Adds the specified mouse wheel listener to receive mouse wheel events
563 * from this component. Containers also receive mouse wheel events from
566 * @param l the mouse wheel listener
568 public void addMouseWheelListener(MouseWheelListener l
) {
570 frame
.addMouseWheelListener(l
);
572 component
.addMouseWheelListener(l
);
577 * Adds the specified window listener to receive window events from this
578 * window. If l is null, no exception is thrown and no action is
581 * @param l the window listener
583 public void addWindowListener(WindowListener l
) {
585 frame
.addWindowListener(l
);