Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/kmcaster.git

Refactor hardware image switch states into Swing component

Author DaveJarvis <email>
Date 2020-07-19 12:49:40 GMT-0700
Commit 5e7b42516b23be9cf9c998d9a2312eed4870bfc9
Parent 3c4c71f
Delta 435 lines added, 351 lines removed, 84-line increase
src/main/com/whitemagicsoftware/kmcaster/EventFrame.java
-/*
- * Copyright 2020 White Magic Software, Ltd.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.whitemagicsoftware.kmcaster;
-
-import com.whitemagicsoftware.kmcaster.listeners.FrameDragListener;
-
-import javax.swing.*;
-import java.awt.*;
-import java.awt.font.TextLayout;
-import java.awt.geom.RoundRectangle2D;
-import java.util.HashMap;
-import java.util.Map;
-
-import static com.whitemagicsoftware.kmcaster.HardwareImages.state;
-import static com.whitemagicsoftware.kmcaster.HardwareSwitch.*;
-import static java.awt.Font.BOLD;
-import static java.lang.Boolean.parseBoolean;
-
-public class EventFrame extends JFrame {
- private static final float ARC = 8;
- private static final Dimension FRAME_DIMENSIONS = new Dimension( 484, 70 );
- private static final Color TRANSLUCENT = new Color( .2f, .2f, .2f, 0.5f );
- private static final Color TRANSPARENT = new Color( 0, 0, 0, 0 );
- private static final Color COLOUR_LABEL = new Color( 33, 33, 33 );
-
- private final HardwareImages mSwitches;
- private final Map<HardwareSwitch, ImageComponent> mSwitchViews =
- new HashMap<>();
-
- public EventFrame() {
- initWindowFrame();
- initWindowDragListener();
-
- final var dimensions = new Dimension( getWidth(), getHeight() - 10 );
- mSwitches = new HardwareImages( dimensions );
-
- final var mouseImage = mSwitches.get( state( MOUSE_LEFT, false ) );
- final var mouseComponent = createImageComponent( mouseImage );
-
- final var shiftUpImage = mSwitches.get( state( KEY_SHIFT, false ) );
- final var ctrlUpImage = mSwitches.get( state( KEY_CTRL, false ) );
- final var altUpImage = mSwitches.get( state( KEY_ALT, false ) );
- final var regularUpImage = mSwitches.get( state( KEY_REGULAR, false ) );
-
- final var shiftComponent = createImageComponent( shiftUpImage );
- final var ctrlComponent = createImageComponent( ctrlUpImage );
- final var altComponent = createImageComponent( altUpImage );
- final var regularComponent = createImageComponent( regularUpImage );
-
- final var panel = new JPanel();
- panel.setAlignmentX( CENTER_ALIGNMENT );
- panel.setBackground( TRANSLUCENT );
- panel.add( mouseComponent );
- panel.add( shiftComponent );
- panel.add( ctrlComponent );
- panel.add( altComponent );
- panel.add( regularComponent );
-
- final var content = getContentPane();
- final var layout = new BoxLayout( content, BoxLayout.Y_AXIS );
-
- content.setLayout( layout );
- content.add( panel );
-
- mSwitchViews.put( KEY_SHIFT, shiftComponent );
- mSwitchViews.put( KEY_CTRL, ctrlComponent );
- mSwitchViews.put( KEY_ALT, altComponent );
- mSwitchViews.put( KEY_REGULAR, regularComponent );
- }
-
- private void initWindowFrame() {
- setDefaultCloseOperation( EXIT_ON_CLOSE );
- setLocationRelativeTo( null );
- setUndecorated( true );
- setAlwaysOnTop( true );
- setBackground( TRANSPARENT );
- setSize( FRAME_DIMENSIONS );
- setShape( createShape() );
- }
-
- private void initWindowDragListener() {
- final var frameDragListener = new FrameDragListener( this );
- addMouseListener( frameDragListener );
- addMouseMotionListener( frameDragListener );
- }
-
- protected void updateSwitchState( final HardwareState keyState ) {
- final var image = mSwitches.get( keyState );
- final var component = mSwitchViews.get( keyState.getHardwareSwitch() );
-
- component.redraw( image );
- }
-
- protected void updateSwitchLabel(
- final HardwareState state, final String value ) {
- if( state.isModifier() ) {
- final var pressed = parseBoolean( value );
- System.out.println( "Modifier pressed: " + pressed );
- }
- else {
- final var component = mSwitchViews.get( state.getHardwareSwitch() );
- component.removeAll();
-
- if( !"false".equals( value ) ) {
- final var bounds = component.getBounds();
- final var label = labelFor( value, bounds );
-
- component.add( label );
- component.repaint();
- }
- }
- }
-
- /**
- * @param text The text
- * @param r The maximum width and height.
- * @return A label adjusted to the given dimensions.
- */
- private JLabel labelFor( final String text, final Rectangle r ) {
- final int width = (int) r.getWidth() + 1;
- final int height = (int) r.getHeight() + 1;
-
- final var label = new JLabel( text );
- final var font = new Font( "DejaVu Sans", BOLD, 12 );
- label.setFont( font );
- label.setSize( width, height );
- label.setForeground( COLOUR_LABEL );
-
- final var scaledFont = scaleFont( label.getText(), font, r );
- label.setFont( scaledFont );
-
- return label;
- }
-
- /**
- * Adjusts the given {@link Font}/{@link String} size such that it fits
- * within the bounds of the given {@link Rectangle}.
- *
- * @param text The text string to be rendered within the bounds of the
- * given {@link Rectangle}.
- * @param font The {@link Font} to use when rendering the text string.
- * @param dst The bounds for fitting the string.
- * @return A new {@link Font} instance that is guaranteed to write the given
- * string within the bounds of the given {@link Rectangle}.
- */
- private Font scaleFont(
- final String text, final Font font, final Rectangle dst ) {
- final var g = (Graphics2D) getGraphics();
- final var frc = g.getFontRenderContext();
- final var dstWidthPx = dst.getWidth();
- final var dstHeightPx = dst.getHeight();
-
- var minSizePt = 1f;
- var maxSizePt = 1000f;
- var scaledFont = font;
- float scaledPt = scaledFont.getSize();
-
- while( maxSizePt - minSizePt > 2 ) {
- final var layout = new TextLayout( text, scaledFont, frc );
- final float fontWidthPx = layout.getVisibleAdvance();
- final var metrics = scaledFont.getLineMetrics( text, frc );
- final float fontHeightPx = metrics.getHeight();
-
- if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
- maxSizePt = scaledPt;
- }
- else {
- minSizePt = scaledPt;
- }
-
- scaledPt = (minSizePt + maxSizePt) / 2;
- scaledFont = scaledFont.deriveFont( scaledPt );
- }
-
- return scaledFont;
- }
-
- private ImageComponent createImageComponent( final Image image ) {
- return new ImageComponent( image );
- }
-
- /**
- * Returns the shape for the application's window frame.
- *
- * @return A rounded rectangle.
- */
- private Shape createShape() {
- return new RoundRectangle2D.Double(
- 0, 0, getWidth(), getHeight(), ARC, ARC
- );
- }
-}
src/main/com/whitemagicsoftware/kmcaster/EventHandler.java
+/*
+ * Copyright 2020 White Magic Software, Ltd.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.whitemagicsoftware.kmcaster;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.font.TextLayout;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+import static com.whitemagicsoftware.kmcaster.HardwareState.ANY_KEY;
+import static java.awt.Font.BOLD;
+import static java.lang.Boolean.parseBoolean;
+
+public class EventHandler implements PropertyChangeListener {
+ private static final Font DEFAULT_FONT = new Font( "DejaVu Sans", BOLD, 12 );
+ private static final Color COLOUR_LABEL = new Color( 33, 33, 33 );
+
+ private final HardwareImages mHardwareImages;
+
+ public EventHandler( final HardwareImages hardwareImages ) {
+ mHardwareImages = hardwareImages;
+ }
+
+ /**
+ * Called when a hardware switch has changed state.
+ *
+ * @param e Contains the identifier for the switch, its previous value,
+ * and its new value.
+ */
+ @Override
+ public void propertyChange( final PropertyChangeEvent e ) {
+ final var switchName = e.getPropertyName();
+ final var switchValue = e.getNewValue().toString();
+
+ // True or false indicates a non-regular key was pressed.
+ final var context =
+ (!"false".equals( switchValue ) && !"true".equals( switchValue ))
+ ? ANY_KEY
+ : switchValue;
+
+ final var switchState = new HardwareState( switchName, context );
+ updateSwitchState( switchState );
+ updateSwitchLabel( switchState, switchValue );
+ }
+
+ protected void updateSwitchState( final HardwareState state ) {
+ final var component = mHardwareImages.get( state.getHardwareSwitch() );
+
+ component.setState( state );
+ }
+
+ protected void updateSwitchLabel(
+ final HardwareState state, final String value ) {
+ if( state.isModifier() ) {
+ //final var pressed = parseBoolean( value );
+ //System.out.println( "Modifier pressed: " + pressed );
+ }
+ else {
+ final var component = mHardwareImages.get( state.getHardwareSwitch() );
+ component.removeAll();
+
+ if( !"false".equals( value ) ) {
+ final var bounds = component.getBounds();
+ final var insets = component.getInsets();
+
+ bounds.x += insets.left;
+ bounds.y += insets.top;
+ bounds.width -= insets.right + insets.left;
+ bounds.height -= insets.bottom + insets.top;
+
+ final var label = createLabel( value, bounds, component.getGraphics() );
+
+ label.setLocation( insets.left, insets.top );
+
+ component.add( label );
+ component.repaint();
+ }
+ }
+ }
+
+ /**
+ * Creates a label that can fit within bounds defined by the given
+ * {@link Rectangle} using the given {@link Graphics} context for deriving
+ * the actualized font dimensions (for a particular font point size).
+ *
+ * @param text The label's text.
+ * @param r The label's maximum width and height.
+ * @param graphics The graphics context used to determine font scale (must
+ * be a {@link Graphics2D instance}).
+ * @return A label adjusted to the given dimensions.
+ */
+ private JLabel createLabel(
+ final String text, final Rectangle r, final Graphics graphics ) {
+ assert text != null;
+ assert r != null;
+ assert graphics != null;
+
+ final int width = (int) r.getWidth();
+ final int height = (int) r.getHeight();
+
+ final var label = new JLabel( text );
+ label.setFont( DEFAULT_FONT );
+ label.setSize( width, height );
+ label.setForeground( COLOUR_LABEL );
+ //label.setBorder( BorderFactory.createLineBorder( Color.RED ) );
+
+ final var scaledFont = scale( label, r, graphics );
+ label.setFont( scaledFont );
+
+ return label;
+ }
+
+ /**
+ * Adjusts the given {@link Font}/{@link String} size such that it fits
+ * within the bounds of the given {@link Rectangle}.
+ *
+ * @param label Contains the text and font to scale.
+ * @param dst The bounds for fitting the string.
+ * @param graphics The context for rendering the string.
+ * @return A new {@link Font} instance that is guaranteed to write the given
+ * string within the bounds of the given {@link Rectangle}.
+ */
+ private Font scale(
+ final JLabel label, final Rectangle dst, final Graphics graphics ) {
+ assert label != null;
+ assert dst != null;
+ assert graphics != null;
+
+ final var font = label.getFont();
+ final var text = label.getText();
+
+ final var frc = ((Graphics2D) graphics).getFontRenderContext();
+
+ final var dstWidthPx = dst.getWidth();
+ final var dstHeightPx = dst.getHeight();
+
+ var minSizePt = 1f;
+ var maxSizePt = 1000f;
+ var scaledFont = font;
+ float scaledPt = scaledFont.getSize();
+
+ while( maxSizePt - minSizePt > 2 ) {
+ scaledFont = scaledFont.deriveFont( scaledPt );
+
+// final var layout = new TextLayout( text, scaledFont, frc );
+// final float fontWidthPx = layout.getVisibleAdvance();
+ final var fm = graphics.getFontMetrics(scaledFont);
+ final float fontWidthPx = (float)fm.getStringBounds( text, graphics ).getWidth();
+
+ final var metrics = scaledFont.getLineMetrics( text, frc );
+ final float fontHeightPx = metrics.getHeight();
+
+ if( (fontWidthPx > dstWidthPx) || (fontHeightPx > dstHeightPx) ) {
+ maxSizePt = scaledPt;
+ }
+ else {
+ minSizePt = scaledPt;
+ }
+
+ scaledPt = (minSizePt + maxSizePt) / 2;
+ }
+
+ return scaledFont;
+ }
+}
src/main/com/whitemagicsoftware/kmcaster/HardwareComponent.java
+/*
+ * Copyright 2020 White Magic Software, Ltd.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * o Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * o Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.whitemagicsoftware.kmcaster;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Responsible for drawing an image based on a state; the state can be
+ * changed at any time.
+ *
+ * @param <S> The type of state associated with an image.
+ */
+public class HardwareComponent<S, I extends Image> extends JComponent {
+ private final static Insets INSET_PROJECTED =
+ new Insets( 3, 7, 6, 7 );
+
+ private final Map<S, I> mStateImages = new HashMap<>();
+
+ /**
+ * Active state.
+ */
+ private S mState;
+
+ /**
+ * Constructs a new {@link HardwareComponent} without an initial state. The
+ * initial state must be set by calling {@link #setState(Object)} before
+ * drawing the image.
+ */
+ public HardwareComponent() {
+ }
+
+ /**
+ * Associates a new (or existing) state with the given image. If the
+ * state already exists for the image, the image is updated for that
+ * state. After calling this method, the active state changes to the
+ * given state as a convenience.
+ *
+ * @param state The state to associate with an image.
+ * @param image The image to paint when the given state is selected.
+ */
+ public void put( final S state, final I image ) {
+ getStateImages().put( state, image );
+ setState( state );
+ }
+
+ @Override
+ public Dimension getPreferredSize() {
+ // Race-condition guard.
+ final var image = getActiveImage();
+
+ return new Dimension(
+ image.getWidth( null ), image.getHeight( null )
+ );
+ }
+
+ @Override
+ public Insets getInsets() {
+ return INSET_PROJECTED;
+ }
+
+ @Override
+ protected void paintComponent( final Graphics graphics ) {
+ super.paintComponent( graphics );
+
+ final var g = (Graphics2D) graphics.create();
+ g.drawImage( getActiveImage(), 0, 0, this );
+ }
+
+ /**
+ * Repaints this component by changing its mutable state. The new state
+ * must have been previously registered by caling {@link #put(Object, Image)}.
+ *
+ * @param state The new state.
+ */
+ public void setState( final S state ) {
+ assert state != null;
+ mState = state;
+
+ repaint();
+ }
+
+ private Image getActiveImage() {
+ return getStateImages().get( getState() );
+ }
+
+ private S getState() {
+ return mState;
+ }
+
+ private Map<S, I> getStateImages() {
+ return mStateImages;
+ }
+}
src/main/com/whitemagicsoftware/kmcaster/HardwareImages.java
private final static SvgRasterizer sRasterizer = new SvgRasterizer();
- private final Map<HardwareState, Image> mImages = new HashMap<>();
+ private final Map<HardwareSwitch, HardwareComponent<HardwareState, Image>>
+ mSwitches = new HashMap<>();
private final Dimension mDimension;
final var mouseReleased = mouseImage( "0" );
+ final var mouseStates = new HardwareComponent<HardwareState, Image>();
for( int i = 1; i <= 3; i++ ) {
final var s = Integer.toString( i );
- final var switchName = HardwareSwitch.valueFrom( "button " + s );
- mImages.put( state( switchName, true ), mouseImage( s ) );
- mImages.put( state( switchName, false ), mouseReleased );
+ mouseStates.put( state( MOUSE, s ), mouseImage( s ) );
}
- mImages.put( state( MOUSE_LR, true ), mouseImage( "1-3" ) );
- mImages.put( state( MOUSE_LR, false ), mouseReleased );
+ mouseStates.put( state( MOUSE, "1-3" ), mouseImage( "1-3" ) );
+ mouseStates.put( state( MOUSE, false ), mouseReleased );
+ mSwitches.put( MOUSE, mouseStates );
- mImages.put( state( KEY_ALT, true ), keyDnImage( "medium" ) );
- mImages.put( state( KEY_ALT, false ), keyUpImage( "medium" ) );
- mImages.put( state( KEY_CTRL, true ), keyDnImage( "medium" ) );
- mImages.put( state( KEY_CTRL, false ), keyUpImage( "medium" ) );
- mImages.put( state( KEY_SHIFT, true ), keyDnImage( "long" ) );
- mImages.put( state( KEY_SHIFT, false ), keyUpImage( "long" ) );
- mImages.put( state( KEY_REGULAR, ANY_KEY ), keyDnImage( "short" ) );
- mImages.put( state( KEY_REGULAR, false ), keyUpImage( "short" ) );
+ final var altStates = new HardwareComponent<HardwareState, Image>();
+ altStates.put( state( KEY_ALT, true ), keyDnImage( "medium" ) );
+ altStates.put( state( KEY_ALT, false ), keyUpImage( "medium" ) );
+ mSwitches.put( KEY_ALT, altStates );
+
+ final var ctrlStates = new HardwareComponent<HardwareState, Image>();
+ ctrlStates.put( state( KEY_CTRL, true ), keyDnImage( "medium" ) );
+ ctrlStates.put( state( KEY_CTRL, false ), keyUpImage( "medium" ) );
+ mSwitches.put( KEY_CTRL, ctrlStates );
+
+ final var shiftStates = new HardwareComponent<HardwareState, Image>();
+ shiftStates.put( state( KEY_SHIFT, true ), keyDnImage( "long" ) );
+ shiftStates.put( state( KEY_SHIFT, false ), keyUpImage( "long" ) );
+ mSwitches.put( KEY_SHIFT, shiftStates );
+
+ final var regularStates = new HardwareComponent<HardwareState, Image>();
+ regularStates.put( state( KEY_REGULAR, ANY_KEY ), keyDnImage( "short" ) );
+ regularStates.put( state( KEY_REGULAR, false ), keyUpImage( "short" ) );
+ mSwitches.put( KEY_REGULAR, regularStates );
}
- public Image get( final HardwareState state ) {
- return mImages.get( state );
+ public HardwareComponent<HardwareState, Image> get(
+ final HardwareSwitch hwSwitch ) {
+ return mSwitches.get( hwSwitch );
}
- public static HardwareState state(
+ private HardwareState state(
final HardwareSwitch name, final boolean state ) {
return state( name, Boolean.toString( state ) );
}
- public static HardwareState state(
+ private HardwareState state(
final HardwareSwitch name, final String state ) {
return new HardwareState( name, state );
final var diagram = sRasterizer.loadDiagram( resource );
final var scale = sRasterizer.calculateScale( diagram, mDimension );
- final var rasterized = sRasterizer.rasterize( diagram, mDimension );
+ final var image = sRasterizer.rasterize( diagram, mDimension );
- return rasterized;
+ // TODO: Scale insets.
+
+ return image;
} catch( final Exception ex ) {
rethrow( ex );
src/main/com/whitemagicsoftware/kmcaster/HardwareSwitch.java
/**
* Used for compile-time binding between change listeners input events.
+ * <p>
+ * The element declaration order dictates the on-screen order.
+ * </p>
*/
public enum HardwareSwitch {
- KEY_ALT( "alt", ALT_MASK ),
- KEY_CTRL( "ctrl", CTRL_MASK ),
+ MOUSE( "mouse" ),
KEY_SHIFT( "shift", SHIFT_MASK ),
- KEY_REGULAR( "regular" ),
- MOUSE_LEFT( "button 1" ),
- MOUSE_WHEEL( "button 2" ),
- MOUSE_RIGHT( "button 3" ),
- MOUSE_LR( "button 1-3" );
+ KEY_CTRL( "ctrl", CTRL_MASK ),
+ KEY_ALT( "alt", ALT_MASK ),
+ KEY_REGULAR( "regular" );
/**
*/
public boolean isModifier() {
- return this == KEY_ALT || this == KEY_CTRL || this == KEY_SHIFT;
+ return mMask != NO_MASK;
}
* Returns the switch name.
*
- * @return The switch name, nothing more.
+ * @return The switch name, not the enum name.
*/
@Override
src/main/com/whitemagicsoftware/kmcaster/ImageComponent.java
-/*
- * Copyright 2020 White Magic Software, Ltd.
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * o Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * o Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.whitemagicsoftware.kmcaster;
-
-import javax.swing.*;
-import java.awt.*;
-
-/**
- * Responsible for drawing an image, which can be changed at any time.
- */
-public class ImageComponent extends JComponent {
- /**
- * Mutable image.
- */
- private Image mImage;
-
- public ImageComponent( final Image image ) {
- assert image != null;
-
- mImage = image;
- }
-
- @Override
- public Dimension getPreferredSize() {
- // Race-condition guard.
- final var image = mImage;
-
- return new Dimension(
- image.getWidth( null ), image.getHeight( null )
- );
- }
-
- @Override
- protected void paintComponent( final Graphics graphics ) {
- super.paintComponent( graphics );
-
- final var g = (Graphics2D) graphics.create();
- g.drawImage( mImage, 0, 0, this );
- }
-
- /**
- * Repaints this component using the given image. This is a mutable
- * operation that changes the internal {@link Image} instance.
- *
- * @param image The new image to use for painting.
- */
- public void redraw( final Image image ) {
- assert image != null;
-
- mImage = image;
- repaint();
- }
-}
src/main/com/whitemagicsoftware/kmcaster/KmCaster.java
package com.whitemagicsoftware.kmcaster;
+import com.whitemagicsoftware.kmcaster.listeners.FrameDragListener;
import com.whitemagicsoftware.kmcaster.listeners.KeyboardListener;
import com.whitemagicsoftware.kmcaster.listeners.MouseListener;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;
-import java.beans.PropertyChangeEvent;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.geom.RoundRectangle2D;
import java.beans.PropertyChangeListener;
import java.util.logging.Level;
-import static com.whitemagicsoftware.kmcaster.HardwareState.ANY_KEY;
import static com.whitemagicsoftware.kmcaster.ui.FontLoader.initFonts;
import static java.util.logging.Logger.getLogger;
*/
@SuppressWarnings("unused")
-public class KmCaster extends EventFrame implements PropertyChangeListener {
+public class KmCaster extends JFrame {
+ private static final Dimension FRAME_DIMENSIONS = new Dimension( 484, 70 );
+ private static final Dimension HARDWARE_DIMENSIONS = new Dimension(
+ (int) FRAME_DIMENSIONS.getWidth(),
+ (int) (FRAME_DIMENSIONS.getHeight() - 10) );
+ private static final Color TRANSPARENT = new Color( 0, 0, 0, 0 );
+ private static final Color TRANSLUCENT = new Color( .2f, .2f, .2f, 0.5f );
+ private static final float ARC = 8;
+
/**
* Fastest typing speed in words per minute.
*/
private final static float TYPING_SPEED_WPM = 216f;
+
/**
* Fastest typing speed in words per second.
*/
private final static float TYPING_SPEED_WPS = TYPING_SPEED_WPM / 60f;
+
/**
* Fastest typing speed in characters per second.
*/
private final static float TYPING_SPEED_CPS = TYPING_SPEED_WPS * 5.1f;
+
/**
* Fastest typing speed in characters per millisecond, which will
* govern the speed that any pressed key remains visible before showing
* as released, even if the typist released the key sooner.
*/
private final static float TYPING_SPEED_CPMS = TYPING_SPEED_CPS / 1000;
+
+ private final HardwareImages mHardwareImages =
+ new HardwareImages( HARDWARE_DIMENSIONS );
+ private final EventHandler mEventHandler =
+ new EventHandler( mHardwareImages );
public KmCaster() {
}
- /**
- * Called when a hardware switch has changed state.
- *
- * @param e Contains the identifier for the switch, its previous value,
- * and its new value.
- */
- @Override
- public void propertyChange( final PropertyChangeEvent e ) {
- final var switchName = e.getPropertyName();
- final var switchValue = e.getNewValue().toString();
+ private void init() {
+ initWindowFrame();
+ initWindowContents();
+ initListeners();
+ pack();
+ setVisible( true );
+ }
- // True or false indicates a non-regular key was pressed.
- final var context =
- (!"false".equals( switchValue ) && !"true".equals( switchValue ))
- ? ANY_KEY
- : switchValue;
+ private void initWindowFrame() {
+ setDefaultCloseOperation( EXIT_ON_CLOSE );
+ setLocationRelativeTo( null );
+ setUndecorated( true );
+ setAlwaysOnTop( true );
+ setBackground( TRANSPARENT );
+ setSize( FRAME_DIMENSIONS );
+ setShape( createShape() );
+ }
- final var switchState = new HardwareState( e.getPropertyName(), context );
- updateSwitchState( switchState );
- updateSwitchLabel( switchState, switchValue );
+ private void initWindowContents() {
+ final var switchPanel = new JPanel();
+ switchPanel.setAlignmentX( CENTER_ALIGNMENT );
+ switchPanel.setBackground( TRANSLUCENT );
+
+ // Added using the enumerated type definition declaration order.
+ for( final var hwSwitch : HardwareSwitch.values() ) {
+ switchPanel.add( mHardwareImages.get( hwSwitch ) );
+ }
+
+ add( switchPanel );
}
private void initListeners() {
+ initWindowDragListener( this );
+ initMouseListener( getEventHandler() );
+ initKeyboardListener( getEventHandler() );
+ }
+
+ private void initWindowDragListener( final JFrame listener ) {
+ final var frameDragListener = new FrameDragListener( listener );
+ addMouseListener( frameDragListener );
+ addMouseMotionListener( frameDragListener );
+ }
+
+ private void initMouseListener( final PropertyChangeListener listener ) {
final MouseListener mouseEventListener = new MouseListener();
addNativeMouseListener( mouseEventListener );
addNativeMouseMotionListener( mouseEventListener );
addNativeMouseWheelListener( mouseEventListener );
+ }
+ private void initKeyboardListener( final PropertyChangeListener listener ) {
final KeyboardListener keyboardListener = new KeyboardListener();
addNativeKeyListener( keyboardListener );
- keyboardListener.addPropertyChangeListener( this );
+ keyboardListener.addPropertyChangeListener( listener );
+ }
+
+ /**
+ * Returns the shape for the application's window frame.
+ *
+ * @return A rounded rectangle.
+ */
+ private Shape createShape() {
+ return new RoundRectangle2D.Double(
+ 0, 0, getWidth(), getHeight(), ARC, ARC
+ );
+ }
+
+ private EventHandler getEventHandler() {
+ return mEventHandler;
}
final var kc = new KmCaster();
-
- invokeLater( () -> {
- kc.setVisible( true );
- kc.initListeners();
- } );
+ invokeLater( kc::init );
}
}