/*
 * Copyright (c) 1996, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package java.awt.event;

import java.awt.Component;
import java.awt.GraphicsEnvironment;
import java.awt.IllegalComponentStateException;
import java.awt.Point;
import java.awt.Toolkit;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serial;

import sun.awt.AWTAccessor;
import sun.awt.SunToolkit;

/**
 * An event which indicates that a mouse action occurred in a component.
 * A mouse action is considered to occur in a particular component if and only
 * if the mouse cursor is over the unobscured part of the component's bounds
 * when the action happens.
 * For lightweight components, such as Swing's components, mouse events
 * are only dispatched to the component if the mouse event type has been
 * enabled on the component. A mouse event type is enabled by adding the
 * appropriate mouse-based {@code EventListener} to the component
 * ({@link MouseListener} or {@link MouseMotionListener}), or by invoking
 * {@link Component#enableEvents(long)} with the appropriate mask parameter
 * ({@code AWTEvent.MOUSE_EVENT_MASK} or {@code AWTEvent.MOUSE_MOTION_EVENT_MASK}).
 * If the mouse event type has not been enabled on the component, the
 * corresponding mouse events are dispatched to the first ancestor that
 * has enabled the mouse event type.
 *<p>
 * For example, if a {@code MouseListener} has been added to a component, or
 * {@code enableEvents(AWTEvent.MOUSE_EVENT_MASK)} has been invoked, then all
 * the events defined by {@code MouseListener} are dispatched to the component.
 * On the other hand, if a {@code MouseMotionListener} has not been added and
 * {@code enableEvents} has not been invoked with
 * {@code AWTEvent.MOUSE_MOTION_EVENT_MASK}, then mouse motion events are not
 * dispatched to the component. Instead the mouse motion events are
 * dispatched to the first ancestors that has enabled mouse motion
 * events.
 * <P>
 * This low-level event is generated by a component object for:
 * <ul>
 * <li>Mouse Events
 *     <ul>
 *     <li>a mouse button is pressed
 *     <li>a mouse button is released
 *     <li>a mouse button is clicked (pressed and released)
 *     <li>the mouse cursor enters the unobscured part of component's geometry
 *     <li>the mouse cursor exits the unobscured part of component's geometry
 *     </ul>
 * <li> Mouse Motion Events
 *     <ul>
 *     <li>the mouse is moved
 *     <li>the mouse is dragged
 *     </ul>
 * </ul>
 * <P>
 * A {@code MouseEvent} object is passed to every
 * {@code MouseListener}
 * or {@code MouseAdapter} object which is registered to receive
 * the "interesting" mouse events using the component's
 * {@code addMouseListener} method.
 * ({@code MouseAdapter} objects implement the
 * {@code MouseListener} interface.) Each such listener object
 * gets a {@code MouseEvent} containing the mouse event.
 * <P>
 * A {@code MouseEvent} object is also passed to every
 * {@code MouseMotionListener} or
 * {@code MouseMotionAdapter} object which is registered to receive
 * mouse motion events using the component's
 * {@code addMouseMotionListener}
 * method. ({@code MouseMotionAdapter} objects implement the
 * {@code MouseMotionListener} interface.) Each such listener object
 * gets a {@code MouseEvent} containing the mouse motion event.
 * <P>
 * When a mouse button is clicked, events are generated and sent to the
 * registered {@code MouseListener}s.
 * The state of modal keys can be retrieved using {@link InputEvent#getModifiers}
 * and {@link InputEvent#getModifiersEx}.
 * The button mask returned by {@link InputEvent#getModifiers} reflects
 * only the button that changed state, not the current state of all buttons.
 * (Note: Due to overlap in the values of ALT_MASK/BUTTON2_MASK and
 * META_MASK/BUTTON3_MASK, this is not always true for mouse events involving
 * modifier keys).
 * To get the state of all buttons and modifier keys, use
 * {@link InputEvent#getModifiersEx}.
 * The button which has changed state is returned by {@link MouseEvent#getButton}
 * <P>
 * For example, if the first mouse button is pressed, events are sent in the
 * following order:
 * <pre><b>
 *    id              modifiers    button</b>{@code
 *    MOUSE_PRESSED:  BUTTON1_MASK BUTTON1
 *    MOUSE_RELEASED: BUTTON1_MASK BUTTON1
 *    MOUSE_CLICKED:  BUTTON1_MASK BUTTON1
 * }</pre>
 * When multiple mouse buttons are pressed, each press, release, and click
 * results in a separate event.
 * <P>
 * For example, if the user presses <b>button 1</b> followed by
 * <b>button 2</b>, and then releases them in the same order,
 * the following sequence of events is generated:
 * <pre><b>
 *    id              modifiers    button</b>{@code
 *    MOUSE_PRESSED:  BUTTON1_MASK BUTTON1
 *    MOUSE_PRESSED:  BUTTON2_MASK BUTTON2
 *    MOUSE_RELEASED: BUTTON1_MASK BUTTON1
 *    MOUSE_CLICKED:  BUTTON1_MASK BUTTON1
 *    MOUSE_RELEASED: BUTTON2_MASK BUTTON2
 *    MOUSE_CLICKED:  BUTTON2_MASK BUTTON2
 * }</pre>
 * If <b>button 2</b> is released first, the
 * {@code MOUSE_RELEASED}/{@code MOUSE_CLICKED} pair
 * for {@code BUTTON2_MASK} arrives first,
 * followed by the pair for {@code BUTTON1_MASK}.
 * <p>
 * Some extra mouse buttons are added to extend the standard set of buttons
 * represented by the following constants:{@code BUTTON1}, {@code BUTTON2}, and {@code BUTTON3}.
 * Extra buttons have no assigned {@code BUTTONx}
 * constants as well as their button masks have no assigned {@code BUTTONx_DOWN_MASK}
 * constants. Nevertheless, ordinal numbers starting from 4 may be
 * used as button numbers (button ids). Values obtained by the
 * {@link InputEvent#getMaskForButton(int) getMaskForButton(button)} method may be used
 * as button masks.
 * <p>
 * {@code MOUSE_DRAGGED} events are delivered to the {@code Component}
 * in which the mouse button was pressed until the mouse button is released
 * (regardless of whether the mouse position is within the bounds of the
 * {@code Component}).  Due to platform-dependent Drag&amp;Drop implementations,
 * {@code MOUSE_DRAGGED} events may not be delivered during a native
 * Drag&amp;Drop operation.
 *
 * In a multi-screen environment mouse drag events are delivered to the
 * {@code Component} even if the mouse position is outside the bounds of the
 * {@code GraphicsConfiguration} associated with that
 * {@code Component}. However, the reported position for mouse drag events
 * in this case may differ from the actual mouse position:
 * <ul>
 * <li>In a multi-screen environment without a virtual device:
 * <br>
 * The reported coordinates for mouse drag events are clipped to fit within the
 * bounds of the {@code GraphicsConfiguration} associated with
 * the {@code Component}.
 * <li>In a multi-screen environment with a virtual device:
 * <br>
 * The reported coordinates for mouse drag events are clipped to fit within the
 * bounds of the virtual device associated with the {@code Component}.
 * </ul>
 * <p>
 * An unspecified behavior will be caused if the {@code id} parameter
 * of any particular {@code MouseEvent} instance is not
 * in the range from {@code MOUSE_FIRST} to {@code MOUSE_LAST}-1
 * ({@code MOUSE_WHEEL} is not acceptable).
 *
 * @author Carl Quinn
 *
 * @see MouseAdapter
 * @see MouseListener
 * @see MouseMotionAdapter
 * @see MouseMotionListener
 * @see MouseWheelListener
 * @see <a href="https://docs.oracle.com/javase/tutorial/uiswing/events/mouselistener.html">Tutorial: Writing a Mouse Listener</a>
 * @see <a href="https://docs.oracle.com/javase/tutorial/uiswing/events/mousemotionlistener.html">Tutorial: Writing a Mouse Motion Listener</a>
 *
 * @since 1.1
 */
