From 2b9f153b5863531add0461e0ae185f742d33eda4 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Thu, 2 May 2013 15:00:38 +0200 Subject: [PATCH 01/10] Allow creating observables from `AbstractButton`s. --- .../src/main/java/rx/SwingObservable.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 extensions/rxjava-swing/src/main/java/rx/SwingObservable.java diff --git a/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java b/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java new file mode 100644 index 00000000000..ee817be543f --- /dev/null +++ b/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -0,0 +1,94 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx; + +import static org.mockito.Mockito.*; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.AbstractButton; + +import org.junit.Test; +import org.mockito.Matchers; + +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public enum SwingObservable { ; // no instances + + public static Observable fromButtonAction(final AbstractButton button) { + return Observable.create(new Func1, Subscription>() { + @Override + public Subscription call(final Observer observer) { + final ActionListener listener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + observer.onNext(e); + } + }; + button.addActionListener(listener); + + return Subscriptions.create(new Action0() { + @Override + public void call() { + button.removeActionListener(listener); + } + }); + } + }); + } + + public static class UnitTest { + @Test + public void testObservingActionEvents() { + @SuppressWarnings("unchecked") + Action1 action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + final ActionEvent event = new ActionEvent(this, 1, "command"); + + class TestButton extends AbstractButton { + void testAction() { + fireActionPerformed(event); + } + } + + TestButton button = new TestButton(); + Subscription sub = fromButtonAction(button).subscribe(action, error, complete); + + verify(action, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + + button.testAction(); + verify(action, times(1)).call(Matchers.any()); + + button.testAction(); + verify(action, times(2)).call(Matchers.any()); + + sub.unsubscribe(); + button.testAction(); + verify(action, times(2)).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + } + } +} From 03e63818075b57705404d3e11ad99d8f161b05b2 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Thu, 2 May 2013 15:05:18 +0200 Subject: [PATCH 02/10] Refactored implementation out from SwingObservable, as there will probably be lots more methods in the long run. --- .../src/main/java/rx/SwingObservable.java | 71 +--------------- .../swing/sources/AbstractButtonSource.java | 85 +++++++++++++++++++ 2 files changed, 88 insertions(+), 68 deletions(-) create mode 100644 extensions/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java diff --git a/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java b/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java index ee817be543f..52cf9e63122 100644 --- a/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -15,80 +15,15 @@ */ package rx; -import static org.mockito.Mockito.*; - import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; import javax.swing.AbstractButton; -import org.junit.Test; -import org.mockito.Matchers; - -import rx.subscriptions.Subscriptions; -import rx.util.functions.Action0; -import rx.util.functions.Action1; -import rx.util.functions.Func1; +import rx.swing.sources.AbstractButtonSource; public enum SwingObservable { ; // no instances - public static Observable fromButtonAction(final AbstractButton button) { - return Observable.create(new Func1, Subscription>() { - @Override - public Subscription call(final Observer observer) { - final ActionListener listener = new ActionListener() { - @Override - public void actionPerformed(ActionEvent e) { - observer.onNext(e); - } - }; - button.addActionListener(listener); - - return Subscriptions.create(new Action0() { - @Override - public void call() { - button.removeActionListener(listener); - } - }); - } - }); - } - - public static class UnitTest { - @Test - public void testObservingActionEvents() { - @SuppressWarnings("unchecked") - Action1 action = mock(Action1.class); - @SuppressWarnings("unchecked") - Action1 error = mock(Action1.class); - Action0 complete = mock(Action0.class); - - final ActionEvent event = new ActionEvent(this, 1, "command"); - - class TestButton extends AbstractButton { - void testAction() { - fireActionPerformed(event); - } - } - - TestButton button = new TestButton(); - Subscription sub = fromButtonAction(button).subscribe(action, error, complete); - - verify(action, never()).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - - button.testAction(); - verify(action, times(1)).call(Matchers.any()); - - button.testAction(); - verify(action, times(2)).call(Matchers.any()); - - sub.unsubscribe(); - button.testAction(); - verify(action, times(2)).call(Matchers.any()); - verify(error, never()).call(Matchers.any()); - verify(complete, never()).call(); - } + public static Observable fromButtonAction(AbstractButton button) { + return AbstractButtonSource.fromActionOf(button); } } diff --git a/extensions/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/extensions/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java new file mode 100644 index 00000000000..fdb871691b1 --- /dev/null +++ b/extensions/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java @@ -0,0 +1,85 @@ +package rx.swing.sources; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.AbstractButton; + +import org.junit.Test; +import org.mockito.Matchers; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public enum AbstractButtonSource { ; // no instances + + public static Observable fromActionOf(final AbstractButton button) { + return Observable.create(new Func1, Subscription>() { + @Override + public Subscription call(final Observer observer) { + final ActionListener listener = new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + observer.onNext(e); + } + }; + button.addActionListener(listener); + + return Subscriptions.create(new Action0() { + @Override + public void call() { + button.removeActionListener(listener); + } + }); + } + }); + } + + public static class UnitTest { + @Test + public void testObservingActionEvents() { + @SuppressWarnings("unchecked") + Action1 action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + final ActionEvent event = new ActionEvent(this, 1, "command"); + + class TestButton extends AbstractButton { + void testAction() { + fireActionPerformed(event); + } + } + + TestButton button = new TestButton(); + Subscription sub = fromActionOf(button).subscribe(action, error, complete); + + verify(action, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + + button.testAction(); + verify(action, times(1)).call(Matchers.any()); + + button.testAction(); + verify(action, times(2)).call(Matchers.any()); + + sub.unsubscribe(); + button.testAction(); + verify(action, times(2)).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + } + } +} From 0dff07aaa96640277133f423ba2add966aa90ec5 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Thu, 2 May 2013 15:07:47 +0200 Subject: [PATCH 03/10] Added some api documentation. --- .../rxjava-swing/src/main/java/rx/SwingObservable.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java b/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java index 52cf9e63122..80db1847d65 100644 --- a/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -21,8 +21,18 @@ import rx.swing.sources.AbstractButtonSource; +/** + * Allows creating observables from various sources specific to Swing. + */ public enum SwingObservable { ; // no instances + /** + * Creates an observable corresponding to a Swing button action. + * + * @param button + * The button to register the observable for. + * @return Observable of action events. + */ public static Observable fromButtonAction(AbstractButton button) { return AbstractButtonSource.fromActionOf(button); } From aa1b66666186a81203da872fc1cc96808ca4abae Mon Sep 17 00:00:00 2001 From: jmhofer Date: Thu, 2 May 2013 19:20:06 +0200 Subject: [PATCH 04/10] Moved Swing observables to new subfolder `rxjava-contrib` after merge. --- .../rxjava-swing/src/main/java/rx/SwingObservable.java | 0 .../src/main/java/rx/swing/sources/AbstractButtonSource.java | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {extensions => rxjava-contrib}/rxjava-swing/src/main/java/rx/SwingObservable.java (100%) rename {extensions => rxjava-contrib}/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java (100%) diff --git a/extensions/rxjava-swing/src/main/java/rx/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java similarity index 100% rename from extensions/rxjava-swing/src/main/java/rx/SwingObservable.java rename to rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java diff --git a/extensions/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java similarity index 100% rename from extensions/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java rename to rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java From ac94e30d9b18b1b856b33285e444a3d33d2de0d5 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Thu, 2 May 2013 21:27:49 +0200 Subject: [PATCH 05/10] Added key events as source for Swing observables. --- .../src/main/java/rx/SwingObservable.java | 14 +++ .../swing/sources/AbstractButtonSource.java | 1 + .../java/rx/swing/sources/KeyEventSource.java | 95 +++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java index 80db1847d65..0469246fdae 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -16,10 +16,13 @@ package rx; import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; import javax.swing.AbstractButton; +import javax.swing.JComponent; import rx.swing.sources.AbstractButtonSource; +import rx.swing.sources.KeyEventSource; /** * Allows creating observables from various sources specific to Swing. @@ -36,4 +39,15 @@ public enum SwingObservable { ; // no instances public static Observable fromButtonAction(AbstractButton button) { return AbstractButtonSource.fromActionOf(button); } + + /** + * Creates an observable corresponding to raw key events. + * + * @param component + * The component to register the observable for. + * @return Observable of key events. + */ + public static Observable fromKeyEvents(JComponent component) { + return KeyEventSource.fromKeyEventsOf(component); + } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java index fdb871691b1..81b199c7009 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java @@ -56,6 +56,7 @@ public void testObservingActionEvents() { final ActionEvent event = new ActionEvent(this, 1, "command"); + @SuppressWarnings("serial") class TestButton extends AbstractButton { void testAction() { fireActionPerformed(event); diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java new file mode 100644 index 00000000000..c33fa27c796 --- /dev/null +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java @@ -0,0 +1,95 @@ +package rx.swing.sources; + +import static org.mockito.Mockito.*; + +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +import javax.swing.JComponent; +import javax.swing.JPanel; + +import org.junit.Test; +import org.mockito.Matchers; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Action1; +import rx.util.functions.Func1; + +public enum KeyEventSource { ; // no instances + + public static Observable fromKeyEventsOf(final JComponent component) { + return Observable.create(new Func1, Subscription>() { + @Override + public Subscription call(final Observer observer) { + final KeyListener listener = new KeyListener() { + @Override + public void keyPressed(KeyEvent event) { + observer.onNext(event); + } + + @Override + public void keyReleased(KeyEvent event) { + observer.onNext(event); + } + + @Override + public void keyTyped(KeyEvent event) { + observer.onNext(event); + } + }; + component.addKeyListener(listener); + + return Subscriptions.create(new Action0() { + @Override + public void call() { + component.removeKeyListener(listener); + } + }); + } + }); + } + + public static class UnitTest { + @Test + public void testObservingActionEvents() { + @SuppressWarnings("unchecked") + Action1 action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + final KeyEvent event = mock(KeyEvent.class); + + JComponent comp = new JPanel(); + + Subscription sub = fromKeyEventsOf(comp).subscribe(action, error, complete); + + verify(action, never()).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + + fireKeyEvent(comp, event); + verify(action, times(1)).call(Matchers.any()); + + fireKeyEvent(comp, event); + verify(action, times(2)).call(Matchers.any()); + + sub.unsubscribe(); + fireKeyEvent(comp, event); + verify(action, times(2)).call(Matchers.any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + } + + private static void fireKeyEvent(JComponent component, KeyEvent event) { + for (KeyListener listener: component.getKeyListeners()) { + listener.keyTyped(event); + } + } + } +} From e55505123bcd2e9eb654d431c70d0c94bea46ad8 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Thu, 2 May 2013 21:37:04 +0200 Subject: [PATCH 06/10] Added filtering for key codes. --- .../src/main/java/rx/SwingObservable.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java index 0469246fdae..ba8577c309a 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -15,14 +15,18 @@ */ package rx; +import static rx.Observable.filter; + import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import java.util.Set; import javax.swing.AbstractButton; import javax.swing.JComponent; import rx.swing.sources.AbstractButtonSource; import rx.swing.sources.KeyEventSource; +import rx.util.functions.Func1; /** * Allows creating observables from various sources specific to Swing. @@ -50,4 +54,20 @@ public static Observable fromButtonAction(AbstractButton button) { public static Observable fromKeyEvents(JComponent component) { return KeyEventSource.fromKeyEventsOf(component); } + + /** + * Creates an observable corresponding to raw key events, restricted a set of given key codes. + * + * @param component + * The component to register the observable for. + * @return Observable of key events. + */ + public static Observable fromKeyEvents(JComponent component, final Set keyCodes) { + return filter(fromKeyEvents(component), new Func1() { + @Override + public Boolean call(KeyEvent event) { + return keyCodes.contains(event.getKeyCode()); + } + }); + } } From a74403ba792447c56d0a536df17934bdd4d05174 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Tue, 7 May 2013 14:02:07 +0200 Subject: [PATCH 07/10] Added observable for all currently pressed keys --- .../src/main/java/rx/SwingObservable.java | 11 ++ .../java/rx/swing/sources/KeyEventSource.java | 113 +++++++++++++++--- 2 files changed, 110 insertions(+), 14 deletions(-) diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java index ba8577c309a..4f53d2a82e4 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -70,4 +70,15 @@ public Boolean call(KeyEvent event) { } }); } + + /** + * Creates an observable that emits the set of all currently pressed keys each time + * this set changes. + * @param component + * The component to register the observable for. + * @return Observable of currently pressed keys. + */ + public static Observable> currentlyPressedKeys(JComponent component) { + return KeyEventSource.currentlyPressedKeysOf(component); + } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java index c33fa27c796..06ea9026f0e 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/KeyEventSource.java @@ -1,15 +1,34 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx.swing.sources; +import static java.util.Arrays.asList; import static org.mockito.Mockito.*; -import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import javax.swing.JComponent; import javax.swing.JPanel; import org.junit.Test; +import org.mockito.InOrder; import org.mockito.Matchers; import rx.Observable; @@ -19,6 +38,7 @@ import rx.util.functions.Action0; import rx.util.functions.Action1; import rx.util.functions.Func1; +import rx.util.functions.Func2; public enum KeyEventSource { ; // no instances @@ -54,40 +74,105 @@ public void call() { }); } + public static Observable> currentlyPressedKeysOf(JComponent component) { + return Observable.>scan(fromKeyEventsOf(component), new HashSet(), new Func2, KeyEvent, Set>() { + @Override + public Set call(Set pressedKeys, KeyEvent event) { + Set afterEvent = new HashSet(pressedKeys); + switch (event.getID()) { + case KeyEvent.KEY_PRESSED: + afterEvent.add(event.getKeyCode()); + break; + + case KeyEvent.KEY_RELEASED: + afterEvent.remove(event.getKeyCode()); + break; + + default: // nothing to do + } + return afterEvent; + } + }); + } + public static class UnitTest { + private JComponent comp = new JPanel(); + @Test - public void testObservingActionEvents() { + public void testObservingKeyEvents() { @SuppressWarnings("unchecked") - Action1 action = mock(Action1.class); + Action1 action = mock(Action1.class); @SuppressWarnings("unchecked") Action1 error = mock(Action1.class); Action0 complete = mock(Action0.class); final KeyEvent event = mock(KeyEvent.class); - JComponent comp = new JPanel(); - Subscription sub = fromKeyEventsOf(comp).subscribe(action, error, complete); - verify(action, never()).call(Matchers.any()); + verify(action, never()).call(Matchers.any()); verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); - fireKeyEvent(comp, event); - verify(action, times(1)).call(Matchers.any()); + fireKeyEvent(event); + verify(action, times(1)).call(Matchers.any()); - fireKeyEvent(comp, event); - verify(action, times(2)).call(Matchers.any()); + fireKeyEvent(event); + verify(action, times(2)).call(Matchers.any()); sub.unsubscribe(); - fireKeyEvent(comp, event); - verify(action, times(2)).call(Matchers.any()); + fireKeyEvent(event); + verify(action, times(2)).call(Matchers.any()); verify(error, never()).call(Matchers.any()); verify(complete, never()).call(); } - private static void fireKeyEvent(JComponent component, KeyEvent event) { - for (KeyListener listener: component.getKeyListeners()) { + @Test + public void testObservingPressedKeys() { + @SuppressWarnings("unchecked") + Action1> action = mock(Action1.class); + @SuppressWarnings("unchecked") + Action1 error = mock(Action1.class); + Action0 complete = mock(Action0.class); + + Subscription sub = currentlyPressedKeysOf(comp).subscribe(action, error, complete); + + InOrder inOrder = inOrder(action); + inOrder.verify(action, times(1)).call(Collections.emptySet()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + + fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1))); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + + fireKeyEvent(keyEvent(2, KeyEvent.KEY_PRESSED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1, 2))); + + fireKeyEvent(keyEvent(2, KeyEvent.KEY_RELEASED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1))); + + fireKeyEvent(keyEvent(3, KeyEvent.KEY_RELEASED)); + inOrder.verify(action, times(1)).call(new HashSet(asList(1))); + + fireKeyEvent(keyEvent(1, KeyEvent.KEY_RELEASED)); + inOrder.verify(action, times(1)).call(Collections.emptySet()); + + sub.unsubscribe(); + + fireKeyEvent(keyEvent(1, KeyEvent.KEY_PRESSED)); + inOrder.verify(action, never()).call(Matchers.>any()); + verify(error, never()).call(Matchers.any()); + verify(complete, never()).call(); + } + + private KeyEvent keyEvent(int keyCode, int id) { + return new KeyEvent(comp, id, -1L, 0, keyCode, ' '); + } + + private void fireKeyEvent(KeyEvent event) { + for (KeyListener listener: comp.getKeyListeners()) { listener.keyTyped(event); } } From e91bc7899e70f1ae9abffcad0121c3e122fd077a Mon Sep 17 00:00:00 2001 From: jmhofer Date: Tue, 7 May 2013 14:14:20 +0200 Subject: [PATCH 08/10] Added basic mouse event observables. --- .../src/main/java/rx/SwingObservable.java | 24 +++++ .../rx/swing/sources/MouseEventSource.java | 101 ++++++++++++++++++ 2 files changed, 125 insertions(+) create mode 100644 rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java index 4f53d2a82e4..06079645d4f 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java @@ -19,6 +19,7 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; import java.util.Set; import javax.swing.AbstractButton; @@ -26,6 +27,7 @@ import rx.swing.sources.AbstractButtonSource; import rx.swing.sources.KeyEventSource; +import rx.swing.sources.MouseEventSource; import rx.util.functions.Func1; /** @@ -81,4 +83,26 @@ public Boolean call(KeyEvent event) { public static Observable> currentlyPressedKeys(JComponent component) { return KeyEventSource.currentlyPressedKeysOf(component); } + + /** + * Creates an observable corresponding to raw mouse events (excluding mouse motion events). + * + * @param component + * The component to register the observable for. + * @return Observable of mouse events. + */ + public static Observable fromMouseEvents(JComponent component) { + return MouseEventSource.fromMouseEventsOf(component); + } + + /** + * Creates an observable corresponding to raw mouse motion events. + * + * @param component + * The component to register the observable for. + * @return Observable of mouse motion events. + */ + public static Observable fromMouseMotionEvents(JComponent component) { + return MouseEventSource.fromMouseMotionEventsOf(component); + } } diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java new file mode 100644 index 00000000000..1e0a9faf199 --- /dev/null +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/MouseEventSource.java @@ -0,0 +1,101 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package rx.swing.sources; + +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +import javax.swing.JComponent; + +import rx.Observable; +import rx.Observer; +import rx.Subscription; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action0; +import rx.util.functions.Func1; + +public enum MouseEventSource { ; // no instances + + public static Observable fromMouseEventsOf(final JComponent component) { + return Observable.create(new Func1, Subscription>() { + @Override + public Subscription call(final Observer observer) { + final MouseListener listener = new MouseListener() { + @Override + public void mouseClicked(MouseEvent event) { + observer.onNext(event); + } + + @Override + public void mousePressed(MouseEvent event) { + observer.onNext(event); + } + + @Override + public void mouseReleased(MouseEvent event) { + observer.onNext(event); + } + + @Override + public void mouseEntered(MouseEvent event) { + observer.onNext(event); + } + + @Override + public void mouseExited(MouseEvent event) { + observer.onNext(event); + } + }; + component.addMouseListener(listener); + + return Subscriptions.create(new Action0() { + @Override + public void call() { + component.removeMouseListener(listener); + } + }); + } + }); + } + + public static Observable fromMouseMotionEventsOf(final JComponent component) { + return Observable.create(new Func1, Subscription>() { + @Override + public Subscription call(final Observer observer) { + final MouseMotionListener listener = new MouseMotionListener() { + @Override + public void mouseDragged(MouseEvent event) { + observer.onNext(event); + } + + @Override + public void mouseMoved(MouseEvent event) { + observer.onNext(event); + } + }; + component.addMouseMotionListener(listener); + + return Subscriptions.create(new Action0() { + @Override + public void call() { + component.removeMouseMotionListener(listener); + } + }); + } + }); + } +} From 4e7fd3dc3b9f32fdfee97070e9a1f0e8e53b824c Mon Sep 17 00:00:00 2001 From: jmhofer Date: Tue, 7 May 2013 16:10:54 +0200 Subject: [PATCH 09/10] added a missing license header --- .../rx/swing/sources/AbstractButtonSource.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java index 81b199c7009..9e2d933cbeb 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/swing/sources/AbstractButtonSource.java @@ -1,3 +1,18 @@ +/** + * Copyright 2013 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package rx.swing.sources; import static org.mockito.Mockito.mock; From 46cfa7909bbf5db3cc57e956c7feb73bed6f4883 Mon Sep 17 00:00:00 2001 From: jmhofer Date: Tue, 7 May 2013 17:02:00 +0200 Subject: [PATCH 10/10] moved SwingObservable into the rx.observables package --- .../src/main/java/rx/{ => observables}/SwingObservable.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename rxjava-contrib/rxjava-swing/src/main/java/rx/{ => observables}/SwingObservable.java (98%) diff --git a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java similarity index 98% rename from rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java rename to rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java index 06079645d4f..90ddcbcfae3 100644 --- a/rxjava-contrib/rxjava-swing/src/main/java/rx/SwingObservable.java +++ b/rxjava-contrib/rxjava-swing/src/main/java/rx/observables/SwingObservable.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package rx; +package rx.observables; import static rx.Observable.filter; @@ -25,6 +25,7 @@ import javax.swing.AbstractButton; import javax.swing.JComponent; +import rx.Observable; import rx.swing.sources.AbstractButtonSource; import rx.swing.sources.KeyEventSource; import rx.swing.sources.MouseEventSource;