src/macosx/classes/sun/lwawt/LWWindowPeer.java

Print this page




  71 
  72     private GraphicsDevice graphicsDevice;
  73     private GraphicsConfiguration graphicsConfig;
  74 
  75     private SurfaceData surfaceData;
  76     private final Object surfaceDataLock = new Object();
  77 
  78     private int backBufferCount;
  79     private BufferCapabilities backBufferCaps;
  80 
  81     // The back buffer is used for two purposes:
  82     // 1. To render all the lightweight peers
  83     // 2. To provide user with a BufferStrategy
  84     // Need to check if a single back buffer can be used for both
  85 // TODO: VolatileImage
  86 //    private VolatileImage backBuffer;
  87     private volatile BufferedImage backBuffer;
  88 
  89     private volatile int windowState = Frame.NORMAL;
  90 
  91     // A peer where the last mouse event came to. Used to generate
  92     // MOUSE_ENTERED/EXITED notifications and by cursor manager to


  93     // find the component under cursor
  94     private static volatile LWComponentPeer lastMouseEventPeer = null;




  95 
  96     // Peers where all dragged/released events should come to,
  97     // depending on what mouse button is being dragged according to Cocoa
  98     private static LWComponentPeer mouseDownTarget[] = new LWComponentPeer[3];
  99 
 100     // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events
 101     // on MOUSE_RELEASE. Click events are only generated if there were no drag
 102     // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button
 103     private static int mouseClickButtons = 0;
 104 
 105     private volatile boolean isOpaque = true;
 106 
 107     private static final Font DEFAULT_FONT = new Font("Lucida Grande", Font.PLAIN, 13);
 108 
 109     private static LWWindowPeer grabbingWindow;
 110 
 111     private volatile boolean skipNextFocusChange;
 112 
 113     private static final Color nonOpaqueBackground = new Color(0, 0, 0, 0);
 114 


 659             grabbingWindow.ungrab();
 660         }
 661     }
 662 
 663     // ---- EVENTS ---- //
 664 
 665     /*
 666      * Called by the delegate to dispatch the event to Java. Event
 667      * coordinates are relative to non-client window are, i.e. the top-left
 668      * point of the client area is (insets.top, insets.left).
 669      */
 670     public void dispatchMouseEvent(int id, long when, int button,
 671                                    int x, int y, int screenX, int screenY,
 672                                    int modifiers, int clickCount, boolean popupTrigger,
 673                                    byte[] bdata)
 674     {
 675         // TODO: fill "bdata" member of AWTEvent
 676         Rectangle r = getBounds();
 677         // findPeerAt() expects parent coordinates
 678         LWComponentPeer targetPeer = findPeerAt(r.x + x, r.y + y);
 679         LWWindowPeer lastWindowPeer =
 680             (lastMouseEventPeer != null) ? lastMouseEventPeer.getWindowPeerOrSelf() : null;
 681         LWWindowPeer curWindowPeer =
 682             (targetPeer != null) ? targetPeer.getWindowPeerOrSelf() : null;
 683 
 684         if (id == MouseEvent.MOUSE_EXITED) {
 685             // Sometimes we may get MOUSE_EXITED after lastMouseEventPeer is switched
 686             // to a peer from another window. So we must first check if this peer is
 687             // the same as lastWindowPeer
 688             if (lastWindowPeer == this) {
 689                 if (isEnabled()) {
 690                     Point lp = lastMouseEventPeer.windowToLocal(x, y,
 691                                                                 lastWindowPeer);
 692                     postEvent(new MouseEvent(lastMouseEventPeer.getTarget(),
 693                                              MouseEvent.MOUSE_EXITED, when,
 694                                              modifiers, lp.x, lp.y, screenX,
 695                                              screenY, clickCount, popupTrigger,
 696                                              button));
 697                 }
 698                 lastMouseEventPeer = null;
 699             }
 700         } else {
 701             if (targetPeer != lastMouseEventPeer) {
 702 
 703                 if (id != MouseEvent.MOUSE_DRAGGED || lastMouseEventPeer == null) {
 704                     // lastMouseEventPeer may be null if mouse was out of Java windows
 705                     if (lastMouseEventPeer != null && lastMouseEventPeer.isEnabled()) {
 706                         // Sometimes, MOUSE_EXITED is not sent by delegate (or is sent a bit
 707                         // later), in which case lastWindowPeer is another window
 708                         if (lastWindowPeer != this) {
 709                             Point oldp = lastMouseEventPeer.windowToLocal(x, y, lastWindowPeer);
 710                             // Additionally translate from this to lastWindowPeer coordinates
 711                             Rectangle lr = lastWindowPeer.getBounds();
 712                             oldp.x += r.x - lr.x;
 713                             oldp.y += r.y - lr.y;
 714                             postEvent(new MouseEvent(lastMouseEventPeer.getTarget(),
 715                                                      MouseEvent.MOUSE_EXITED,
 716                                                      when, modifiers,
 717                                                      oldp.x, oldp.y, screenX, screenY,
 718                                                      clickCount, popupTrigger, button));
 719                         } else {
 720                             Point oldp = lastMouseEventPeer.windowToLocal(x, y, this);
 721                             postEvent(new MouseEvent(lastMouseEventPeer.getTarget(),
 722                                                      MouseEvent.MOUSE_EXITED,
 723                                                      when, modifiers,
 724                                                      oldp.x, oldp.y, screenX, screenY,
 725                                                      clickCount, popupTrigger, button));
 726                         }

 727                     }
 728                     if (targetPeer != null && targetPeer.isEnabled() && id != MouseEvent.MOUSE_ENTERED) {
 729                         Point newp = targetPeer.windowToLocal(x, y, curWindowPeer);



 730                         postEvent(new MouseEvent(targetPeer.getTarget(),
 731                                                  MouseEvent.MOUSE_ENTERED,
 732                                                  when, modifiers,
 733                                                  newp.x, newp.y, screenX, screenY,
 734                                                  clickCount, popupTrigger, button));
 735                     }
 736                 }

 737                 lastMouseEventPeer = targetPeer;
 738             }























 739             // TODO: fill "bdata" member of AWTEvent
 740 
 741             int eventButtonMask = (button > 0)? MouseEvent.getMaskForButton(button) : 0;
 742             int otherButtonsPressed = modifiers & ~eventButtonMask;
 743 
 744             // For pressed/dragged/released events OS X treats other
 745             // mouse buttons as if they were BUTTON2, so we do the same
 746             int targetIdx = (button > 3) ? MouseEvent.BUTTON2 - 1 : button - 1;
 747 
 748             // MOUSE_ENTERED/EXITED are generated for the components strictly under
 749             // mouse even when dragging. That's why we first update lastMouseEventPeer
 750             // based on initial targetPeer value and only then recalculate targetPeer
 751             // for MOUSE_DRAGGED/RELEASED events
 752             if (id == MouseEvent.MOUSE_PRESSED) {
 753 
 754                 // Ungrab only if this window is not an owned window of the grabbing one.
 755                 if (!isGrabbing() && grabbingWindow != null &&
 756                     grabbingWindow != getOwnerFrameDialog(this))
 757                 {
 758                     grabbingWindow.ungrab();


 768                 // Cocoa dragged event has the information about which mouse
 769                 // button is being dragged. Use it to determine the peer that
 770                 // should receive the dragged event.
 771                 targetPeer = mouseDownTarget[targetIdx];
 772                 mouseClickButtons &= ~modifiers;
 773             } else if (id == MouseEvent.MOUSE_RELEASED) {
 774                 // TODO: currently, mouse released event goes to the same component
 775                 // that received corresponding mouse pressed event. For most cases,
 776                 // it's OK, however, we need to make sure that our behavior is consistent
 777                 // with 1.6 for cases where component in question have been
 778                 // hidden/removed in between of mouse pressed/released events.
 779                 targetPeer = mouseDownTarget[targetIdx];
 780 
 781                 if ((modifiers & eventButtonMask) == 0) {
 782                     mouseDownTarget[targetIdx] = null;
 783                 }
 784 
 785                 // mouseClickButtons is updated below, after MOUSE_CLICK is sent
 786             }
 787 
 788             // check if we receive mouseEvent from outside the window's bounds
 789             // it can be either mouseDragged or mouseReleased
 790             if (curWindowPeer == null) {
 791                 //TODO This can happen if this window is invisible. this is correct behavior in this case?
 792                 curWindowPeer = this;
 793             }
 794             if (targetPeer == null) {
 795                 //TODO This can happen if this window is invisible. this is correct behavior in this case?
 796                 targetPeer = this;
 797             }
 798 
 799 
 800             Point lp = targetPeer.windowToLocal(x, y, curWindowPeer);
 801             if (targetPeer.isEnabled()) {
 802                 MouseEvent event = new MouseEvent(targetPeer.getTarget(), id,
 803                                                   when, modifiers, lp.x, lp.y,
 804                                                   screenX, screenY, clickCount,
 805                                                   popupTrigger, button);
 806                 postEvent(event);
 807             }
 808 
 809             if (id == MouseEvent.MOUSE_RELEASED) {
 810                 if ((mouseClickButtons & eventButtonMask) != 0
 811                     && targetPeer.isEnabled()) {
 812                     postEvent(new MouseEvent(targetPeer.getTarget(),
 813                                              MouseEvent.MOUSE_CLICKED,
 814                                              when, modifiers,
 815                                              lp.x, lp.y, screenX, screenY,
 816                                              clickCount, popupTrigger, button));
 817                 }
 818                 mouseClickButtons &= ~eventButtonMask;
 819             }
 820         }
 821         notifyUpdateCursor();
 822     }
 823 
































 824     public void dispatchMouseWheelEvent(long when, int x, int y, int modifiers,
 825                                         int scrollType, int scrollAmount,
 826                                         int wheelRotation, double preciseWheelRotation,
 827                                         byte[] bdata)
 828     {
 829         // TODO: could we just use the last mouse event target here?
 830         Rectangle r = getBounds();
 831         // findPeerAt() expects parent coordinates
 832         final LWComponentPeer targetPeer = findPeerAt(r.x + x, r.y + y);
 833         if (targetPeer == null || !targetPeer.isEnabled()) {
 834             return;
 835         }
 836 
 837         Point lp = targetPeer.windowToLocal(x, y, this);
 838         // TODO: fill "bdata" member of AWTEvent
 839         // TODO: screenX/screenY
 840         postEvent(new MouseWheelEvent(targetPeer.getTarget(),
 841                                       MouseEvent.MOUSE_WHEEL,
 842                                       when, modifiers,
 843                                       lp.x, lp.y,


1048      * should be recalculated.
1049      *
1050      * This method may be called on the toolkit thread.
1051      */
1052     public boolean updateInsets(Insets newInsets) {
1053         boolean changed = false;
1054         synchronized (getStateLock()) {
1055             changed = (insets.equals(newInsets));
1056             insets = newInsets;
1057         }
1058 
1059         if (changed) {
1060             replaceSurfaceData();
1061             repaintPeer();
1062         }
1063 
1064         return changed;
1065     }
1066 
1067     public static LWWindowPeer getWindowUnderCursor() {
1068         return lastMouseEventPeer != null ? lastMouseEventPeer.getWindowPeerOrSelf() : null;
1069     }
1070 
1071     public static LWComponentPeer<?, ?> getPeerUnderCursor() {
1072         return lastMouseEventPeer;
1073     }
1074 
1075     /*
1076      * Requests platform to set native focus on a frame/dialog.
1077      * In case of a simple window, triggers appropriate java focus change.
1078      */
1079     public boolean requestWindowFocus(CausedFocusEvent.Cause cause) {
1080         if (focusLog.isLoggable(PlatformLogger.FINE)) {
1081             focusLog.fine("requesting native focus to " + this);
1082         }
1083 
1084         if (!focusAllowedFor()) {
1085             focusLog.fine("focus is not allowed");
1086             return false;
1087         }
1088 
1089         if (platformWindow.rejectFocusRequest(cause)) {
1090             return false;
1091         }
1092 




  71 
  72     private GraphicsDevice graphicsDevice;
  73     private GraphicsConfiguration graphicsConfig;
  74 
  75     private SurfaceData surfaceData;
  76     private final Object surfaceDataLock = new Object();
  77 
  78     private int backBufferCount;
  79     private BufferCapabilities backBufferCaps;
  80 
  81     // The back buffer is used for two purposes:
  82     // 1. To render all the lightweight peers
  83     // 2. To provide user with a BufferStrategy
  84     // Need to check if a single back buffer can be used for both
  85 // TODO: VolatileImage
  86 //    private VolatileImage backBuffer;
  87     private volatile BufferedImage backBuffer;
  88 
  89     private volatile int windowState = Frame.NORMAL;
  90 
  91     // check that the mouse is over the window
  92     private volatile boolean isMouseOver = false;
  93 
  94     // A peer where the last mouse event came to. Used by cursor manager to
  95     // find the component under cursor
  96     private static volatile LWComponentPeer lastCommonMouseEventPeer = null;
  97 
  98     // A peer where the last mouse event came to. Used to generate
  99     // MOUSE_ENTERED/EXITED notifications
 100     private volatile LWComponentPeer lastMouseEventPeer;
 101 
 102     // Peers where all dragged/released events should come to,
 103     // depending on what mouse button is being dragged according to Cocoa
 104     private static LWComponentPeer mouseDownTarget[] = new LWComponentPeer[3];
 105 
 106     // A bitmask that indicates what mouse buttons produce MOUSE_CLICKED events
 107     // on MOUSE_RELEASE. Click events are only generated if there were no drag
 108     // events between MOUSE_PRESSED and MOUSE_RELEASED for particular button
 109     private static int mouseClickButtons = 0;
 110 
 111     private volatile boolean isOpaque = true;
 112 
 113     private static final Font DEFAULT_FONT = new Font("Lucida Grande", Font.PLAIN, 13);
 114 
 115     private static LWWindowPeer grabbingWindow;
 116 
 117     private volatile boolean skipNextFocusChange;
 118 
 119     private static final Color nonOpaqueBackground = new Color(0, 0, 0, 0);
 120 


 665             grabbingWindow.ungrab();
 666         }
 667     }
 668 
 669     // ---- EVENTS ---- //
 670 
 671     /*
 672      * Called by the delegate to dispatch the event to Java. Event
 673      * coordinates are relative to non-client window are, i.e. the top-left
 674      * point of the client area is (insets.top, insets.left).
 675      */
 676     public void dispatchMouseEvent(int id, long when, int button,
 677                                    int x, int y, int screenX, int screenY,
 678                                    int modifiers, int clickCount, boolean popupTrigger,
 679                                    byte[] bdata)
 680     {
 681         // TODO: fill "bdata" member of AWTEvent
 682         Rectangle r = getBounds();
 683         // findPeerAt() expects parent coordinates
 684         LWComponentPeer targetPeer = findPeerAt(r.x + x, r.y + y);




 685 
 686         if (id == MouseEvent.MOUSE_EXITED) {
 687             isMouseOver = false;
 688             if (lastMouseEventPeer != null) {
 689                 if (lastMouseEventPeer.isEnabled()) {


 690                     Point lp = lastMouseEventPeer.windowToLocal(x, y,
 691                             this);
 692                     postEvent(new MouseEvent(lastMouseEventPeer.getTarget(),
 693                             MouseEvent.MOUSE_EXITED, when,
 694                             modifiers, lp.x, lp.y, screenX,
 695                             screenY, clickCount, popupTrigger,
 696                             button));
 697                 }




 698 
 699                 // Sometimes we may get MOUSE_EXITED after lastCommonMouseEventPeer is switched
 700                 // to a peer from another window. So we must first check if this peer is
 701                 // the same as lastWindowPeer
 702                 if (lastCommonMouseEventPeer != null && lastCommonMouseEventPeer.getWindowPeerOrSelf() == this) {
 703                     lastCommonMouseEventPeer = null;


















 704                 }
 705                 lastMouseEventPeer = null;
 706             }
 707         } else if(id == MouseEvent.MOUSE_ENTERED) {
 708             isMouseOver = true;
 709             if (targetPeer != null) {
 710                 if (targetPeer.isEnabled()) {
 711                     Point lp = targetPeer.windowToLocal(x, y, this);
 712                         postEvent(new MouseEvent(targetPeer.getTarget(),
 713                             MouseEvent.MOUSE_ENTERED, when,
 714                             modifiers, lp.x, lp.y, screenX,
 715                             screenY, clickCount, popupTrigger,
 716                             button));

 717                 }
 718                 lastCommonMouseEventPeer = targetPeer;
 719                 lastMouseEventPeer = targetPeer;
 720             }
 721         } else {
 722             PlatformWindow topmostPlatforWindow =
 723                     platformWindow.getTopmostPlatformWindowUnderMouse();
 724 
 725             LWWindowPeer topmostWindowPeer =
 726                     topmostPlatforWindow != null ? topmostPlatforWindow.getPeer() : null;
 727 
 728             // topmostWindowPeer == null condition is added for the backward
 729             // compatibility with applets. It can be removed when the
 730             // getTopmostPlatformWindowUnderMouse() method will be properly
 731             // implemented in CPlatformEmbeddedFrame class
 732             if (topmostWindowPeer == this || topmostWindowPeer == null) {
 733                 generateMouseEnterExitEventsForComponents(when, button, x, y,
 734                         screenX, screenY, modifiers, clickCount, popupTrigger,
 735                         targetPeer);
 736             } else {
 737                 LWComponentPeer topmostTargetPeer =
 738                         topmostWindowPeer != null ? topmostWindowPeer.findPeerAt(r.x + x, r.y + y) : null;
 739                 topmostWindowPeer.generateMouseEnterExitEventsForComponents(when, button, x, y,
 740                         screenX, screenY, modifiers, clickCount, popupTrigger,
 741                         topmostTargetPeer);
 742             }
 743 
 744             // TODO: fill "bdata" member of AWTEvent
 745 
 746             int eventButtonMask = (button > 0)? MouseEvent.getMaskForButton(button) : 0;
 747             int otherButtonsPressed = modifiers & ~eventButtonMask;
 748 
 749             // For pressed/dragged/released events OS X treats other
 750             // mouse buttons as if they were BUTTON2, so we do the same
 751             int targetIdx = (button > 3) ? MouseEvent.BUTTON2 - 1 : button - 1;
 752 
 753             // MOUSE_ENTERED/EXITED are generated for the components strictly under
 754             // mouse even when dragging. That's why we first update lastMouseEventPeer
 755             // based on initial targetPeer value and only then recalculate targetPeer
 756             // for MOUSE_DRAGGED/RELEASED events
 757             if (id == MouseEvent.MOUSE_PRESSED) {
 758 
 759                 // Ungrab only if this window is not an owned window of the grabbing one.
 760                 if (!isGrabbing() && grabbingWindow != null &&
 761                     grabbingWindow != getOwnerFrameDialog(this))
 762                 {
 763                     grabbingWindow.ungrab();


 773                 // Cocoa dragged event has the information about which mouse
 774                 // button is being dragged. Use it to determine the peer that
 775                 // should receive the dragged event.
 776                 targetPeer = mouseDownTarget[targetIdx];
 777                 mouseClickButtons &= ~modifiers;
 778             } else if (id == MouseEvent.MOUSE_RELEASED) {
 779                 // TODO: currently, mouse released event goes to the same component
 780                 // that received corresponding mouse pressed event. For most cases,
 781                 // it's OK, however, we need to make sure that our behavior is consistent
 782                 // with 1.6 for cases where component in question have been
 783                 // hidden/removed in between of mouse pressed/released events.
 784                 targetPeer = mouseDownTarget[targetIdx];
 785 
 786                 if ((modifiers & eventButtonMask) == 0) {
 787                     mouseDownTarget[targetIdx] = null;
 788                 }
 789 
 790                 // mouseClickButtons is updated below, after MOUSE_CLICK is sent
 791             }
 792 






 793             if (targetPeer == null) {
 794                 //TODO This can happen if this window is invisible. this is correct behavior in this case?
 795                 targetPeer = this;
 796             }
 797 
 798 
 799             Point lp = targetPeer.windowToLocal(x, y, this);
 800             if (targetPeer.isEnabled()) {
 801                 MouseEvent event = new MouseEvent(targetPeer.getTarget(), id,
 802                                                   when, modifiers, lp.x, lp.y,
 803                                                   screenX, screenY, clickCount,
 804                                                   popupTrigger, button);
 805                 postEvent(event);
 806             }
 807 
 808             if (id == MouseEvent.MOUSE_RELEASED) {
 809                 if ((mouseClickButtons & eventButtonMask) != 0
 810                     && targetPeer.isEnabled()) {
 811                     postEvent(new MouseEvent(targetPeer.getTarget(),
 812                                              MouseEvent.MOUSE_CLICKED,
 813                                              when, modifiers,
 814                                              lp.x, lp.y, screenX, screenY,
 815                                              clickCount, popupTrigger, button));
 816                 }
 817                 mouseClickButtons &= ~eventButtonMask;
 818             }
 819         }
 820         notifyUpdateCursor();
 821     }
 822 
 823     private void generateMouseEnterExitEventsForComponents(long when,
 824             int button, int x, int y, int screenX, int screenY,
 825             int modifiers, int clickCount, boolean popupTrigger,
 826             LWComponentPeer targetPeer) {
 827 
 828         if (!isMouseOver || targetPeer == lastMouseEventPeer) {
 829             return;
 830         }
 831 
 832         // Generate Mouse Exit for components
 833         if (lastMouseEventPeer != null && lastMouseEventPeer.isEnabled()) {
 834             Point oldp = lastMouseEventPeer.windowToLocal(x, y, this);
 835             postEvent(new MouseEvent(lastMouseEventPeer.getTarget(),
 836                     MouseEvent.MOUSE_EXITED,
 837                     when, modifiers,
 838                     oldp.x, oldp.y, screenX, screenY,
 839                     clickCount, popupTrigger, button));
 840         }
 841         lastCommonMouseEventPeer = targetPeer;
 842         lastMouseEventPeer = targetPeer;
 843 
 844         // Generate Mouse Enter for components
 845         if (targetPeer != null && targetPeer.isEnabled()) {
 846             Point newp = targetPeer.windowToLocal(x, y, this);
 847             postEvent(new MouseEvent(targetPeer.getTarget(),
 848                     MouseEvent.MOUSE_ENTERED,
 849                     when, modifiers,
 850                     newp.x, newp.y, screenX, screenY,
 851                     clickCount, popupTrigger, button));
 852         }
 853     }
 854 
 855     public void dispatchMouseWheelEvent(long when, int x, int y, int modifiers,
 856                                         int scrollType, int scrollAmount,
 857                                         int wheelRotation, double preciseWheelRotation,
 858                                         byte[] bdata)
 859     {
 860         // TODO: could we just use the last mouse event target here?
 861         Rectangle r = getBounds();
 862         // findPeerAt() expects parent coordinates
 863         final LWComponentPeer targetPeer = findPeerAt(r.x + x, r.y + y);
 864         if (targetPeer == null || !targetPeer.isEnabled()) {
 865             return;
 866         }
 867 
 868         Point lp = targetPeer.windowToLocal(x, y, this);
 869         // TODO: fill "bdata" member of AWTEvent
 870         // TODO: screenX/screenY
 871         postEvent(new MouseWheelEvent(targetPeer.getTarget(),
 872                                       MouseEvent.MOUSE_WHEEL,
 873                                       when, modifiers,
 874                                       lp.x, lp.y,


1079      * should be recalculated.
1080      *
1081      * This method may be called on the toolkit thread.
1082      */
1083     public boolean updateInsets(Insets newInsets) {
1084         boolean changed = false;
1085         synchronized (getStateLock()) {
1086             changed = (insets.equals(newInsets));
1087             insets = newInsets;
1088         }
1089 
1090         if (changed) {
1091             replaceSurfaceData();
1092             repaintPeer();
1093         }
1094 
1095         return changed;
1096     }
1097 
1098     public static LWWindowPeer getWindowUnderCursor() {
1099         return lastCommonMouseEventPeer != null ? lastCommonMouseEventPeer.getWindowPeerOrSelf() : null;
1100     }
1101 
1102     public static LWComponentPeer<?, ?> getPeerUnderCursor() {
1103         return lastCommonMouseEventPeer;
1104     }
1105 
1106     /*
1107      * Requests platform to set native focus on a frame/dialog.
1108      * In case of a simple window, triggers appropriate java focus change.
1109      */
1110     public boolean requestWindowFocus(CausedFocusEvent.Cause cause) {
1111         if (focusLog.isLoggable(PlatformLogger.FINE)) {
1112             focusLog.fine("requesting native focus to " + this);
1113         }
1114 
1115         if (!focusAllowedFor()) {
1116             focusLog.fine("focus is not allowed");
1117             return false;
1118         }
1119 
1120         if (platformWindow.rejectFocusRequest(cause)) {
1121             return false;
1122         }
1123