001package jmri.util.swing;
002
003import java.awt.Component;
004import java.awt.Point;
005import java.awt.Toolkit;
006import java.awt.event.InputEvent;
007import java.awt.event.MouseEvent;
008
009import jmri.util.SystemType;
010
011/**
012 * Adaptor for MouseEvent.
013 * This class is used to fix some issues with MouseEvent on Windows.
014 *
015 * @author Daniel Bergqvist (C) 2022
016 */
017public class JmriMouseEvent {
018
019    /**
020     * The "mouse clicked" event. This {@code MouseEvent}
021     * occurs when a mouse button is pressed and released.
022     */
023    public static final int MOUSE_CLICKED = MouseEvent.MOUSE_CLICKED;
024
025    /**
026     * The "mouse pressed" event. This {@code MouseEvent}
027     * occurs when a mouse button is pushed down.
028     */
029    public static final int MOUSE_PRESSED = MouseEvent.MOUSE_PRESSED; //Event.MOUSE_DOWN
030
031    /**
032     * The "mouse released" event. This {@code MouseEvent}
033     * occurs when a mouse button is let up.
034     */
035    public static final int MOUSE_RELEASED = MouseEvent.MOUSE_RELEASED; //Event.MOUSE_UP
036
037    /**
038     * The "mouse moved" event. This {@code MouseEvent}
039     * occurs when the mouse position changes.
040     */
041    public static final int MOUSE_MOVED = MouseEvent.MOUSE_MOVED; //Event.MOUSE_MOVE
042
043    /**
044     * The "mouse entered" event. This {@code MouseEvent}
045     * occurs when the mouse cursor enters the unobscured part of component's
046     * geometry.
047     */
048    public static final int MOUSE_ENTERED = MouseEvent.MOUSE_ENTERED; //Event.MOUSE_ENTER
049
050    /**
051     * The "mouse exited" event. This {@code MouseEvent}
052     * occurs when the mouse cursor exits the unobscured part of component's
053     * geometry.
054     */
055    public static final int MOUSE_EXITED = MouseEvent.MOUSE_EXITED; //Event.MOUSE_EXIT
056
057    /**
058     * The "mouse dragged" event. This {@code MouseEvent}
059     * occurs when the mouse position changes while a mouse button is pressed.
060     */
061    public static final int MOUSE_DRAGGED = MouseEvent.MOUSE_DRAGGED; //Event.MOUSE_DRAG
062
063    /**
064     * The "mouse wheel" event.  This is the only {@code MouseWheelEvent}.
065     * It occurs when a mouse equipped with a wheel has its wheel rotated.
066     * @since 1.4
067     */
068    public static final int MOUSE_WHEEL = MouseEvent.MOUSE_WHEEL;
069
070    /**
071     * Indicates no mouse buttons; used by {@link #getButton}.
072     * @since 1.4
073     */
074    public static final int NOBUTTON = MouseEvent.NOBUTTON;
075
076    /**
077     * Indicates mouse button #1; used by {@link #getButton}.
078     * @since 1.4
079     */
080    public static final int BUTTON1 = MouseEvent.BUTTON1;
081
082    /**
083     * Indicates mouse button #2; used by {@link #getButton}.
084     * @since 1.4
085     */
086    public static final int BUTTON2 = MouseEvent.BUTTON2;
087
088    /**
089     * Indicates mouse button #3; used by {@link #getButton}.
090     * @since 1.4
091     */
092    public static final int BUTTON3 = MouseEvent.BUTTON3;
093
094
095    private final MouseEvent event;
096
097    public JmriMouseEvent(MouseEvent event) {
098        this.event = event;
099    }
100
101    public JmriMouseEvent(Component source, int id, long when, int modifiers,
102                      int x, int y, int clickCount, boolean popupTrigger) {
103        this.event = new MouseEvent(source, id, when, modifiers, x, y, clickCount, popupTrigger, NOBUTTON);
104     }
105
106    public JmriMouseEvent(Component source, int id, long when, int modifiers,
107                      int x, int y, int clickCount, boolean popupTrigger,
108                      int button) {
109        this.event = new MouseEvent(source, id, when, modifiers, x, y, clickCount, popupTrigger, button);
110     }
111
112    /**
113     * Returns the event type.
114     *
115     * @return the event's type id
116     */
117    public int getID() {
118        return event.getID();
119    }
120
121    /**
122     * Returns the absolute x, y position of the event.
123     * In a virtual device multi-screen environment in which the
124     * desktop area could span multiple physical screen devices,
125     * these coordinates are relative to the virtual coordinate system.
126     * Otherwise, these coordinates are relative to the coordinate system
127     * associated with the Component's GraphicsConfiguration.
128     *
129     * @return a {@code Point} object containing the absolute  x
130     *  and y coordinates.
131     *
132     * @see java.awt.GraphicsConfiguration
133     * @since 1.6
134     */
135    public Point getLocationOnScreen(){
136        return event.getLocationOnScreen();
137    }
138
139    /**
140     * Returns the absolute horizontal x position of the event.
141     * In a virtual device multi-screen environment in which the
142     * desktop area could span multiple physical screen devices,
143     * this coordinate is relative to the virtual coordinate system.
144     * Otherwise, this coordinate is relative to the coordinate system
145     * associated with the Component's GraphicsConfiguration.
146     *
147     * @return x  an integer indicating absolute horizontal position.
148     *
149     * @see java.awt.GraphicsConfiguration
150     * @since 1.6
151     */
152    public int getXOnScreen() {
153        return event.getXOnScreen();
154    }
155
156    /**
157     * Returns the absolute vertical y position of the event.
158     * In a virtual device multi-screen environment in which the
159     * desktop area could span multiple physical screen devices,
160     * this coordinate is relative to the virtual coordinate system.
161     * Otherwise, this coordinate is relative to the coordinate system
162     * associated with the Component's GraphicsConfiguration.
163     *
164     * @return y  an integer indicating absolute vertical position.
165     *
166     * @see java.awt.GraphicsConfiguration
167     * @since 1.6
168     */
169    public int getYOnScreen() {
170        return event.getYOnScreen();
171    }
172
173    /**
174     * Returns the horizontal x position of the event relative to the
175     * source component.
176     *
177     * @return x  an integer indicating horizontal position relative to
178     *            the component
179     */
180    public int getX() {
181        return event.getX();
182    }
183
184    /**
185     * Returns the vertical y position of the event relative to the
186     * source component.
187     *
188     * @return y  an integer indicating vertical position relative to
189     *            the component
190     */
191    public int getY() {
192        return event.getY();
193    }
194
195    /**
196     * Returns the x,y position of the event relative to the source component.
197     *
198     * @return a {@code Point} object containing the x and y coordinates
199     *         relative to the source component
200     *
201     */
202    public Point getPoint() {
203        return event.getPoint();
204    }
205
206    /**
207     * Translates the event's coordinates to a new position
208     * by adding specified {@code x} (horizontal) and {@code y}
209     * (vertical) offsets.
210     *
211     * @param x the horizontal x value to add to the current x
212     *          coordinate position
213     * @param y the vertical y value to add to the current y
214                coordinate position
215     */
216    public synchronized void translatePoint(int x, int y) {
217        event.translatePoint(x, y);
218    }
219
220    /**
221     * Returns the number of mouse clicks associated with this event.
222     *
223     * @return integer value for the number of clicks
224     */
225    public int getClickCount() {
226        return event.getClickCount();
227    }
228
229    /**
230     * Returns which, if any, of the mouse buttons has changed state.
231     * The returned value is ranged
232     * from 0 to the {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}
233     * value.
234     * The returned value includes at least the following constants:
235     * <ul>
236     * <li> {@code NOBUTTON}
237     * <li> {@code BUTTON1}
238     * <li> {@code BUTTON2}
239     * <li> {@code BUTTON3}
240     * </ul>
241     * It is allowed to use those constants to compare with the returned button number in the application.
242     * For example,
243     * <pre>
244     * if (anEvent.getButton() == JmriMouseEvent.BUTTON1) {
245     * </pre>
246     * In particular, for a mouse with one, two, or three buttons this method may return the following values:
247     * <ul>
248     * <li> 0 ({@code NOBUTTON})
249     * <li> 1 ({@code BUTTON1})
250     * <li> 2 ({@code BUTTON2})
251     * <li> 3 ({@code BUTTON3})
252     * </ul>
253     * Button numbers greater than {@code BUTTON3} have no constant identifier.
254     * So if a mouse with five buttons is
255     * installed, this method may return the following values:
256     * <ul>
257     * <li> 0 ({@code NOBUTTON})
258     * <li> 1 ({@code BUTTON1})
259     * <li> 2 ({@code BUTTON2})
260     * <li> 3 ({@code BUTTON3})
261     * <li> 4
262     * <li> 5
263     * </ul>
264     * <p>
265     * Note: If support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
266     * then the AWT event subsystem does not produce mouse events for the extended mouse
267     * buttons. So it is not expected that this method returns anything except {@code NOBUTTON}, {@code BUTTON1},
268     * {@code BUTTON2}, {@code BUTTON3}.
269     *
270     * @return one of the values from 0 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}
271     *         if support for the extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java.
272     *         That range includes {@code NOBUTTON}, {@code BUTTON1}, {@code BUTTON2}, {@code BUTTON3};
273     *         <br>
274     *         {@code NOBUTTON}, {@code BUTTON1}, {@code BUTTON2} or {@code BUTTON3}
275     *         if support for the extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
276     * @since 1.4
277     * @see Toolkit#areExtraMouseButtonsEnabled()
278     * @see java.awt.MouseInfo#getNumberOfButtons()
279     * @see MouseEvent#MouseEvent(Component, int, long, int, int, int, int, int, int, boolean, int)
280     * @see InputEvent#getMaskForButton(int)
281     */
282    public int getButton() {
283        return event.getButton();
284    }
285
286    /**
287     * Returns whether or not this mouse event is the popup menu
288     * trigger event for the platform.
289     * <p><b>Note</b>: Popup menus are triggered differently
290     * on different systems. Therefore, {@code isPopupTrigger}
291     * should be checked in both {@code mousePressed}
292     * and {@code mouseReleased}
293     * for proper cross-platform functionality.
294     *
295     * @return boolean, true if this event is the popup menu trigger
296     *         for this platform
297     */
298    public boolean isPopupTrigger() {
299        if (SystemType.isWindows()) {
300            switch (event.getID()) {
301                case MouseEvent.MOUSE_PRESSED:
302                case MouseEvent.MOUSE_RELEASED:
303                case MouseEvent.MOUSE_CLICKED:
304                    // event.isPopupTrigger() returns false on mousePressed() on Windows.
305                    // The bad news is that SwingUtilities.isRightMouseButton(event) doesn't work either.
306                    return (event.getModifiersEx() & InputEvent.META_DOWN_MASK) != 0
307                            || (event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0;
308
309                default:
310                    return event.isPopupTrigger();
311            }
312        } else {
313            return event.isPopupTrigger();
314        }
315    }
316
317    /**
318     * Returns a {@code String} instance describing the modifier keys and
319     * mouse buttons that were down during the event, such as "Shift",
320     * or "Ctrl+Shift". These strings can be localized by changing
321     * the {@code awt.properties} file.
322     * <p>
323     * Note that the {@code InputEvent.ALT_MASK} and
324     * {@code InputEvent.BUTTON2_MASK} have equal values,
325     * so the "Alt" string is returned for both modifiers.  Likewise,
326     * the {@code InputEvent.META_MASK} and
327     * {@code InputEvent.BUTTON3_MASK} have equal values,
328     * so the "Meta" string is returned for both modifiers.
329     * <p>
330     * Note that passing negative parameter is incorrect,
331     * and will cause the returning an unspecified string.
332     * Zero parameter means that no modifiers were passed and will
333     * cause the returning an empty string.
334     *
335     * @param modifiers A modifier mask describing the modifier keys and
336     *                  mouse buttons that were down during the event
337     * @return string   string text description of the combination of modifier
338     *                  keys and mouse buttons that were down during the event
339     * @see InputEvent#getModifiersExText(int)
340     * @since 1.4
341     */
342    public static String getMouseModifiersText(int modifiers) {
343        return MouseEvent.getMouseModifiersText(modifiers);
344    }
345
346    /**
347     * Returns a parameter string identifying this event.
348     * This method is useful for event-logging and for debugging.
349     *
350     * @return a string identifying the event and its attributes
351     */
352    public String paramString() {
353        return event.paramString();
354    }
355
356    /**
357     * Returns whether or not the Shift modifier is down on this event.
358     * @return whether or not the Shift modifier is down on this event
359     */
360    public boolean isShiftDown() {
361        return event.isShiftDown();
362    }
363
364    /**
365     * Returns whether or not the Control modifier is down on this event.
366     * @return whether or not the Control modifier is down on this event
367     */
368    public boolean isControlDown() {
369        return event.isControlDown();
370    }
371
372    /**
373     * Returns whether or not the Meta modifier is down on this event.
374     *
375     * The meta key was until Java 8 the right mouse button on Windows.
376     * On Java 9 on Windows 10, there is no more meta key. Note that this
377     * method is called both on mouse button events and mouse move events,
378     * and therefore "event.getButton() == JmriMouseEvent.BUTTON3" doesn't work.
379     *
380     * As of Java 11, the meta key process has changed.  The getModifiersEx() value will vary
381     * when button 3 is used, depending on the mouse event.
382     *     mousePressed  :: 4096 (button 3)
383     *     mouseDragged  :: 4096
384     *     mouseReleased :: 256  (meta)
385     *     mouseClicked  :: 256
386     * The meta value is simulated by Java for Linux and Windows based on button 3 being active.
387     *
388     * @return whether or not the Meta modifier is down on this event
389     */
390    public boolean isMetaDown() {
391        if (SystemType.isWindows() || SystemType.isLinux()) {
392            return ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0 ||
393                    (event.getModifiersEx() & InputEvent.META_DOWN_MASK) != 0);
394        } else {
395            return event.isMetaDown();
396        }
397    }
398
399    /**
400     * Returns whether or not the Alt modifier is down on this event.
401     * @return whether or not the Alt modifier is down on this event
402     */
403    public boolean isAltDown() {
404        return event.isAltDown();
405    }
406
407    /**
408     * Returns whether or not the AltGraph modifier is down on this event.
409     * @return whether or not the AltGraph modifier is down on this event
410     */
411    public boolean isAltGraphDown() {
412        return event.isAltGraphDown();
413    }
414
415    /**
416     * Returns the difference in milliseconds between the timestamp of when this event occurred and
417     * midnight, January 1, 1970 UTC.
418     * @return the difference in milliseconds between the timestamp and midnight, January 1, 1970 UTC
419     */
420    public long getWhen() {
421        return event.getWhen();
422    }
423
424    /**
425     * Returns the modifier mask for this event.
426     *
427     * @return the modifier mask for this event
428     * @deprecated It is recommended that extended modifier keys and
429     *             {@link #getModifiersEx()} be used instead
430     */
431    @Deprecated(since = "9")
432    @SuppressWarnings("deprecation")
433    public int getModifiers() {
434        return event.getModifiers();
435    }
436
437    /**
438     * Returns the extended modifier mask for this event.
439     * <P>
440     * Extended modifiers are the modifiers that ends with the _DOWN_MASK suffix,
441     * such as ALT_DOWN_MASK, BUTTON1_DOWN_MASK, and others.
442     * <P>
443     * Extended modifiers represent the state of all modal keys,
444     * such as ALT, CTRL, META, and the mouse buttons just after
445     * the event occurred.
446     * <P>
447     * For example, if the user presses <b>button 1</b> followed by
448     * <b>button 2</b>, and then releases them in the same order,
449     * the following sequence of events is generated:
450     * <PRE>
451     *    {@code MOUSE_PRESSED}:  {@code BUTTON1_DOWN_MASK}
452     *    {@code MOUSE_PRESSED}:  {@code BUTTON1_DOWN_MASK | BUTTON2_DOWN_MASK}
453     *    {@code MOUSE_RELEASED}: {@code BUTTON2_DOWN_MASK}
454     *    {@code MOUSE_CLICKED}:  {@code BUTTON2_DOWN_MASK}
455     *    {@code MOUSE_RELEASED}:
456     *    {@code MOUSE_CLICKED}:
457     * </PRE>
458     * <P>
459     * It is not recommended to compare the return value of this method
460     * using {@code ==} because new modifiers can be added in the future.
461     * For example, the appropriate way to check that SHIFT and BUTTON1 are
462     * down, but CTRL is up is demonstrated by the following code:
463     * <PRE>
464     *    int onmask = SHIFT_DOWN_MASK | BUTTON1_DOWN_MASK;
465     *    int offmask = CTRL_DOWN_MASK;
466     *    if ((event.getModifiersEx() &amp; (onmask | offmask)) == onmask) {
467     *        ...
468     *    }
469     * </PRE>
470     * The above code will work even if new modifiers are added.
471     *
472     * @return the extended modifier mask for this event
473     * @since 1.4
474     */
475    public int getModifiersEx() {
476        return event.getModifiersEx();
477    }
478
479    /**
480     * Returns the originator of the event.
481     *
482     * @return the {@code Component} object that originated
483     * the event, or {@code null} if the object is not a
484     * {@code Component}.
485     */
486    public Component getComponent() {
487        return event.getComponent();
488    }
489
490    /**
491     * The object on which the Event initially occurred.
492     *
493     * @return the object on which the Event initially occurred
494     */
495    public Object getSource() {
496        return event.getSource();
497    }
498
499    /**
500     * Consumes this event so that it will not be processed
501     * in the default manner by the source which originated it.
502     */
503    public void consume() {
504        event.consume();
505    }
506
507}