001package org.jdesktop.swingx.plaf.basic; 002 003import static java.awt.event.KeyEvent.VK_CAPS_LOCK; 004 005import java.awt.KeyEventDispatcher; 006import java.awt.KeyboardFocusManager; 007import java.awt.Toolkit; 008import java.awt.event.KeyEvent; 009 010import org.jdesktop.beans.AbstractBean; 011 012/** 013 * A class for determining the state of the {@link java.awt.event.KeyEvent.VK_CAPS_LOCK CAPS LOCK 014 * key}. It also supports notification when the locking state changes. 015 * <p> 016 * Although it is possible to use {@link Toolkit#getLockingKeyState(int)} to determine the current 017 * state of the CAPS LOCK key, that method is not guaranteed to work on all platforms. This class 018 * attempts to handle those shortfalls and provide an easy mechanism for listening to state changes. 019 * 020 * <pre> 021 * CapsLockSupport cls = CapsLockSupport.getInstance(); 022 * // for get the current state of the caps lock key 023 * boolean currentState = cls.isCapsLockEnabled(); 024 * // for listening to changes in the caps lock state 025 * cls.addPropertyChangeListener("capsLockEnabled", myListener); 026 * </pre> 027 * 028 * There is one special case to be aware of. If {@code CapsLockSupport} is not able to determine the 029 * state of the CAPS LOCK key, then {@link #isInitialized()} will return {@code false} until it is 030 * able to introspect a {@link KeyEvent} and determine the current locking state. If 031 * {@code CapsLockSupport} must use delayed initialization, it will fire a property change to notify 032 * listeners that it is now in an accurate state. 033 * 034 * @author kschaefer 035 */ 036public final class CapsLockSupport extends AbstractBean implements KeyEventDispatcher { 037 private boolean useToolkit; 038 private boolean capsLockeEnabled; 039 private boolean updateViaKeyEvent; 040 041 private static class SingletonHolder { 042 private static final CapsLockSupport INSTANCE = new CapsLockSupport(); 043 044 static { 045 KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(INSTANCE); 046 } 047 } 048 049 private CapsLockSupport() { 050 try { 051 capsLockeEnabled = Toolkit.getDefaultToolkit().getLockingKeyState(VK_CAPS_LOCK); 052 useToolkit = true; 053 updateViaKeyEvent = false; 054 } catch (UnsupportedOperationException e) { 055 capsLockeEnabled = false; 056 useToolkit = false; 057 updateViaKeyEvent = true; 058 } 059 } 060 061 /** 062 * Gets the only instance of {@code CapsLockSupport}. 063 * 064 * @return the {@code CapsLockSupport} instance 065 */ 066 public static CapsLockSupport getInstance() { 067 return SingletonHolder.INSTANCE; 068 } 069 070 /** 071 * Determines if {@code CapsLockSupport} is accurately synchronized with the state of the CAPS 072 * LOCK key. When not initialized, {@link #isCapsLockEnabled()} will always return {@code false} 073 * . {@code CapsLockSupport} will fail to initialize only if 074 * {@code Toolkit#getLockingKeyState(int)} throws an exception; in that case, it will initialize 075 * as soon as it receives a valid key event (that can be used to determine the current locking 076 * state). 077 * 078 * @return {@code true} if {@code CapsLockSupport} accurately knows the state of the CAPS LOCK 079 * key 080 */ 081 public boolean isInitialized() { 082 return useToolkit || (useToolkit ^ updateViaKeyEvent); 083 } 084 085 /** 086 * Determines the current state of the {@link java.awt.event.KeyEvent.VK_CAPS_LOCK CAPS LOCK key}. 087 * 088 * @return {@code true} if CAPS LOCK is enabled; {@code false} otherwise 089 */ 090 public boolean isCapsLockEnabled() { 091 if (useToolkit) { 092 try { 093 return Toolkit.getDefaultToolkit().getLockingKeyState(VK_CAPS_LOCK); 094 } catch (UnsupportedOperationException shouldNeverHappen) { 095 return capsLockeEnabled; 096 } 097 } 098 099 return capsLockeEnabled; 100 } 101 102 void setCapsLockEnabled(boolean capsLockEnabled) { 103 boolean oldValue = this.capsLockeEnabled; 104 this.capsLockeEnabled = capsLockEnabled; 105 firePropertyChange("capsLockEnabled", oldValue, this.capsLockeEnabled); //$NON-NLS-1$ 106 } 107 108 // updateViaKeyEvent is use to find the initial state of the CAPS LOCK key when the Toolkit does 109 // not support it 110 /** 111 * This is an implementation detail and should not be considered public. 112 */ 113 @Override 114 public boolean dispatchKeyEvent(KeyEvent e) { 115 if (e.getID() == KeyEvent.KEY_PRESSED) { 116 int keyCode = e.getKeyCode(); 117 118 if (keyCode == VK_CAPS_LOCK) { 119 if (!updateViaKeyEvent) { 120 if (useToolkit) { 121 try { 122 setCapsLockEnabled(Toolkit.getDefaultToolkit().getLockingKeyState(VK_CAPS_LOCK)); 123 } catch (UnsupportedOperationException shouldNeverHappen) { 124 setCapsLockEnabled(!capsLockeEnabled); 125 } 126 } else { 127 setCapsLockEnabled(!capsLockeEnabled); 128 } 129 } 130 } else if (updateViaKeyEvent && Character.isLetter(keyCode)) { 131 if (keyCode == e.getKeyChar()) { 132 capsLockeEnabled = !e.isShiftDown(); 133 } else { 134 capsLockeEnabled = e.isShiftDown(); 135 } 136 137 updateViaKeyEvent = false; 138 firePropertyChange("initialized", false, true); //$NON-NLS-1$ 139 } 140 } 141 142 return false; 143 } 144}