Dave Jarvis' Repositories

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

Fix release delay erasure issue

Author DaveJarvis <email>
Date 2020-07-31 17:32:16 GMT-0700
Commit 460f0b0fad9a5f589d9e176fdae66fab260283ec
Parent 09ce849
.idea/codeStyles/codeStyleConfig.xml
-
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+ </state>
+</component>
src/main/com/whitemagicsoftware/kmcaster/EventHandler.java
import com.whitemagicsoftware.kmcaster.ui.AutofitLabel;
+import com.whitemagicsoftware.kmcaster.ui.ResetTimer;
import com.whitemagicsoftware.kmcaster.util.ConsecutiveEventCounter;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
+import java.util.HashMap;
import java.util.Map;
-import java.util.Optional;
import static com.whitemagicsoftware.kmcaster.HardwareState.SWITCH_PRESSED;
import static com.whitemagicsoftware.kmcaster.HardwareState.SWITCH_RELEASED;
-import static com.whitemagicsoftware.kmcaster.HardwareSwitch.*;
+import static com.whitemagicsoftware.kmcaster.HardwareSwitch.KEY_REGULAR;
+import static com.whitemagicsoftware.kmcaster.LabelConfig.*;
import static com.whitemagicsoftware.kmcaster.ui.Constants.*;
import static java.awt.Toolkit.getDefaultToolkit;
-import static javax.swing.SwingConstants.*;
import static javax.swing.SwingUtilities.invokeLater;
/**
* Responsible for controlling the application state between the events
* and the view.
*/
public final class EventHandler implements PropertyChangeListener {
-
- /**
- * Used for initializing the {@link AutofitLabel} instances.
- */
- private enum LabelConfig {
- LABEL_SHIFT( KEY_SHIFT, CENTER, CENTER ),
- LABEL_CTRL( KEY_CTRL, CENTER, CENTER ),
- LABEL_ALT( KEY_ALT, CENTER, CENTER ),
- LABEL_REGULAR( KEY_REGULAR, CENTER, CENTER ),
- LABEL_REGULAR_NUM_MAIN( CENTER, CENTER ),
- LABEL_REGULAR_NUM_SUPERSCRIPT( TOP, LEFT ),
- LABEL_REGULAR_COUNTER( TOP, RIGHT );
-
- private final HardwareSwitch mHardwareSwitch;
- private final int mHorizontalAlign;
- private final int mVerticalAlign;
-
- LabelConfig( final int vAlign, final int hAlign ) {
- this( null, vAlign, hAlign );
- }
-
- LabelConfig(
- final HardwareSwitch hwSwitch, final int vAlign, final int hAlign ) {
- mHardwareSwitch = hwSwitch;
- mVerticalAlign = vAlign;
- mHorizontalAlign = hAlign;
- }
-
- private Optional<HardwareSwitch> getHardwareSwitch() {
- return Optional.ofNullable( mHardwareSwitch );
- }
-
- private int getHorizontalAlign() {
- return mHorizontalAlign;
- }
-
- private int getVerticalAlign() {
- return mVerticalAlign;
- }
-
- /**
- * Returns a blank space when no {@link HardwareSwitch} is assigned.
- *
- * @return The title case version of the hardware switch, or a space if
- * there is no direct correlation.
- */
- private String toTitleCase() {
- return mHardwareSwitch == null ? " " : mHardwareSwitch.toTitleCase();
- }
-
- /**
- * Returns the number of values in the enumeration.
- *
- * @return {@link #values()}.length.
- */
- private static int size() {
- return values().length;
- }
- }
/**
private final HardwareImages mHardwareImages;
private final AutofitLabel[] mLabels = new AutofitLabel[ LabelConfig.size() ];
+ private final Map<HardwareSwitch, ResetTimer> mTimers = new HashMap<>();
- public EventHandler( final HardwareImages hardwareImages ) {
+ public EventHandler(
+ final HardwareImages hardwareImages,
+ final int regularDelay,
+ final int modifierDelay ) {
mHardwareImages = hardwareImages;
() -> mHardwareImages.get( KEY_REGULAR ).add( label )
);
+ }
+
+ for( final var key : HardwareSwitch.keyboardSwitches() ) {
+ final var delay = key.isModifier() ? modifierDelay : regularDelay;
+
+ mTimers.put( key, new ResetTimer( delay ) );
}
}
final var switchState = new HardwareSwitchState(
hwSwitch, hwState, switchValue );
-
- updateSwitchState( switchState );
if( hwSwitch.isKeyboard() ) {
- updateSwitchLabel( switchState );
+ final var timer = getTimer( hwSwitch );
+
+ if( hwState == SWITCH_RELEASED ) {
+ timer.addActionListener( ( event ) -> {
+ updateSwitchState( switchState );
+ updateSwitchLabel( switchState );
+ } );
+ }
+ else {
+ timer.stop();
+ updateSwitchState( switchState );
+ updateSwitchLabel( switchState );
+ }
+ }
+ else {
+ updateSwitchState( switchState );
}
}
* @param state The key that has changed.
*/
- protected void updateSwitchLabel( final HardwareSwitchState state ) {
+ protected synchronized void updateSwitchLabel(
+ final HardwareSwitchState state ) {
final var hwState = state.getHardwareState();
-
- getLabel( LabelConfig.LABEL_REGULAR ).setVisible( false );
- getLabel( LabelConfig.LABEL_REGULAR_COUNTER ).setVisible( false );
if( state.isModifier() ) {
updateLabel( state );
mKeyCounter.reset();
}
else {
// Hide any previously displayed labels.
- final var main = getLabel( LabelConfig.LABEL_REGULAR_NUM_MAIN );
- final var sup = getLabel( LabelConfig.LABEL_REGULAR_NUM_SUPERSCRIPT );
+ getLabel( LABEL_REGULAR ).setVisible( false );
+
+ final var main = getLabel( LABEL_REGULAR_NUM_MAIN );
+ final var sup = getLabel( LABEL_REGULAR_NUM_SUPERSCRIPT );
+ final var tally = getLabel( LABEL_REGULAR_COUNTER );
+
+ tally.setVisible( false );
main.setVisible( false );
sup.setVisible( false );
// Track the consecutive key presses for this value.
if( mKeyCounter.apply( keyValue ) ) {
- final var tally = getLabel( LabelConfig.LABEL_REGULAR_COUNTER );
-
tally.setText( mKeyCounter.toString() );
tally.transform( .25f );
private AutofitLabel getLabel( final LabelConfig config ) {
return mLabels[ config.ordinal() ];
+ }
+
+ private ResetTimer getTimer( final HardwareSwitch hwSwitch ) {
+ return mTimers.get( hwSwitch );
}
}
src/main/com/whitemagicsoftware/kmcaster/KmCaster.java
final var appDimension = new Dimension( 1024 + mHeight, mHeight );
final var hardwareImages = new HardwareImages( appDimension );
- final var eventHandler = new EventHandler( hardwareImages );
+ final var eventHandler = new EventHandler(
+ hardwareImages, mDelayKeyRegular, mDelayKeyModifier );
initWindowFrame();
private void initKeyboardListener( final PropertyChangeListener listener ) {
- final KeyboardListener keyboardListener = new KeyboardListener(
- mDelayKeyRegular, mDelayKeyModifier
- );
+ final KeyboardListener keyboardListener = new KeyboardListener();
addNativeKeyListener( keyboardListener );
keyboardListener.addPropertyChangeListener( listener );
src/main/com/whitemagicsoftware/kmcaster/LabelConfig.java
+package com.whitemagicsoftware.kmcaster;
+
+import com.whitemagicsoftware.kmcaster.ui.AutofitLabel;
+
+import javax.swing.*;
+import java.util.Optional;
+
+import static com.whitemagicsoftware.kmcaster.HardwareSwitch.*;
+import static java.util.Optional.*;
+import static javax.swing.SwingConstants.*;
+
+/**
+ * Used for initializing the {@link AutofitLabel} instances.
+ */
+public enum LabelConfig {
+ LABEL_SHIFT( KEY_SHIFT, CENTER, CENTER ),
+ LABEL_CTRL( KEY_CTRL, CENTER, CENTER ),
+ LABEL_ALT( KEY_ALT, CENTER, CENTER ),
+ LABEL_REGULAR( KEY_REGULAR, CENTER, CENTER ),
+ LABEL_REGULAR_NUM_MAIN( CENTER, CENTER ),
+ LABEL_REGULAR_NUM_SUPERSCRIPT( TOP, LEFT ),
+ LABEL_REGULAR_COUNTER( TOP, RIGHT );
+
+ /**
+ * A value of {@code null} indicates multiple labels adorn the switch.
+ */
+ private final HardwareSwitch mHardwareSwitch;
+ private final int mHorizontalAlign;
+ private final int mVerticalAlign;
+
+ /**
+ * Creates a configuration for a multi-label hardware switch (such as
+ * a regular key).
+ *
+ * @param vAlign Vertical alignment.
+ * @param hAlign Horizontal alignment.
+ */
+ LabelConfig( final int vAlign, final int hAlign ) {
+ this( null, vAlign, hAlign );
+ }
+
+ /**
+ * Creates a configuration for a hardware switch that supports a single
+ * label (such as a modifier key).
+ *
+ * @param hwSwitch The switch to associate with the single label.
+ * @param vAlign Vertical alignment.
+ * @param hAlign Horizontal alignment.
+ */
+ LabelConfig(
+ final HardwareSwitch hwSwitch, final int vAlign, final int hAlign ) {
+ mHardwareSwitch = hwSwitch;
+ mVerticalAlign = vAlign;
+ mHorizontalAlign = hAlign;
+ }
+
+ /**
+ * If present, a return value indicates the switch has a single label and
+ * is associated with it.
+ *
+ * @return A switch associated with a label, or empty if multi-labelled.
+ */
+ Optional<HardwareSwitch> getHardwareSwitch() {
+ return ofNullable( mHardwareSwitch );
+ }
+
+ /**
+ * Returns an alignment value from {@link SwingConstants}.
+ *
+ * @return An indicator of left, right, or centered alignment.
+ */
+ int getHorizontalAlign() {
+ return mHorizontalAlign;
+ }
+
+ /**
+ * Returns an alignment value from {@link SwingConstants}.
+ *
+ * @return An indicator of top, bottom, or centered alignment.
+ */
+ int getVerticalAlign() {
+ return mVerticalAlign;
+ }
+
+ /**
+ * Returns a blank space when no {@link HardwareSwitch} is assigned.
+ *
+ * @return The title case version of the hardware switch, or a space if
+ * there is no direct correlation.
+ */
+ String toTitleCase() {
+ return mHardwareSwitch == null ? " " : mHardwareSwitch.toTitleCase();
+ }
+
+ /**
+ * Returns the number of values in the enumeration.
+ *
+ * @return {@link #values()}.length.
+ */
+ static int size() {
+ return values().length;
+ }
+}
src/main/com/whitemagicsoftware/kmcaster/listeners/KeyboardListener.java
import javax.swing.*;
import java.awt.event.ActionListener;
-import java.util.Deque;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.Map;
*/
private final Map<HardwareSwitch, Integer> mModifiers = new HashMap<>();
-
- /**
- * Informing the application of a key release is delayed so that the user
- * interface will give the end user a momentary glance of what key was
- * pressed before it is released. Without this delay the keys disappear
- * as fast as a typist can type, which can be too quick to read as individual
- * keystrokes.
- * <p>
- * Track the number of key release timers are running so that they can
- * all be stopped to prevent releasing the key when another key has been
- * pressed in the mean time.
- * </p>
- */
- private final Deque<Timer> mTimers = new LinkedList<>();
-
- private final int mDelayRegular;
- private final int mDelayModifier;
/**
* Creates a keyboard listener that publishes events when keys are either
* pressed or released. The constructor initializes all modifier keys to
* the released state because the native keyboard hook API does not offer
* a way to query what keys are currently pressed.
- *
- * @param delayRegular Milliseconds to wait before releasing a regular key.
- * @param delayModifier Milliseconds to wait before releasing a modifier key.
*/
- public KeyboardListener( final int delayRegular, final int delayModifier ) {
- mDelayRegular = delayRegular;
- mDelayModifier = delayModifier;
-
+ public KeyboardListener() {
for( final var key : modifierSwitches() ) {
mModifiers.put( key, 0 );
if( modifierKey == null ) {
- while( !mTimers.isEmpty() ) {
- mTimers.pop().stop();
- }
-
updateRegular( mRegularHeld, getDisplayText( e ) );
}
else {
updateModifier( modifierKey, 1 );
}
}
+
+ final Object mMutex = new Object();
@Override
public void nativeKeyReleased( final NativeKeyEvent e ) {
final var modifierKey = getModifierKey( e );
if( modifierKey == null ) {
- final var timer = delayedAction( mDelayRegular, ( action ) ->
- updateRegular( getDisplayText( e ), "" ) );
-
- mTimers.push( timer );
+ updateRegular( getDisplayText( e ), "" );
}
else {
- delayedAction( mDelayModifier, ( action ) ->
- updateModifier( modifierKey, -1 )
- );
+ updateModifier( modifierKey, -1 );
}
}
src/main/com/whitemagicsoftware/kmcaster/ui/AutofitLabel.java
var scaledFont = getFont();
- // Using the scaledPt as a relative max size reduces the iterations by two.
var scaledPt = scaledFont.getSize();
- var minSizePt = 1;
- var maxSizePt = scaledPt * 2;
+ var minSizePt = 4;
+ var maxSizePt = 100;
while( maxSizePt - minSizePt > 1 ) {
src/main/com/whitemagicsoftware/kmcaster/ui/ResetTimer.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.ui;
+
+import javax.swing.*;
+import javax.swing.event.EventListenerList;
+import java.awt.event.ActionListener;
+
+/**
+ * A one-off timer that can be reset to its initial delay.
+ */
+public class ResetTimer extends Timer {
+ /**
+ * @inheritDoc
+ */
+ public ResetTimer( final int delay ) {
+ super( delay, ( event ) -> {} );
+ setRepeats( false );
+ }
+
+ /**
+ * Limits the number of action listeners to one. The act of adding a
+ * listener resets the timer.
+ *
+ * @param listener The only listener to call when the time is up.
+ */
+ @Override
+ public void addActionListener( final ActionListener listener ) {
+ super.listenerList = new EventListenerList();
+ super.addActionListener( listener );
+ super.restart();
+ }
+}
Delta 220 lines added, 119 lines removed, 101-line increase