001package jmri.util;
002
003import java.awt.Component;
004import java.awt.Container;
005import java.awt.Cursor;
006import java.awt.Point;
007import java.awt.Rectangle;
008import java.awt.event.MouseEvent;
009import java.util.List;
010
011import javax.swing.JComponent;
012import javax.swing.JFrame;
013import javax.swing.SwingUtilities;
014import javax.swing.event.MouseInputAdapter;
015
016import jmri.util.swing.JmriMouseEvent;
017
018/**
019 * Used to intercept inputs and to display a busy cursor during reads and
020 * writes.
021 *
022 * Based in part on code from the Java Tutorial for glass panes (java.sun.com).
023 *
024 * Used in PaneProgFrame to control cursor operations during programming.
025 *
026 * @author Howard G. Penny Copyright (C) 2005
027 */
028public class BusyGlassPane extends JComponent {
029
030    CBListener listener;
031
032    public BusyGlassPane(List<JComponent> components, List<Rectangle> rectangles, Container contentPane, JFrame parent) {
033        listener = new CBListener(components, rectangles, this, contentPane, parent);
034        addMouseListener(listener);
035        addMouseMotionListener(listener);
036    }
037
038    public void dispose() {
039        this.removeMouseListener(listener);
040        this.removeMouseMotionListener(listener);
041    }
042
043    /**
044     * Listen for all events that our components are likely to be interested in.
045     * Redispatch them to the appropriate component.
046     */
047    static class CBListener extends MouseInputAdapter {
048
049        JFrame parentFrame;
050        List<JComponent> liveComponents;
051        List<Rectangle> liveRectangles;
052        BusyGlassPane glassPane;
053        Container contentPane;
054        boolean inDrag = false;
055
056        public CBListener(List<JComponent> objects, List<Rectangle> rectangles,
057                BusyGlassPane glassPane, Container contentPane, JFrame parent) {
058            this.parentFrame = parent;
059            this.liveComponents = objects;
060            this.liveRectangles = rectangles;
061            this.glassPane = glassPane;
062            this.contentPane = contentPane;
063        }
064
065        @Override
066        public void mouseMoved(MouseEvent e) {
067            redispatchMouseEvent(e);
068        }
069
070        /*
071         * We must forward at least the mouse drags that started
072         * with mouse presses over the button.  Otherwise,
073         * when the user presses the button then drags off,
074         * the button isn't disarmed -- it keeps its dark
075         * gray background or whatever its L&F uses to indicate
076         * that the button is currently being pressed.
077         */
078        @Override
079        public void mouseDragged(MouseEvent e) {
080            redispatchMouseEvent(e);
081        }
082
083        @Override
084        public void mouseClicked(MouseEvent e) {
085            redispatchMouseEvent(e);
086        }
087
088        @Override
089        public void mouseEntered(MouseEvent e) {
090            redispatchMouseEvent(e);
091        }
092
093        @Override
094        public void mouseExited(MouseEvent e) {
095            redispatchMouseEvent(e);
096        }
097
098        @Override
099        public void mousePressed(MouseEvent e) {
100            redispatchMouseEvent(e);
101        }
102
103        @Override
104        public void mouseReleased(MouseEvent e) {
105            redispatchMouseEvent(e);
106            inDrag = false;
107        }
108
109        @SuppressWarnings("deprecation") // InputEvent.getModifiers
110        private void redispatchMouseEvent(MouseEvent e) {
111            boolean inButton = false;
112            Point glassPanePoint = e.getPoint();
113            Component component = null;
114            Container container = contentPane;
115            Point containerPoint = SwingUtilities.convertPoint(glassPane,
116                    glassPanePoint,
117                    contentPane);
118            int eventID = e.getID();
119
120            //XXX: If the event is from a component in a popped-up menu,
121            //XXX: then the container should probably be the menu's
122            //XXX: JPopupMenu, and containerPoint should be adjusted
123            //XXX: accordingly.
124            component = SwingUtilities.getDeepestComponentAt(container,
125                    containerPoint.x,
126                    containerPoint.y);
127
128            if (component == null) {
129                return;
130            }
131
132            for (int i = 0; i < liveComponents.size(); i++) {
133                if (component.equals(liveComponents.get(i))) {
134                    inButton = true;
135                    testForDrag(eventID);
136                }
137            }
138
139            for (int i = 0; i < liveRectangles.size(); i++) {
140                Rectangle rectangle = this.liveRectangles.get(i);
141                if (rectangle != null && rectangle.contains(containerPoint)) {
142                    inButton = true;
143                    testForDrag(eventID);
144                }
145            }
146
147            if (inButton || inDrag) {
148                Point componentPoint = SwingUtilities.convertPoint(glassPane,
149                        glassPanePoint,
150                        component);
151                parentFrame.setCursor(Cursor.getDefaultCursor());
152
153                component.dispatchEvent(new MouseEvent(component,
154                        eventID,
155                        e.getWhen(),
156
157                        // The Java8 Javadoc
158                        //  https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseEvent.html#MouseEvent-java.awt.Component-int-long-int-int-int-int-boolean-
159                        // says the following should reference
160                        // getModifiersEx()
161                        // but that makes it impossible to cancel
162                        // ReadAll, WriteAll etc in DecoderPro.
163                        // When it fails, getModifier is 0x10 BUTTON1_MASK
164                        // and
165                        // getModifierEx is 0x400 BUTTON1_DOWN_MASK
166
167                        e.getModifiers(),
168                        componentPoint.x,
169                        componentPoint.y,
170                        e.getClickCount(),
171                        e.isPopupTrigger()));
172            } else {
173                parentFrame.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
174            }
175
176        }
177
178        private void testForDrag(int eventID) {
179            if (eventID == JmriMouseEvent.MOUSE_PRESSED) {
180                inDrag = true;
181            }
182        }
183    }
184}