| Author | DaveJarvis <email> |
|---|---|
| Date | 2020-07-31 17:32:16 GMT-0700 |
| Commit | 460f0b0fad9a5f589d9e176fdae66fab260283ec |
| Parent | 09ce849 |
| - | ||
| +<component name="ProjectCodeStyleConfiguration"> | ||
| + <state> | ||
| + <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> | ||
| + </state> | ||
| +</component> |
| 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 ); | ||
| } | ||
| } | ||
| 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 ); | ||
| +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; | ||
| + } | ||
| +} | ||
| 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 ); | ||
| } | ||
| } | ||
| 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 ) { |
| +/* | ||
| + * 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 |
|---|