public non-sealed class MouseEvent extends InputEvent {

    /**
     * The first number in the range of ids used for mouse events.
     */
    public static final int MOUSE_FIRST         = 500;

    /**
     * The last number in the range of ids used for mouse events.
     */
    public static final int MOUSE_LAST          = 507;

    /**
     * The "mouse clicked" event. This {@code MouseEvent}
     * occurs when a mouse button is pressed and released.
     */
    public static final int MOUSE_CLICKED = MOUSE_FIRST;

    /**
     * The "mouse pressed" event. This {@code MouseEvent}
     * occurs when a mouse button is pushed down.
     */
    public static final int MOUSE_PRESSED = 1 + MOUSE_FIRST; //Event.MOUSE_DOWN

    /**
     * The "mouse released" event. This {@code MouseEvent}
     * occurs when a mouse button is let up.
     */
    public static final int MOUSE_RELEASED = 2 + MOUSE_FIRST; //Event.MOUSE_UP

    /**
     * The "mouse moved" event. This {@code MouseEvent}
     * occurs when the mouse position changes.
     */
    public static final int MOUSE_MOVED = 3 + MOUSE_FIRST; //Event.MOUSE_MOVE

    /**
     * The "mouse entered" event. This {@code MouseEvent}
     * occurs when the mouse cursor enters the unobscured part of component's
     * geometry.
     */
    public static final int MOUSE_ENTERED = 4 + MOUSE_FIRST; //Event.MOUSE_ENTER

    /**
     * The "mouse exited" event. This {@code MouseEvent}
     * occurs when the mouse cursor exits the unobscured part of component's
     * geometry.
     */
    public static final int MOUSE_EXITED = 5 + MOUSE_FIRST; //Event.MOUSE_EXIT

    /**
     * The "mouse dragged" event. This {@code MouseEvent}
     * occurs when the mouse position changes while a mouse button is pressed.
     */
    public static final int MOUSE_DRAGGED = 6 + MOUSE_FIRST; //Event.MOUSE_DRAG

    /**
     * The "mouse wheel" event.  This is the only {@code MouseWheelEvent}.
     * It occurs when a mouse equipped with a wheel has its wheel rotated.
     * @since 1.4
     */
    public static final int MOUSE_WHEEL = 7 + MOUSE_FIRST;

    /**
     * Indicates no mouse buttons; used by {@link #getButton}.
     * @since 1.4
     */
    public static final int NOBUTTON = 0;

    /**
     * Indicates mouse button #1; used by {@link #getButton}.
     * @since 1.4
     */
    public static final int BUTTON1 = 1;

    /**
     * Indicates mouse button #2; used by {@link #getButton}.
     * @since 1.4
     */
    public static final int BUTTON2 = 2;

    /**
     * Indicates mouse button #3; used by {@link #getButton}.
     * @since 1.4
     */
    public static final int BUTTON3 = 3;

    /**
     * The mouse event's x coordinate.
     * The x value is relative to the component that fired the event.
     *
     * @serial
     * @see #getX()
     */
    int x;

    /**
     * The mouse event's y coordinate.
     * The y value is relative to the component that fired the event.
     *
     * @serial
     * @see #getY()
     */
    int y;

    /**
     * The mouse event's x absolute coordinate.
     * In a virtual device multi-screen environment in which the
     * desktop area could span multiple physical screen devices,
     * this coordinate is relative to the virtual coordinate system.
     * Otherwise, this coordinate is relative to the coordinate system
     * associated with the Component's GraphicsConfiguration.
     *
     * @serial
   */
    private int xAbs;

    /**
     * The mouse event's y absolute coordinate.
     * In a virtual device multi-screen environment in which the
     * desktop area could span multiple physical screen devices,
     * this coordinate is relative to the virtual coordinate system.
     * Otherwise, this coordinate is relative to the coordinate system
     * associated with the Component's GraphicsConfiguration.
     *
     * @serial
     */
    private int yAbs;

    /**
     * Indicates the number of quick consecutive clicks of
     * a mouse button.
     * clickCount will be valid for only three mouse events :<BR>
     * {@code MOUSE_CLICKED},
     * {@code MOUSE_PRESSED} and
     * {@code MOUSE_RELEASED}.
     * For the above, the {@code clickCount} will be at least 1.
     * For all other events the count will be 0.
     *
     * @serial
     * @see #getClickCount()
     */
    int clickCount;

    /**
     * Indicates whether the event is a result of a touch event.
     */
    private boolean causedByTouchEvent;

    /**
     * Indicates which, if any, of the mouse buttons has changed state.
     *
     * The valid values are ranged from 0 to the value returned by the
     * {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()} method.
     * This range already includes constants {@code NOBUTTON}, {@code BUTTON1},
     * {@code BUTTON2}, and {@code BUTTON3}
     * if these buttons are present. So it is allowed to use these constants too.
     * For example, for a mouse with two buttons this field may contain the following values:
     * <ul>
     * <li> 0 ({@code NOBUTTON})
     * <li> 1 ({@code BUTTON1})
     * <li> 2 ({@code BUTTON2})
     * </ul>
     * If a mouse has 5 buttons, this field may contain the following values:
     * <ul>
     * <li> 0 ({@code NOBUTTON})
     * <li> 1 ({@code BUTTON1})
     * <li> 2 ({@code BUTTON2})
     * <li> 3 ({@code BUTTON3})
     * <li> 4
     * <li> 5
     * </ul>
     * If support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled()} disabled by Java
     * then the field may not contain the value larger than {@code BUTTON3}.
     * @serial
     * @see #getButton()
     * @see java.awt.Toolkit#areExtraMouseButtonsEnabled()
     */
    int button;

    /**
     * A property used to indicate whether a Popup Menu
     * should appear  with a certain gestures.
     * If {@code popupTrigger} = {@code false},
     * no popup menu should appear.  If it is {@code true}
     * then a popup menu should appear.
     *
     * @serial
     * @see java.awt.PopupMenu
     * @see #isPopupTrigger()
     */
    boolean popupTrigger = false;

    /**
     * Use serialVersionUID from JDK 1.1 for interoperability.
     */
    @Serial
    private static final long serialVersionUID = -991214153494842848L;

    /**
     * A number of buttons available on the mouse at the {@code Toolkit} machinery startup.
     */
    private static int cachedNumberOfButtons;

    static {
        /* ensure that the necessary native libraries are loaded */
        NativeLibLoader.loadLibraries();
        if (!GraphicsEnvironment.isHeadless()) {
            initIDs();
        }
        final Toolkit tk = Toolkit.getDefaultToolkit();
        if (tk instanceof SunToolkit) {
            cachedNumberOfButtons = ((SunToolkit)tk).getNumberOfButtons();
        } else {
            //It's expected that some toolkits (Headless,
            //whatever besides SunToolkit) could also operate.
            cachedNumberOfButtons = 3;
        }
        AWTAccessor.setMouseEventAccessor(
            new AWTAccessor.MouseEventAccessor() {
                public boolean isCausedByTouchEvent(MouseEvent ev) {
                    return ev.causedByTouchEvent;
                }

                public void setCausedByTouchEvent(MouseEvent ev,
                    boolean causedByTouchEvent) {
                    ev.causedByTouchEvent = causedByTouchEvent;
                }
            });
    }

    /**
     * Initialize JNI field and method IDs for fields that may be
     *  accessed from C.
     */
    private static native void initIDs();

    /**
     * Returns the absolute x, y position of the event.
     * In a virtual device multi-screen environment in which the
     * desktop area could span multiple physical screen devices,
     * these coordinates are relative to the virtual coordinate system.
     * Otherwise, these coordinates are relative to the coordinate system
     * associated with the Component's GraphicsConfiguration.
     *
     * @return a {@code Point} object containing the absolute  x
     *  and y coordinates.
     *
     * @see java.awt.GraphicsConfiguration
     * @since 1.6
     */
    public Point getLocationOnScreen(){
      return new Point(xAbs, yAbs);
    }

    /**
     * Returns the absolute horizontal x position of the event.
     * In a virtual device multi-screen environment in which the
     * desktop area could span multiple physical screen devices,
     * this coordinate is relative to the virtual coordinate system.
     * Otherwise, this coordinate is relative to the coordinate system
     * associated with the Component's GraphicsConfiguration.
     *
     * @return x  an integer indicating absolute horizontal position.
     *
     * @see java.awt.GraphicsConfiguration
     * @since 1.6
     */
    public int getXOnScreen() {
        return xAbs;
    }

    /**
     * Returns the absolute vertical y position of the event.
     * In a virtual device multi-screen environment in which the
     * desktop area could span multiple physical screen devices,
     * this coordinate is relative to the virtual coordinate system.
     * Otherwise, this coordinate is relative to the coordinate system
     * associated with the Component's GraphicsConfiguration.
     *
     * @return y  an integer indicating absolute vertical position.
     *
     * @see java.awt.GraphicsConfiguration
     * @since 1.6
     */
    public int getYOnScreen() {
        return yAbs;
    }

    /**
     * Constructs a {@code MouseEvent} object with the
     * specified source component,
     * type, time, modifiers, coordinates, click count, popupTrigger flag,
     * and button number.
     * <p>
     * Creating an invalid event (such
     * as by using more than one of the old _MASKs, or modifier/button
     * values which don't match) results in unspecified behavior.
     * An invocation of the form
     * {@code MouseEvent(source, id, when, modifiers, x, y, clickCount, popupTrigger, button)}
     * behaves in exactly the same way as the invocation
     * {@link #MouseEvent(Component, int, long, int, int, int,
     * int, int, int, boolean, int) MouseEvent(source, id, when, modifiers,
     * x, y, xAbs, yAbs, clickCount, popupTrigger, button)}
     * where xAbs and yAbs defines as source's location on screen plus
     * relative coordinates x and y.
     * xAbs and yAbs are set to zero if the source is not showing.
     * This method throws an
     * {@code IllegalArgumentException} if {@code source}
     * is {@code null}.
     *
     * @param source       The {@code Component} that originated the event
     * @param id              An integer indicating the type of event.
     *                     For information on allowable values, see
     *                     the class description for {@link MouseEvent}
     * @param when         A long integer that gives the time the event occurred.
     *                     Passing negative or zero value
     *                     is not recommended
     * @param modifiers    a modifier mask describing the modifier keys and mouse
     *                     buttons (for example, shift, ctrl, alt, and meta) that
     *                     are down during the event.
     *                     Only extended modifiers are allowed to be used as a
     *                     value for this parameter (see the {@link InputEvent#getModifiersEx}
     *                     class for the description of extended modifiers).
     *                     Passing negative parameter
     *                     is not recommended.
     *                     Zero value means that no modifiers were passed
     * @param x            The horizontal x coordinate for the mouse location.
     *                       It is allowed to pass negative values
     * @param y            The vertical y coordinate for the mouse location.
     *                       It is allowed to pass negative values
     * @param clickCount   The number of mouse clicks associated with event.
     *                       Passing negative value
     *                       is not recommended
     * @param popupTrigger A boolean that equals {@code true} if this event
     *                     is a trigger for a popup menu
     * @param button       An integer that indicates, which of the mouse buttons has
     *                     changed its state.
     * The following rules are applied to this parameter:
     * <ul>
     * <li>If support for the extended mouse buttons is
     * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * then it is allowed to create {@code MouseEvent} objects only with the standard buttons:
     * {@code NOBUTTON}, {@code BUTTON1}, {@code BUTTON2}, and
     * {@code BUTTON3}.
     * <li> If support for the extended mouse buttons is
     * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
     * then it is allowed to create {@code MouseEvent} objects with
     * the standard buttons.
     * In case the support for extended mouse buttons is
     * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java, then
     * in addition to the standard buttons, {@code MouseEvent} objects can be created
     * using buttons from the range starting from 4 to
     * {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}
     * if the mouse has more than three buttons.
     * </ul>
     * @throws IllegalArgumentException if {@code button} is less than zero
     * @throws IllegalArgumentException if {@code source} is null
     * @throws IllegalArgumentException if {@code button} is greater than BUTTON3
     *                                  and the support for extended mouse buttons is
     *                                  {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @throws IllegalArgumentException if {@code button} is greater than the
     *                                  {@link java.awt.MouseInfo#getNumberOfButtons() current number of buttons}
     *                                  and the support for extended mouse buttons is
     *                                  {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
     *                                  by Java
     * @throws IllegalArgumentException if an invalid {@code button}
     *            value is passed in
     * @throws IllegalArgumentException if {@code source} is null
     * @see #getSource()
     * @see #getID()
     * @see #getWhen()
     * @see #getModifiers()
     * @see #getX()
     * @see #getY()
     * @see #getClickCount()
     * @see #isPopupTrigger()
     * @see #getButton()
     * @since 1.4
     */
    public MouseEvent(Component source, int id, long when, int modifiers,
                      int x, int y, int clickCount, boolean popupTrigger,
                      int button)
    {
        this(source, id, when, modifiers, x, y, 0, 0, clickCount, popupTrigger, button);
        Point eventLocationOnScreen = new Point(0, 0);
        try {
          eventLocationOnScreen = source.getLocationOnScreen();
          this.xAbs = eventLocationOnScreen.x + x;
          this.yAbs = eventLocationOnScreen.y + y;
        } catch (IllegalComponentStateException e){
          this.xAbs = 0;
          this.yAbs = 0;
        }
    }

    /**
     * Constructs a {@code MouseEvent} object with the
     * specified source component,
     * type, modifiers, coordinates, click count, and popupTrigger flag.
     * An invocation of the form
     * {@code MouseEvent(source, id, when, modifiers, x, y, clickCount, popupTrigger)}
     * behaves in exactly the same way as the invocation
     * {@link #MouseEvent(Component, int, long, int, int, int,
     * int, int, int, boolean, int) MouseEvent(source, id, when, modifiers,
     * x, y, xAbs, yAbs, clickCount, popupTrigger, MouseEvent.NOBUTTON)}
     * where xAbs and yAbs defines as source's location on screen plus
     * relative coordinates x and y.
     * xAbs and yAbs are set to zero if the source is not showing.
     * This method throws an {@code IllegalArgumentException}
     * if {@code source} is {@code null}.
     *
     * @param source       The {@code Component} that originated the event
     * @param id              An integer indicating the type of event.
     *                     For information on allowable values, see
     *                     the class description for {@link MouseEvent}
     * @param when         A long integer that gives the time the event occurred.
     *                     Passing negative or zero value
     *                     is not recommended
     * @param modifiers    a modifier mask describing the modifier keys and mouse
     *                     buttons (for example, shift, ctrl, alt, and meta) that
     *                     are down during the event.
     *                     Only extended modifiers are allowed to be used as a
     *                     value for this parameter (see the {@link InputEvent#getModifiersEx}
     *                     class for the description of extended modifiers).
     *                     Passing negative parameter
     *                     is not recommended.
     *                     Zero value means that no modifiers were passed
     * @param x            The horizontal x coordinate for the mouse location.
     *                       It is allowed to pass negative values
     * @param y            The vertical y coordinate for the mouse location.
     *                       It is allowed to pass negative values
     * @param clickCount   The number of mouse clicks associated with event.
     *                       Passing negative value
     *                       is not recommended
     * @param popupTrigger A boolean that equals {@code true} if this event
     *                     is a trigger for a popup menu
     * @throws IllegalArgumentException if {@code source} is null
     * @see #getSource()
     * @see #getID()
     * @see #getWhen()
     * @see #getModifiers()
     * @see #getX()
     * @see #getY()
     * @see #getClickCount()
     * @see #isPopupTrigger()
     */
     public MouseEvent(Component source, int id, long when, int modifiers,
                      int x, int y, int clickCount, boolean popupTrigger) {
        this(source, id, when, modifiers, x, y, clickCount, popupTrigger, NOBUTTON);
     }


    /* if the button is an extra button and it is released or clicked then in Xsystem its state
       is not modified. Exclude this button number from ExtModifiers mask.*/
    private transient boolean shouldExcludeButtonFromExtModifiers = false;

    /**
     * {@inheritDoc}
     */
    public int getModifiersEx() {
        int tmpModifiers = modifiers;
        if (shouldExcludeButtonFromExtModifiers) {
            tmpModifiers &= ~(InputEvent.getMaskForButton(getButton()));
        }
        return tmpModifiers & ~JDK_1_3_MODIFIERS;
    }

    /**
     * Constructs a {@code MouseEvent} object with the
     * specified source component,
     * type, time, modifiers, coordinates, absolute coordinates, click count, popupTrigger flag,
     * and button number.
     * <p>
     * Creating an invalid event (such
     * as by using more than one of the old _MASKs, or modifier/button
     * values which don't match) results in unspecified behavior.
     * Even if inconsistent values for relative and absolute coordinates are
     * passed to the constructor, the mouse event instance is still
     * created and no exception is thrown.
     * This method throws an
     * {@code IllegalArgumentException} if {@code source}
     * is {@code null}.
     *
     * @param source       The {@code Component} that originated the event
     * @param id              An integer indicating the type of event.
     *                     For information on allowable values, see
     *                     the class description for {@link MouseEvent}
     * @param when         A long integer that gives the time the event occurred.
     *                     Passing negative or zero value
     *                     is not recommended
     * @param modifiers    a modifier mask describing the modifier keys and mouse
     *                     buttons (for example, shift, ctrl, alt, and meta) that
     *                     are down during the event.
     *                     Only extended modifiers are allowed to be used as a
     *                     value for this parameter (see the {@link InputEvent#getModifiersEx}
     *                     class for the description of extended modifiers).
     *                     Passing negative parameter
     *                     is not recommended.
     *                     Zero value means that no modifiers were passed
     * @param x            The horizontal x coordinate for the mouse location.
     *                       It is allowed to pass negative values
     * @param y            The vertical y coordinate for the mouse location.
     *                       It is allowed to pass negative values
     * @param xAbs           The absolute horizontal x coordinate for the mouse location
     *                       It is allowed to pass negative values
     * @param yAbs           The absolute vertical y coordinate for the mouse location
     *                       It is allowed to pass negative values
     * @param clickCount   The number of mouse clicks associated with event.
     *                       Passing negative value
     *                       is not recommended
     * @param popupTrigger A boolean that equals {@code true} if this event
     *                     is a trigger for a popup menu
     * @param button       An integer that indicates, which of the mouse buttons has
     *                     changed its state.
     * The following rules are applied to this parameter:
     * <ul>
     * <li>If support for the extended mouse buttons is
     * {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * then it is allowed to create {@code MouseEvent} objects only with the standard buttons:
     * {@code NOBUTTON}, {@code BUTTON1}, {@code BUTTON2}, and
     * {@code BUTTON3}.
     * <li> If support for the extended mouse buttons is
     * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java
     * then it is allowed to create {@code MouseEvent} objects with
     * the standard buttons.
     * In case the support for extended mouse buttons is
     * {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java, then
     * in addition to the standard buttons, {@code MouseEvent} objects can be created
     * using buttons from the range starting from 4 to
     * {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}
     * if the mouse has more than three buttons.
     * </ul>
     * @throws IllegalArgumentException if {@code button} is less than zero
     * @throws IllegalArgumentException if {@code source} is null
     * @throws IllegalArgumentException if {@code button} is greater than BUTTON3
     *                                  and the support for extended mouse buttons is
     *                                  {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @throws IllegalArgumentException if {@code button} is greater than the
     *                                  {@link java.awt.MouseInfo#getNumberOfButtons()
     *                                  current number of buttons} and the support
     *                                  for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled}
     *                                  by Java
     * @throws IllegalArgumentException if an invalid {@code button}
     *            value is passed in
     * @throws IllegalArgumentException if {@code source} is null
     * @see #getSource()
     * @see #getID()
     * @see #getWhen()
     * @see #getModifiers()
     * @see #getX()
     * @see #getY()
     * @see #getXOnScreen()
     * @see #getYOnScreen()
     * @see #getClickCount()
     * @see #isPopupTrigger()
     * @see #getButton()
     * @see Toolkit#areExtraMouseButtonsEnabled()
     * @see java.awt.MouseInfo#getNumberOfButtons()
     * @see InputEvent#getMaskForButton(int)
     * @since 1.6
     */
    @SuppressWarnings("deprecation")
    public MouseEvent(Component source, int id, long when, int modifiers,
                      int x, int y, int xAbs, int yAbs,
                      int clickCount, boolean popupTrigger, int button)
    {
        super(source, id, when, modifiers);
        this.x = x;
        this.y = y;
        this.xAbs = xAbs;
        this.yAbs = yAbs;
        this.clickCount = clickCount;
        this.popupTrigger = popupTrigger;
        if (button < NOBUTTON){
            throw new IllegalArgumentException("Invalid button value :" + button);
        }
        if (button > BUTTON3) {
            if (!Toolkit.getDefaultToolkit().areExtraMouseButtonsEnabled()){
                throw new IllegalArgumentException("Extra mouse events are disabled " + button);
            } else {
                if (button > cachedNumberOfButtons) {
                    throw new IllegalArgumentException("Nonexistent button " + button);
                }
            }
            // XToolkit: extra buttons are not reporting about their state correctly.
            // Being pressed they report the state=0 both on the press and on the release.
            // For 1-3 buttons the state value equals zero on press and non-zero on release.
            // Other modifiers like Shift, ALT etc seem report well with extra buttons.
            // The problem reveals as follows: one button is pressed and then another button is pressed and released.
            // So, the getModifiersEx() would not be zero due to a first button and we will skip this modifier.
            // This may have to be moved into the peer code instead if possible.

            if (getModifiersEx() != 0) { //There is at least one more button in a pressed state.
                if (id == MouseEvent.MOUSE_RELEASED || id == MouseEvent.MOUSE_CLICKED){
                    shouldExcludeButtonFromExtModifiers = true;
                }
            }
        }

        this.button = button;

        if ((getModifiers() != 0) && (getModifiersEx() == 0)) {
            setNewModifiers();
        } else if ((getModifiers() == 0) &&
                   (getModifiersEx() != 0 || button != NOBUTTON) &&
                   (button <= BUTTON3))
        {
            setOldModifiers();
        }
    }

    /**
     * Returns the horizontal x position of the event relative to the
     * source component.
     *
     * @return x  an integer indicating horizontal position relative to
     *            the component
     */
    public int getX() {
        return x;
    }

    /**
     * Returns the vertical y position of the event relative to the
     * source component.
     *
     * @return y  an integer indicating vertical position relative to
     *            the component
     */
    public int getY() {
        return y;
    }

    /**
     * Returns the x,y position of the event relative to the source component.
     *
     * @return a {@code Point} object containing the x and y coordinates
     *         relative to the source component
     *
     */
    public Point getPoint() {
        int x;
        int y;
        synchronized (this) {
            x = this.x;
            y = this.y;
        }
        return new Point(x, y);
    }

    /**
     * Translates the event's coordinates to a new position
     * by adding specified {@code x} (horizontal) and {@code y}
     * (vertical) offsets.
     *
     * @param x the horizontal x value to add to the current x
     *          coordinate position
     * @param y the vertical y value to add to the current y
                coordinate position
     */
    public synchronized void translatePoint(int x, int y) {
        this.x += x;
        this.y += y;
    }

    /**
     * Returns the number of mouse clicks associated with this event.
     *
     * @return integer value for the number of clicks
     */
    public int getClickCount() {
        return clickCount;
    }

    /**
     * Returns which, if any, of the mouse buttons has changed state.
     * The returned value is ranged
     * from 0 to the {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}
     * value.
     * The returned value includes at least the following constants:
     * <ul>
     * <li> {@code NOBUTTON}
     * <li> {@code BUTTON1}
     * <li> {@code BUTTON2}
     * <li> {@code BUTTON3}
     * </ul>
     * It is allowed to use those constants to compare with the returned button number in the application.
     * For example,
     * <pre>
     * if (anEvent.getButton() == MouseEvent.BUTTON1) {
     * </pre>
     * In particular, for a mouse with one, two, or three buttons this method may return the following values:
     * <ul>
     * <li> 0 ({@code NOBUTTON})
     * <li> 1 ({@code BUTTON1})
     * <li> 2 ({@code BUTTON2})
     * <li> 3 ({@code BUTTON3})
     * </ul>
     * Button numbers greater than {@code BUTTON3} have no constant identifier.
     * So if a mouse with five buttons is
     * installed, this method may return the following values:
     * <ul>
     * <li> 0 ({@code NOBUTTON})
     * <li> 1 ({@code BUTTON1})
     * <li> 2 ({@code BUTTON2})
     * <li> 3 ({@code BUTTON3})
     * <li> 4
     * <li> 5
     * </ul>
     * <p>
     * Note: If support for extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * then the AWT event subsystem does not produce mouse events for the extended mouse
     * buttons. So it is not expected that this method returns anything except {@code NOBUTTON}, {@code BUTTON1},
     * {@code BUTTON2}, {@code BUTTON3}.
     *
     * @return one of the values from 0 to {@link java.awt.MouseInfo#getNumberOfButtons() MouseInfo.getNumberOfButtons()}
     *         if support for the extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() enabled} by Java.
     *         That range includes {@code NOBUTTON}, {@code BUTTON1}, {@code BUTTON2}, {@code BUTTON3};
     *         <br>
     *         {@code NOBUTTON}, {@code BUTTON1}, {@code BUTTON2} or {@code BUTTON3}
     *         if support for the extended mouse buttons is {@link Toolkit#areExtraMouseButtonsEnabled() disabled} by Java
     * @since 1.4
     * @see Toolkit#areExtraMouseButtonsEnabled()
     * @see java.awt.MouseInfo#getNumberOfButtons()
     * @see #MouseEvent(Component, int, long, int, int, int, int, int, int, boolean, int)
     * @see InputEvent#getMaskForButton(int)
     */
    public int getButton() {
        return button;
    }

    /**
     * Returns whether or not this mouse event is the popup menu
     * trigger event for the platform.
     * <p><b>Note</b>: Popup menus are triggered differently
     * on different systems. Therefore, {@code isPopupTrigger}
     * should be checked in both {@code mousePressed}
     * and {@code mouseReleased}
     * for proper cross-platform functionality.
     *
     * @return boolean, true if this event is the popup menu trigger
     *         for this platform
     */
    public boolean isPopupTrigger() {
        return popupTrigger;
    }

    /**
     * Returns a {@code String} instance describing the modifier keys and
     * mouse buttons that were down during the event, such as "Shift",
     * or "Ctrl+Shift". These strings can be localized by changing
     * the {@code awt.properties} file.
     * <p>
     * Note that the {@code InputEvent.ALT_MASK} and
     * {@code InputEvent.BUTTON2_MASK} have equal values,
     * so the "Alt" string is returned for both modifiers.  Likewise,
     * the {@code InputEvent.META_MASK} and
     * {@code InputEvent.BUTTON3_MASK} have equal values,
     * so the "Meta" string is returned for both modifiers.
     * <p>
     * Note that passing negative parameter is incorrect,
     * and will cause the returning an unspecified string.
     * Zero parameter means that no modifiers were passed and will
     * cause the returning an empty string.
     *
     * @param modifiers A modifier mask describing the modifier keys and
     *                  mouse buttons that were down during the event
     * @return string   string text description of the combination of modifier
     *                  keys and mouse buttons that were down during the event
     * @see InputEvent#getModifiersExText(int)
     * @since 1.4
     */
    @SuppressWarnings("deprecation")
    public static String getMouseModifiersText(int modifiers) {
        StringBuilder buf = new StringBuilder();
        if ((modifiers & InputEvent.ALT_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.alt", "Alt"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.META_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.meta", "Meta"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.CTRL_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.control", "Ctrl"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.shift", "Shift"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.altGraph", "Alt Graph"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.BUTTON1_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.button1", "Button1"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.BUTTON2_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.button2", "Button2"));
            buf.append("+");
        }
        if ((modifiers & InputEvent.BUTTON3_MASK) != 0) {
            buf.append(Toolkit.getProperty("AWT.button3", "Button3"));
            buf.append("+");
        }

        int mask;

        // TODO: add a toolkit field that holds a number of button on the mouse.
        // As the method getMouseModifiersText() is static and obtain
        // an integer as a parameter then we may not restrict this with the number
        // of buttons installed on the mouse.
        // It's a temporary solution. We need to somehow hold the number of buttons somewhere else.
        for (int i = 1; i <= cachedNumberOfButtons; i++){
            mask = InputEvent.getMaskForButton(i);
            if ((modifiers & mask) != 0 &&
                buf.indexOf(Toolkit.getProperty("AWT.button"+i, "Button"+i)) == -1) //1,2,3 buttons may already be there; so don't duplicate it.
            {
                buf.append(Toolkit.getProperty("AWT.button"+i, "Button"+i));
                buf.append("+");
            }
        }

        if (buf.length() > 0) {
            buf.setLength(buf.length()-1); // remove trailing '+'
        }
        return buf.toString();
    }

    /**
     * Returns a parameter string identifying this event.
     * This method is useful for event-logging and for debugging.
     *
     * @return a string identifying the event and its attributes
     */
    @SuppressWarnings("deprecation")
    public String paramString() {
        StringBuilder str = new StringBuilder(80);

        switch(id) {
          case MOUSE_PRESSED:
              str.append("MOUSE_PRESSED");
              break;
          case MOUSE_RELEASED:
              str.append("MOUSE_RELEASED");
              break;
          case MOUSE_CLICKED:
              str.append("MOUSE_CLICKED");
              break;
          case MOUSE_ENTERED:
              str.append("MOUSE_ENTERED");
              break;
          case MOUSE_EXITED:
              str.append("MOUSE_EXITED");
              break;
          case MOUSE_MOVED:
              str.append("MOUSE_MOVED");
              break;
          case MOUSE_DRAGGED:
              str.append("MOUSE_DRAGGED");
              break;
          case MOUSE_WHEEL:
              str.append("MOUSE_WHEEL");
              break;
           default:
              str.append("unknown type");
        }

        // (x,y) coordinates
        str.append(",(").append(x).append(",").append(y).append(")");
        str.append(",absolute(").append(xAbs).append(",").append(yAbs).append(")");

        if (id != MOUSE_DRAGGED && id != MOUSE_MOVED){
            str.append(",button=").append(getButton());
        }

        if (getModifiers() != 0) {
            str.append(",modifiers=").append(getMouseModifiersText(modifiers));
        }

        if (getModifiersEx() != 0) {
            //Using plain "modifiers" here does show an excluded extended buttons in the string event representation.
            //getModifiersEx() solves the problem.
            str.append(",extModifiers=").append(getModifiersExText(getModifiersEx()));
        }

        str.append(",clickCount=").append(clickCount);

        return str.toString();
    }

    /**
     * Sets new modifiers by the old ones.
     * Also sets button.
     */
    @SuppressWarnings("deprecation")
    private void setNewModifiers() {
        if ((modifiers & BUTTON1_MASK) != 0) {
            modifiers |= BUTTON1_DOWN_MASK;
        }
        if ((modifiers & BUTTON2_MASK) != 0) {
            modifiers |= BUTTON2_DOWN_MASK;
        }
        if ((modifiers & BUTTON3_MASK) != 0) {
            modifiers |= BUTTON3_DOWN_MASK;
        }
        if (id == MOUSE_PRESSED
            || id == MOUSE_RELEASED
            || id == MOUSE_CLICKED)
        {
            if ((modifiers & BUTTON1_MASK) != 0) {
                button = BUTTON1;
                modifiers &= ~BUTTON2_MASK & ~BUTTON3_MASK;
                if (id != MOUSE_PRESSED) {
                    modifiers &= ~BUTTON1_DOWN_MASK;
                }
            } else if ((modifiers & BUTTON2_MASK) != 0) {
                button = BUTTON2;
                modifiers &= ~BUTTON1_MASK & ~BUTTON3_MASK;
                if (id != MOUSE_PRESSED) {
                    modifiers &= ~BUTTON2_DOWN_MASK;
                }
            } else if ((modifiers & BUTTON3_MASK) != 0) {
                button = BUTTON3;
                modifiers &= ~BUTTON1_MASK & ~BUTTON2_MASK;
                if (id != MOUSE_PRESSED) {
                    modifiers &= ~BUTTON3_DOWN_MASK;
                }
            }
        }
        if ((modifiers & InputEvent.ALT_MASK) != 0) {
            modifiers |= InputEvent.ALT_DOWN_MASK;
        }
        if ((modifiers & InputEvent.META_MASK) != 0) {
            modifiers |= InputEvent.META_DOWN_MASK;
        }
        if ((modifiers & InputEvent.SHIFT_MASK) != 0) {
            modifiers |= InputEvent.SHIFT_DOWN_MASK;
        }
        if ((modifiers & InputEvent.CTRL_MASK) != 0) {
            modifiers |= InputEvent.CTRL_DOWN_MASK;
        }
        if ((modifiers & InputEvent.ALT_GRAPH_MASK) != 0) {
            modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
        }
    }

    /**
     * Sets old modifiers by the new ones.
     */
    @SuppressWarnings("deprecation")
    private void setOldModifiers() {
        if (id == MOUSE_PRESSED
            || id == MOUSE_RELEASED
            || id == MOUSE_CLICKED)
        {
            switch(button) {
            case BUTTON1:
                modifiers |= BUTTON1_MASK;
                break;
            case BUTTON2:
                modifiers |= BUTTON2_MASK;
                break;
            case BUTTON3:
                modifiers |= BUTTON3_MASK;
                break;
            }
        } else {
            if ((modifiers & BUTTON1_DOWN_MASK) != 0) {
                modifiers |= BUTTON1_MASK;
            }
            if ((modifiers & BUTTON2_DOWN_MASK) != 0) {
                modifiers |= BUTTON2_MASK;
            }
            if ((modifiers & BUTTON3_DOWN_MASK) != 0) {
                modifiers |= BUTTON3_MASK;
            }
        }
        if ((modifiers & ALT_DOWN_MASK) != 0) {
            modifiers |= ALT_MASK;
        }
        if ((modifiers & META_DOWN_MASK) != 0) {
            modifiers |= META_MASK;
        }
        if ((modifiers & SHIFT_DOWN_MASK) != 0) {
            modifiers |= SHIFT_MASK;
        }
        if ((modifiers & CTRL_DOWN_MASK) != 0) {
            modifiers |= CTRL_MASK;
        }
        if ((modifiers & ALT_GRAPH_DOWN_MASK) != 0) {
            modifiers |= ALT_GRAPH_MASK;
        }
    }

    /**
     * Sets new modifiers by the old ones.
     *
     * @param  s the {@code ObjectInputStream} to read
     * @throws ClassNotFoundException if the class of a serialized object could
     *         not be found
     * @throws IOException if an I/O error occurs
     */
    @Serial
    @SuppressWarnings("deprecation")
    private void readObject(ObjectInputStream s)
      throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        if (getModifiers() != 0 && getModifiersEx() == 0) {
            setNewModifiers();
        }
    }
}
