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}