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() & (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}