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
= null;
88 // ------------------------------------------------------------------------
89 // Constructors -----------------------------------------------------------
90 // ------------------------------------------------------------------------
93 * Construct using a JFrame.
95 * @param frame the JFrame to draw to
97 public SwingComponent(final JFrame frame
) {
99 if (System
.getProperty("os.name").startsWith("Linux")) {
100 // On my Linux dev system, a Swing frame draws its contents just
101 // a little off. No idea why, but I've seen it on both Debian
102 // and Fedora with KDE. These adjustments to the adjustments
103 // seem to center it OK in the frame.
104 adjustInsets
= new Insets(BORDER
+ 5, BORDER
,
105 BORDER
- 3, BORDER
+ 2);
107 adjustInsets
= new Insets(BORDER
, BORDER
, BORDER
, BORDER
);
113 * Construct using a JComponent.
115 * @param component the JComponent to draw to
117 public SwingComponent(final JComponent component
) {
118 this.component
= component
;
119 adjustInsets
= new Insets(BORDER
, BORDER
, BORDER
, BORDER
);
123 // ------------------------------------------------------------------------
124 // SwingComponent ---------------------------------------------------------
125 // ------------------------------------------------------------------------
128 * Get the BufferStrategy object needed for triple-buffering.
130 * @return the BufferStrategy
131 * @throws IllegalArgumentException if this function is called when
132 * not rendering to a JFrame
134 public BufferStrategy
getBufferStrategy() {
136 return frame
.getBufferStrategy();
138 throw new IllegalArgumentException("BufferStrategy not used " +
139 "for JComponent access");
144 * Get the JFrame reference.
146 * @return the frame, or null if this is drawing to a JComponent
148 public JFrame
getFrame() {
153 * Get the JComponent reference.
155 * @return the component, or null if this is drawing to a JFrame
157 public JComponent
getComponent() {
162 * Setup to render to an existing JComponent.
164 public void setupComponent() {
165 component
.setBackground(Color
.black
);
167 if (System
.getProperty("jexer.Swing.mouseImage") != null) {
168 component
.setCursor(getMouseImage());
169 } else if (System
.getProperty("jexer.Swing.mouseStyle") != null) {
170 component
.setCursor(getMouseCursor());
171 } else if (System
.getProperty("jexer.textMouse",
172 "true").equals("false")
174 // If the user has suppressed the text mouse, don't kill the X11
176 component
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
178 // Kill the X11 cursor
179 // Transparent 16 x 16 pixel cursor image.
180 BufferedImage cursorImg
= new BufferedImage(16, 16,
181 BufferedImage
.TYPE_INT_ARGB
);
182 // Create a new blank cursor.
183 Cursor blankCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
184 cursorImg
, new Point(0, 0), "blank cursor");
185 component
.setCursor(blankCursor
);
188 // Be capable of seeing Tab / Shift-Tab
189 component
.setFocusTraversalKeysEnabled(false);
193 * Setup to render to an existing JFrame.
195 public void setupFrame() {
196 frame
.setTitle("Jexer Application");
197 frame
.setBackground(Color
.black
);
200 if (System
.getProperty("jexer.Swing.mouseImage") != null) {
201 frame
.setCursor(getMouseImage());
202 } else if (System
.getProperty("jexer.Swing.mouseStyle") != null) {
203 frame
.setCursor(getMouseCursor());
204 } else if (System
.getProperty("jexer.textMouse",
205 "true").equals("false")
207 // If the user has suppressed the text mouse, don't kill the X11
209 frame
.setCursor(Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
));
211 // Kill the X11 cursor
212 // Transparent 16 x 16 pixel cursor image.
213 BufferedImage cursorImg
= new BufferedImage(16, 16,
214 BufferedImage
.TYPE_INT_ARGB
);
215 // Create a new blank cursor.
216 Cursor blankCursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
217 cursorImg
, new Point(0, 0), "blank cursor");
218 frame
.setCursor(blankCursor
);
221 // Be capable of seeing Tab / Shift-Tab
222 frame
.setFocusTraversalKeysEnabled(false);
224 // Setup triple-buffering
226 frame
.setIgnoreRepaint(true);
227 frame
.createBufferStrategy(3);
232 * Load an image named in jexer.Swing.mouseImage as the mouse cursor.
233 * The image must be on the classpath.
237 private Cursor
getMouseImage() {
238 Cursor cursor
= Cursor
.getDefaultCursor();
239 String filename
= System
.getProperty("jexer.Swing.mouseImage");
240 assert (filename
!= null);
243 ClassLoader loader
= Thread
.currentThread().
244 getContextClassLoader();
246 java
.net
.URL url
= loader
.getResource(filename
);
248 // User named a file, but it's not on the classpath. Bail
253 BufferedImage cursorImage
= ImageIO
.read(url
);
254 java
.awt
.Dimension cursorSize
= Toolkit
.getDefaultToolkit().
256 cursorImage
.getWidth(), cursorImage
.getHeight());
258 cursor
= Toolkit
.getDefaultToolkit().createCustomCursor(cursorImage
,
259 new Point((int) Math
.min(cursorImage
.getWidth() / 2,
260 cursorSize
.getWidth() - 1),
261 (int) Math
.min(cursorImage
.getHeight() / 2,
262 cursorSize
.getHeight() - 1)),
264 } catch (IOException e
) {
272 * Get the appropriate mouse cursor based on jexer.Swing.mouseStyle.
276 private Cursor
getMouseCursor() {
277 Cursor cursor
= Cursor
.getDefaultCursor();
278 String style
= System
.getProperty("jexer.Swing.mouseStyle");
279 assert (style
!= null);
281 style
= style
.toLowerCase();
283 if (style
.equals("none")) {
284 // Transparent 16 x 16 pixel cursor image.
285 BufferedImage cursorImg
= new BufferedImage(16, 16,
286 BufferedImage
.TYPE_INT_ARGB
);
287 // Create a new blank cursor.
288 cursor
= Toolkit
.getDefaultToolkit().createCustomCursor(
289 cursorImg
, new Point(0, 0), "blank cursor");
290 } else if (style
.equals("default")) {
291 cursor
= Cursor
.getPredefinedCursor(Cursor
.DEFAULT_CURSOR
);
292 } else if (style
.equals("hand")) {
293 cursor
= Cursor
.getPredefinedCursor(Cursor
.HAND_CURSOR
);
294 } else if (style
.equals("text")) {
295 cursor
= Cursor
.getPredefinedCursor(Cursor
.TEXT_CURSOR
);
296 } else if (style
.equals("move")) {
297 cursor
= Cursor
.getPredefinedCursor(Cursor
.MOVE_CURSOR
);
298 } else if (style
.equals("crosshair")) {
299 cursor
= Cursor
.getPredefinedCursor(Cursor
.CROSSHAIR_CURSOR
);
306 * Set the window title.
308 * @param title the new title
310 public void setTitle(final String title
) {
312 frame
.setTitle(title
);
317 * Paints this component.
319 * @param g the graphics context to use for painting
321 public void paint(Graphics g
) {
330 * Repaints this component.
332 public void repaint() {
341 * Repaints the specified rectangle of this component.
343 * @param x the x coordinate
344 * @param y the y coordinate
345 * @param width the width
346 * @param height the height
348 public void repaint(int x
, int y
, int width
, int height
) {
350 frame
.repaint(x
, y
, width
, height
);
352 component
.repaint(x
, y
, width
, height
);
357 * If a border has been set on this component, returns the border's
358 * insets; otherwise calls super.getInsets.
360 * @return the value of the insets property
362 public Insets
getInsets() {
363 Insets swingInsets
= null;
365 swingInsets
= frame
.getInsets();
367 swingInsets
= component
.getInsets();
369 Insets result
= new Insets(swingInsets
.top
+ adjustInsets
.top
,
370 swingInsets
.left
+ adjustInsets
.left
,
371 swingInsets
.bottom
+ adjustInsets
.bottom
,
372 swingInsets
.right
+ adjustInsets
.right
);
377 * Returns the current width of this component.
379 * @return the current width of this component
381 public int getWidth() {
383 return frame
.getWidth();
385 return component
.getWidth();
390 * Returns the current height of this component.
392 * @return the current height of this component
394 public int getHeight() {
396 return frame
.getHeight();
398 return component
.getHeight();
403 * Gets the font of this component.
405 * @return this component's font; if a font has not been set for this
406 * component, the font of its parent is returned
408 public Font
getFont() {
410 return frame
.getFont();
412 return component
.getFont();
417 * Sets the font of this component.
419 * @param f the font to become this component's font; if this parameter
420 * is null then this component will inherit the font of its parent
422 public void setFont(final Font f
) {
426 component
.setFont(f
);
431 * Shows or hides this Window depending on the value of parameter b.
433 * @param b if true, make visible, else make invisible
435 public void setVisible(final boolean b
) {
439 component
.setVisible(b
);
444 * Creates a graphics context for this component. This method will return
445 * null if this component is currently not displayable.
447 * @return a graphics context for this component, or null if it has none
449 public Graphics
getGraphics() {
451 return frame
.getGraphics();
453 return component
.getGraphics();
458 * Releases all of the native screen resources used by this Window, its
459 * subcomponents, and all of its owned children. That is, the resources
460 * for these Components will be destroyed, any memory they consume will
461 * be returned to the OS, and they will be marked as undisplayable.
463 public void dispose() {
467 component
.getParent().remove(component
);
472 * Resize the component to match the font dimensions.
474 * @param width the new width in pixels
475 * @param height the new height in pixels
477 public void setDimensions(final int width
, final int height
) {
478 if (SwingUtilities
.isEventDispatchThread()) {
479 // We are in the Swing thread and can safely set the size.
481 // Figure out the thickness of borders and use that to set the
484 Insets insets
= getInsets();
485 frame
.setSize(width
+ insets
.left
+ insets
.right
,
486 height
+ insets
.top
+ insets
.bottom
);
488 Insets insets
= getInsets();
489 component
.setSize(width
+ insets
.left
+ insets
.right
,
490 height
+ insets
.top
+ insets
.bottom
);
495 SwingUtilities
.invokeLater(new Runnable() {
497 // Figure out the thickness of borders and use that to set
500 Insets insets
= getInsets();
501 frame
.setSize(width
+ insets
.left
+ insets
.right
,
502 height
+ insets
.top
+ insets
.bottom
);
504 Insets insets
= getInsets();
505 component
.setSize(width
+ insets
.left
+ insets
.right
,
506 height
+ insets
.top
+ insets
.bottom
);
513 * Adds the specified component listener to receive component events from
514 * this component. If listener l is null, no exception is thrown and no
515 * action is performed.
517 * @param l the component listener
519 public void addComponentListener(ComponentListener l
) {
521 frame
.addComponentListener(l
);
523 component
.addComponentListener(l
);
528 * Adds the specified key listener to receive key events from this
529 * component. If l is null, no exception is thrown and no action is
532 * @param l the key listener.
534 public void addKeyListener(KeyListener l
) {
536 frame
.addKeyListener(l
);
538 component
.addKeyListener(l
);
543 * Adds the specified mouse listener to receive mouse events from this
544 * component. If listener l is null, no exception is thrown and no action
547 * @param l the mouse listener
549 public void addMouseListener(MouseListener l
) {
551 frame
.addMouseListener(l
);
553 component
.addMouseListener(l
);
558 * Adds the specified mouse motion listener to receive mouse motion
559 * events from this component. If listener l is null, no exception is
560 * thrown and no action is performed.
562 * @param l the mouse motion listener
564 public void addMouseMotionListener(MouseMotionListener l
) {
566 frame
.addMouseMotionListener(l
);
568 component
.addMouseMotionListener(l
);
573 * Adds the specified mouse wheel listener to receive mouse wheel events
574 * from this component. Containers also receive mouse wheel events from
577 * @param l the mouse wheel listener
579 public void addMouseWheelListener(MouseWheelListener l
) {
581 frame
.addMouseWheelListener(l
);
583 component
.addMouseWheelListener(l
);
588 * Adds the specified window listener to receive window events from this
589 * window. If l is null, no exception is thrown and no action is
592 * @param l the window listener
594 public void addWindowListener(WindowListener l
) {
596 frame
.addWindowListener(l
);
601 * Requests that this Component get the input focus, if this Component's
602 * top-level ancestor is already the focused Window.
604 public void requestFocusInWindow() {
606 frame
.requestFocusInWindow();
608 component
.requestFocusInWindow();