source: trunk/src/org/expeditee/gio/swing/MouseEventRouter.java@ 1187

Last change on this file since 1187 was 1187, checked in by bln4, 6 years ago

Updated how the SwingInputManager deals with popups so that the call to focus can be re-enabled.

File size: 14.1 KB
Line 
1/**
2 * MouseEventRouter.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.gio.swing;
20
21import java.awt.AWTEvent;
22import java.awt.Component;
23import java.awt.Container;
24import java.awt.Point;
25import java.awt.Toolkit;
26import java.awt.event.AWTEventListener;
27import java.awt.event.MouseEvent;
28import java.awt.event.MouseListener;
29import java.awt.event.MouseMotionListener;
30import java.awt.event.MouseWheelEvent;
31import java.awt.event.MouseWheelListener;
32import java.util.LinkedList;
33import java.util.List;
34
35import javax.swing.JComboBox;
36import javax.swing.JComponent;
37import javax.swing.JMenuBar;
38import javax.swing.SwingUtilities;
39
40import org.expeditee.gio.gesture.StandardGestureActions;
41import org.expeditee.gui.DisplayController;
42import org.expeditee.gui.FrameGraphics;
43import org.expeditee.gui.FrameUtils;
44import org.expeditee.gui.FreeItems;
45import org.expeditee.gui.PopupManager;
46import org.expeditee.items.Item;
47
48/**
49 * The gateway for mouse input; conditionally forwards mouse messages to swing
50 * components / expeditee frames for the Browser.
51 *
52 * Various mouse listeners can attach themselves here to listen to mouse events
53 * forwarded to expeditee .. this excludes widget-exclusive mouse messages.
54 *
55 * @author Brook Novak
56 *
57 */
58public class MouseEventRouter extends JComponent {
59
60 private static final long serialVersionUID = 1L;
61
62 private JMenuBar _menuBar;
63
64 private Container _contentPane;
65
66 private List<MouseListener> _mouseListeners = new LinkedList<MouseListener>();
67
68 private List<MouseMotionListener> _mouseMotionListeners = new LinkedList<MouseMotionListener>();
69
70 private List<MouseWheelListener> _mouseWheelListeners = new LinkedList<MouseWheelListener>();
71
72 private static MouseEvent _currentMouseEvent = null;
73
74 /**
75 * Routes events on given frame layers... the menu bar and content pane
76 *
77 * @param menuBar
78 * Must not be null.
79 *
80 * @param contentPane
81 * Must not be null.
82 */
83 public MouseEventRouter(JMenuBar menuBar, Container contentPane) {
84 if (contentPane == null) {
85 throw new NullPointerException("contentPane");
86 }
87
88 // Listen for all AWT events (ensures popups are included)
89 Toolkit.getDefaultToolkit().addAWTEventListener(new EventCatcher(),
90 AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK);
91
92 this._menuBar = menuBar;
93 this._contentPane = contentPane;
94 }
95
96 /**
97 * Listens only to events to frames... i.e. to expeditee, not to widgets.
98 *
99 * @param listener
100 * The listener to add.
101 */
102 public void addExpediteeMouseListener(MouseListener listener) {
103 if (listener == null) {
104 throw new NullPointerException("listener");
105 }
106
107 _mouseListeners.add(listener);
108 }
109
110 public void removeExpediteeMouseListener(MouseListener listener) {
111 if (listener == null) {
112 throw new NullPointerException("listener");
113 }
114
115 _mouseListeners.remove(listener);
116 }
117
118 public void addExpediteeMouseMotionListener(MouseMotionListener listener) {
119 if (listener == null) {
120 throw new NullPointerException("listener");
121 }
122
123 _mouseMotionListeners.add(listener);
124 }
125
126 public void removeExpediteeMouseMotionListener(MouseMotionListener listener) {
127 if (listener == null) {
128 throw new NullPointerException("listener");
129 }
130
131 _mouseMotionListeners.remove(listener);
132 }
133
134 public void addExpediteeMouseWheelListener(MouseWheelListener listener) {
135 if (listener == null) {
136 throw new NullPointerException("listener");
137 }
138
139 _mouseWheelListeners.add(listener);
140 }
141
142 public void removeExpediteeMouseWheelListener(MouseWheelListener listener) {
143 if (listener == null) {
144 throw new NullPointerException("listener");
145 }
146
147 _mouseWheelListeners.remove(listener);
148 }
149
150 /**
151 * Conceal event catching from outside
152 *
153 * @author Brook Novak
154 */
155 private class EventCatcher implements AWTEventListener {
156 /**
157 * All events for every component in frame are fired to here
158 */
159 @Override
160 public void eventDispatched(AWTEvent event) {
161 if (event instanceof MouseEvent) {
162 routeMouseEvent((MouseEvent) event);
163 }
164 }
165
166 }
167
168 /**
169 * Forwards the mouse event to its appropriate destination. For events that
170 * belong to Expeditee space the events are consumed and manually routed to the
171 * mouse actions handler.
172 *
173 * @param e
174 * The mouse event for any component in the browser frame
175 */
176 private void routeMouseEvent(MouseEvent e) {
177 _currentMouseEvent = e;
178
179 // First convert the point to expeditee space
180 Point containerPoint = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), _contentPane);
181
182 // TODO: Find a reliable way of detecting when the mouse moved onto a window
183 // that isn't a child of ours
184 // if(e.getID() == MouseEvent.MOUSE_EXITED) {
185 // // System.out.println(e.getComponent());
186 // if(containerPoint.x <= 0 || containerPoint.x >= _contentPane.getWidth() ||
187 // containerPoint.y <= 0 || containerPoint.y >= _contentPane.getHeight())
188 // {
189 // //FrameGestureActions.mouseExitedWindow(e);
190 // }
191 // }
192
193 // TODO: Find a reliable way of detecting when the mouse moved onto a window
194 // that isn't a child of ours
195 if (e.getID() == MouseEvent.MOUSE_EXITED) {
196 // System.out.println(e.getComponent());
197 if (containerPoint.x >= 0 && containerPoint.x <= _contentPane.getWidth() && containerPoint.y >= 0
198 && containerPoint.y <= _contentPane.getHeight()) {
199 return;
200 // FrameGestureActions.mouseExitedWindow(e);
201 }
202 }
203
204 if (containerPoint.y < 0 && e.getID() != MouseEvent.MOUSE_EXITED) { // not in the content pane
205
206 if (_menuBar != null && containerPoint.y + _menuBar.getHeight() >= 0) {
207 // The mouse event is over the menu bar.
208 // Could handle specially.
209 } else {
210 // The mouse event is over non-system window
211 // decorations, such as the ones provided by
212 // the Java look and feel.
213 // Could handle specially.
214 }
215 } else {
216
217 // Check to see if the mouse is over an expeditee item or
218 // whether an expeditee item is currently picked up
219 boolean forwardToExpeditee = false;
220 boolean isOverPopup = PopupManager.getInstance()
221 .isPointOverPopup(SwingConversions.fromSwingPoint(containerPoint));
222 if (isOverPopup) {
223 // Popups have highest preference
224 // forwardToExpiditee = false...
225
226 // If there are items in free space - keep them moving with the
227 // cursor over the
228 // popups.
229 if (!FreeItems.getInstance().isEmpty()) {
230 StandardGestureActions.move(FreeItems.getInstance(),
231 SwingConversions.fromSwingPoint(containerPoint));
232 }
233
234 // Note: all frame.content pane events belong to expeditee
235 } else if (e.getSource() == _contentPane
236 || e.getSource() == SwingMiscManager.getIfUsingSwingGraphicsManager().getJFrame()
237 || !FreeItems.getInstance().isEmpty()) {
238 forwardToExpeditee = true;
239 } else if (DisplayController.getCurrentFrame() != null) {
240 /* is mouse over a specific expeditee item? */
241 // NOTE: Do not use FrameUtils.getCurrentItem() - thats relevent
242 // for old mouse position only
243 /*
244 * for (Item i : DisplayIO.getCurrentFrame().getItems()) { if
245 * (i.getPolygon().contains(containerPoint)) { forwardToExpiditee = true; break;
246 * } }
247 */// ABOVE: Does not consider overlays
248 // NOTE: Below is an expensive operation and could be re-used
249 // when passing mouse events!!!
250 forwardToExpeditee = (FrameUtils.onItem(DisplayController.getCurrentFrame(), containerPoint.x,
251 containerPoint.y, true) != null);
252 } else {
253 forwardToExpeditee = false;
254 }
255
256 //System.err.println("MouseEventRouter::routeMouseEvent::contentPane: " + _contentPane);
257 //System.err.println("MouseEventRouter::routeMouseEvent::e.getSource():" + e.getSource());
258 //System.err.println("MouseEventRouter::routeMouseEvent::forwardToExpeditee:" + forwardToExpeditee);
259
260 // Create artificial mouse event and forward it to expeditee
261 MouseEvent expediteeEvent = SwingUtilities.convertMouseEvent(e.getComponent(), e, _contentPane);
262
263 // NOTE: Convert util masks-out the needed extensions
264 MouseEvent withExtensions = null;
265
266 if (forwardToExpeditee) {
267
268 // Ensure that underlying widgets don't get the event
269 e.consume();
270
271 switch (e.getID()) {
272 case MouseEvent.MOUSE_MOVED:
273
274 for (MouseMotionListener listener : _mouseMotionListeners) {
275 listener.mouseMoved(expediteeEvent);
276 }
277
278 // Ensure that expeditee has focus only if no pop-ups exist
279 SwingGraphicsManager g = SwingMiscManager.getIfUsingSwingGraphicsManager();
280 if (g.getContentPane() != null) {
281 if (!g.getContentPane().isFocusOwner() && !isPopupVisible()) {
282 g.getContentPane().requestFocus();
283 }
284 }
285
286 break;
287
288 case MouseEvent.MOUSE_CLICKED:
289
290 withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
291
292 for (MouseListener listener : _mouseListeners) {
293 listener.mouseClicked(withExtensions);
294 }
295
296 break;
297
298 case MouseEvent.MOUSE_PRESSED:
299
300 withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
301
302 for (MouseListener listener : _mouseListeners) {
303 listener.mousePressed(withExtensions);
304 }
305
306 break;
307
308 case MouseEvent.MOUSE_RELEASED:
309
310 withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
311
312 for (MouseListener listener : _mouseListeners) {
313 listener.mouseReleased(withExtensions);
314 }
315
316 break;
317 case MouseEvent.MOUSE_WHEEL:
318 MouseWheelEvent mwe = (MouseWheelEvent) expediteeEvent;
319 for (MouseWheelListener listener : _mouseWheelListeners) {
320 listener.mouseWheelMoved(mwe);
321 }
322 break;
323 case MouseEvent.MOUSE_ENTERED:
324 withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
325 for (MouseListener listener : _mouseListeners) {
326 listener.mouseEntered(withExtensions);
327 }
328 break;
329 case MouseEvent.MOUSE_EXITED:
330 withExtensions = duplicateMouseEvent(expediteeEvent, e.getModifiers() | e.getModifiersEx());
331 for (MouseListener listener : _mouseListeners) {
332 listener.mouseExited(withExtensions);
333 }
334 break;
335 case MouseEvent.MOUSE_DRAGGED:
336 for (MouseMotionListener listener : _mouseMotionListeners) {
337 listener.mouseDragged(expediteeEvent);
338 }
339 break;
340 }
341
342 } else {
343
344 // Keep expeditees mouse X/Y updated
345 // FrameMouseActions.MouseX = expediteeEvent.getX();
346 // FrameMouseActions.MouseY = expediteeEvent.getY();
347
348 // If forwarding to swing ensure that widgets are not highlighted
349 // to give visual feedback to users such that swing has focus.
350 Item i = StandardGestureActions.getlastHighlightedItem();
351 if (i != null && i.getHighlightMode() != Item.HighlightMode.None) {
352 FrameGraphics.changeHighlightMode(i, Item.HighlightMode.None);
353 }
354
355 // Also bring expeditee behaviour to swing: Auto-focus on component
356 // whenever the mouse moves over it.
357 if (e.getID() == MouseEvent.MOUSE_MOVED) {
358 if (e.getSource() instanceof Component) {
359 Component target = (Component) e.getSource();
360 if (!target.isFocusOwner()) {
361 target.requestFocus();
362 }
363 }
364 // Auto-hide pop-ups when user click on something other
365 // than a pop-up - and is not on a pop-up invoker
366 } /*
367 * else if (!isOverPopup // TODO: Reinstate. cts16 && e.getID() ==
368 * MouseEvent.MOUSE_PRESSED &&
369 * !PopupManager.getInstance().isInvoker(e.getComponent())) {
370 * PopupManager.getInstance().hideAutohidePopups(); }
371 */
372 }
373 }
374 }
375
376 public static boolean isPopupVisible() {
377 return isPopupVisible(SwingMiscManager.getIfUsingSwingGraphicsManager().getLayeredPane());
378 }
379
380// private static boolean isPopupVisible(Container parent) {
381// for (Component c : parent.getComponents()) {
382// if (c instanceof JPopupMenu && ((JPopupMenu) c).isVisible()) {
383// System.err.println("Popup visible");
384// return true;
385//
386// } else if (c instanceof Container
387// && c != SwingMiscManager.getIfUsingSwingGraphicsManager().getContentPane()) {
388// if (isPopupVisible((Container) c)) {
389// System.err.println("Popup visible");
390// return true;
391// }
392// }
393// }
394// System.err.println("Popup not visible");
395// return false;
396// }
397
398 private static boolean isPopupVisible(final JComponent parent) {
399 if(parent instanceof JComboBox) {
400 if(((JComboBox<?>) parent).isPopupVisible()) { return true; }
401 }
402 for (final Component c : parent.getComponents()) {
403 if(c instanceof JComponent && isPopupVisible((JComponent) c)) {
404 //System.err.println("MouseEventRouter::found visible popup");
405 return true;
406 }
407 }
408 return false;
409 }
410
411// private static boolean isPopupVisible(final JComponent parent) {
412// final JPopupMenu popup = parent.getComponentPopupMenu();
413// if(popup != null) {
414// System.err.println("Found control with popup");
415// }
416// if(popup != null && popup.isVisible()) {
417// System.err.println("Popup visible");
418// return true;
419// } else {
420// for (final Component c : parent.getComponents()) {
421// if(c instanceof JComponent && isPopupVisible((JComponent) c)) {
422// System.err.println("Popup visible");
423// return true;
424// }
425// }
426// }
427// System.err.println("Popup not visible");
428// return false;
429// }
430
431 private MouseEvent duplicateMouseEvent(MouseEvent e, int modifiers) {
432 return new MouseEvent(e.getComponent(), e.getID(), e.getWhen(), modifiers, e.getX(), e.getY(),
433 /** The below methods are not compatible with Java 1.5 */
434 /*
435 * e.getXOnScreen(), e.getYOnScreen(),
436 */
437 e.getClickCount(), e.isPopupTrigger(), e.getButton());
438 }
439
440 public static MouseEvent getCurrentMouseEvent() {
441 return _currentMouseEvent;
442 }
443}
Note: See TracBrowser for help on using the repository browser.