001 /*
002 * Created on Jul 19, 2008
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005 * the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011 * specific language governing permissions and limitations under the License.
012 *
013 * Copyright @2008-2010 the original author or authors.
014 */
015 package org.fest.swing.core;
016
017 import static java.awt.AWTEvent.KEY_EVENT_MASK;
018 import static java.awt.event.InputEvent.CTRL_MASK;
019 import static java.awt.event.InputEvent.SHIFT_MASK;
020 import static java.awt.event.KeyEvent.KEY_PRESSED;
021 import static java.awt.event.KeyEvent.VK_A;
022 import static org.fest.swing.core.InputModifiers.modifiersMatch;
023 import static org.fest.swing.core.InputModifiers.unify;
024
025 import java.awt.AWTEvent;
026 import java.awt.Toolkit;
027 import java.awt.event.AWTEventListener;
028 import java.awt.event.KeyEvent;
029
030 import org.fest.util.VisibleForTesting;
031
032 /**
033 * Understands an escape valve for users to abort a running FEST-Swing test by pressing 'Ctrl + Shift + A'. The key
034 * combination to use to abort test is configurable through the method
035 * <code>{@link EmergencyAbortListener#keyCombination(KeyPressInfo)}</code>.
036 * <p>
037 * The following example shows to use this listener in a TestNG test:
038 * <pre>
039 * private EmergencyAbortListener listener;
040 *
041 *
042 * @BeforeMethod public void setUp() {
043 * // set up your test fixture.
044 * listener = EmergencyAbortListener.registerInToolkit();
045 * }
046 *
047 * @AfterMethod public void tearDown() {
048 * // clean up resources.
049 * listener.unregister();
050 * }
051 * </pre>
052 * </p>
053 * <p>
054 * Changing the default key combination for aborting test:
055 * <pre>
056 * listener = EmergencyAbortListener.registerInToolkit().{@link EmergencyAbortListener#keyCombination(KeyPressInfo) keyCombination}(key(VK_C).modifiers(SHIFT_MASK));
057 * </pre>
058 * </p>
059 *
060 * @author <a href="mailto:simeon.fitch@mseedsoft.com">Simeon H.K. Fitch</a>
061 * @author Alex Ruiz
062 */
063 public class EmergencyAbortListener implements AWTEventListener {
064
065 private static final long EVENT_MASK = KEY_EVENT_MASK;
066
067 private final Toolkit toolkit;
068 private final TestTerminator testTerminator;
069
070 private int keyCode = VK_A;
071 private int modifiers = unify(CTRL_MASK, SHIFT_MASK);
072
073 /**
074 * Attaches a new instance of <code>{@link EmergencyAbortListener}</code> in the given <code>{@link Toolkit}</code>.
075 * Any other instances of <code>EmergencyAbortListener</code> will be removed from the <code>Toolkit</code>.
076 * @return the created listener.
077 */
078 public static EmergencyAbortListener registerInToolkit() {
079 EmergencyAbortListener listener = new EmergencyAbortListener(Toolkit.getDefaultToolkit());
080 listener.register();
081 return listener;
082 }
083
084 @VisibleForTesting
085 EmergencyAbortListener(Toolkit toolkit) {
086 this(toolkit, new TestTerminator());
087 }
088
089 @VisibleForTesting
090 EmergencyAbortListener(Toolkit toolkit, TestTerminator testTerminator) {
091 this.testTerminator = testTerminator;
092 this.toolkit = toolkit;
093 }
094
095 @VisibleForTesting
096 void register() {
097 removePrevious();
098 toolkit.addAWTEventListener(this, EVENT_MASK);
099 }
100
101 private void removePrevious() {
102 AWTEventListener[] listeners = toolkit.getAWTEventListeners(EVENT_MASK);
103 for (AWTEventListener listener : listeners)
104 if (listener instanceof EmergencyAbortListener) toolkit.removeAWTEventListener(listener);
105 }
106
107 /**
108 * Sets the key combination that will terminate any FEST-Swing test. The default combination is 'Ctrl + Shift + A'.
109 * @param keyPressInfo contains information about the key code and modifiers.
110 * @return this listener.
111 * @throws NullPointerException if the <code>KeyPressInfo</code> is <code>null</code>.
112 */
113 public EmergencyAbortListener keyCombination(KeyPressInfo keyPressInfo) {
114 if (keyPressInfo == null) throw new NullPointerException("KeyPressInfo should not be null");
115 keyCode = keyPressInfo.keyCode();
116 modifiers = unify(keyPressInfo.modifiers());
117 return this;
118 }
119
120 /**
121 * Removes this listener from the <code>{@link Toolkit}</code> this listener is attached to.
122 */
123 public void unregister() {
124 toolkit.removeAWTEventListener(this);
125 }
126
127 /**
128 * Inspects key events for the key combination that should terminate any running FEST-Swing tests.
129 * @param event the event to inspect.
130 * @see java.awt.event.AWTEventListener#eventDispatched(java.awt.AWTEvent)
131 */
132 public void eventDispatched(AWTEvent event) {
133 if (event.getID() != KEY_PRESSED) return;
134 if (!(event instanceof KeyEvent)) return;
135 KeyEvent e = (KeyEvent) event;
136 if (e.getKeyCode() != keyCode) return;
137 if (!modifiersMatch(e, modifiers)) return;
138 testTerminator.terminateTests();
139 }
140
141 @VisibleForTesting
142 int keyCode() { return keyCode; }
143
144 @VisibleForTesting
145 int modifiers() { return modifiers; }
146 }