001: /*
002: * @(#)PopupWindow.java
003: *
004: * Copyright 2002 - 2003 JIDE Software. All rights reserved.
005: */
006: package com.jidesoft.swing;
007:
008: import com.jidesoft.utils.PortingUtils;
009:
010: import javax.swing.*;
011: import javax.swing.event.EventListenerList;
012: import javax.swing.event.PopupMenuEvent;
013: import javax.swing.event.PopupMenuListener;
014: import java.awt.*;
015: import java.awt.event.*;
016: import java.beans.PropertyChangeEvent;
017: import java.beans.PropertyChangeListener;
018: import java.util.List;
019: import java.util.Vector;
020:
021: /**
022: * PopupWindow class
023: * <p/>
024: * You can add another JPopupMenu or JComboxBox in this popup.
025: * <p/>
026: * This class is copied from http://forum.java.sun.com/thread.jsp?forum=57&thread=230866
027: * with some minor modifications.
028: */
029: public class PopupWindow {
030:
031: /**
032: * A list of event listeners for this component.
033: */
034: protected EventListenerList listenerList = new EventListenerList();
035:
036: private JWindow _delegate;
037: private Container _container;
038: private List _grabbed = new Vector();
039: private List _excluded = new Vector();
040: private WindowListener _windowListener;
041: private ComponentListener _componentListener;
042: private ContainerListener _containerListener;
043: private MouseListener _mouseListener;
044: private Component _component;
045:
046: // JDK 1.3 Porting Hint.
047: // Use AWTEventListener instead
048: // private AWTEventListener _keyEventDispatcher;
049: private KeyEventDispatcher _keyEventDispatcher;
050:
051: private Component _parent;
052:
053: public PopupWindow(Container container) {
054: _container = container;
055: createDelegate();
056: createListeners();
057: }
058:
059: private void createDelegate() {
060: Window window = getWindow();
061: if (window != null) {
062: _delegate = new JWindow(window);
063: }
064: }
065:
066: public void add(Component component) {
067: _component = component;
068: _component.addPropertyChangeListener("preferredSize",
069: new PropertyChangeListener() {
070: public void propertyChange(PropertyChangeEvent evt) {
071: if (_delegate != null) {
072: _delegate.pack();
073: }
074: }
075: });
076: if (_delegate != null) {
077: _delegate.getContentPane().add(component);
078: _delegate.pack();
079: // workaournd for a problem. JWindow somehow offset the height by 1
080: // See http://developer.java.sun.com/developer/bugParade/bugs/4511106.html
081: // looks like call pack again solve the problem.
082: _delegate.pack();
083: // mDelegate.setSize(mDelegate.getSize().width, mDelegate.getSize().height + 1);
084: }
085: }
086:
087: public void show(Component relative, int x, int y) {
088: _parent = relative;
089: if (_delegate == null) {
090: createDelegate();
091: if (_delegate == null)
092: return;
093: add(_component);
094: }
095:
096: Point p = new Point(x, y);
097:
098: SwingUtilities.convertPointToScreen(p, relative);
099:
100: Rectangle screenSize = PortingUtils.getScreenBounds(relative);
101:
102: Dimension size = _component.getPreferredSize();
103:
104: int left = p.x + size.width;
105: int bottom = p.y + size.height;
106:
107: if (p.x < screenSize.x) {
108: p.x = screenSize.x;
109: }
110: if (left > screenSize.width) {
111: p.x = screenSize.width - size.width;
112: }
113:
114: if (p.y < screenSize.y) {
115: p.y = screenSize.y;
116: }
117: if (bottom > screenSize.height) {
118: p.y -= size.height + relative.getHeight();
119: }
120:
121: // Point location = relative.getLocationOnScreen();
122: _delegate.setLocation(p.x, p.y);
123: _delegate.setSize(_component.getPreferredSize());
124: firePopupMenuWillBecomeVisible();
125: _delegate.setVisible(true);
126: grabContainers();
127:
128: // set popup window focus and register esc key
129: // _delegate.toFront();
130: // _delegate.requestFocus();
131:
132: // JDK 1.3 Porting Hint
133: // Replace by AWTEventListener
134: // Following Block is for JDK 1.3
135: // _keyEventDispatcher = new AWTEventListener() {
136: // public void eventDispatched(AWTEvent e) {
137: // if (e instanceof KeyEvent) {
138: // if (((KeyEvent) e).getKeyCode() == KeyEvent.VK_ESCAPE) {
139: // hide();
140: // }
141: // }
142: // }
143: // };
144: // Toolkit.getDefaultToolkit().addAWTEventListener(_keyEventDispatcher, AWTEvent.KEY_EVENT_MASK);
145:
146: // Following Block is for JDK 1.4
147: _keyEventDispatcher = new KeyEventDispatcher() {
148: public boolean dispatchKeyEvent(KeyEvent e) {
149: if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
150: hide();
151: return true;
152: }
153: return false;
154: }
155: };
156: DefaultFocusManager.getCurrentKeyboardFocusManager()
157: .addKeyEventDispatcher(_keyEventDispatcher);
158: }
159:
160: public void hide() {
161: if (_parent != null) {
162: _parent.requestFocus();
163: }
164: firePopupMenuWillBecomeInvisible();
165:
166: if (_delegate != null) {
167: _delegate.setVisible(false);
168: }
169:
170: if (_keyEventDispatcher != null) {
171: // JDK 1.3 Porting Hint
172: // Replace by AWTEventListener
173: // Toolkit.getDefaultToolkit().removeAWTEventListener(_keyEventDispatcher);
174: DefaultFocusManager.getCurrentKeyboardFocusManager()
175: .removeKeyEventDispatcher(_keyEventDispatcher);
176: _keyEventDispatcher = null;
177: }
178: releaseContainers();
179: disposeDelegate();
180: }
181:
182: private void createListeners() {
183: _windowListener = new WindowAdapter() {
184: @Override
185: public void windowClosing(WindowEvent e) {
186: hide();
187: }
188:
189: @Override
190: public void windowClosed(WindowEvent e) {
191: hide();
192: }
193:
194: @Override
195: public void windowIconified(WindowEvent e) {
196: hide();
197: }
198: };
199: _componentListener = new ComponentListener() {
200: public void componentResized(ComponentEvent e) {
201: hide();
202: }
203:
204: public void componentMoved(ComponentEvent e) {
205: hide();
206: }
207:
208: public void componentShown(ComponentEvent e) {
209: hide();
210: }
211:
212: public void componentHidden(ComponentEvent e) {
213: hide();
214: }
215: };
216: _containerListener = new ContainerListener() {
217: public void componentAdded(ContainerEvent e) {
218: hide();
219: }
220:
221: public void componentRemoved(ContainerEvent e) {
222: hide();
223: }
224: };
225: _mouseListener = new MouseAdapter() {
226: @Override
227: public void mousePressed(MouseEvent e) {
228: hide();
229: }
230: };
231: }
232:
233: private void disposeDelegate() {
234: if (_delegate != null) {
235: _delegate.dispose();
236: _delegate = null;
237: }
238: }
239:
240: private Window getWindow() {
241: Container c = _container;
242: if (c == null) {
243: return null;
244: }
245: while (!(c instanceof Window) && c.getParent() != null)
246: c = c.getParent();
247: if (c instanceof Window)
248: return (Window) c;
249: return null;
250: }
251:
252: private void grabContainers() {
253: Container c = _container;
254: while (!(c instanceof Window) && c.getParent() != null)
255: c = c.getParent();
256: grabContainer(c);
257: }
258:
259: private void grabContainer(Container c) {
260: if (c instanceof Window) {
261: ((Window) c).addWindowListener(_windowListener);
262: c.addComponentListener(_componentListener);
263: _grabbed.add(c);
264: }
265:
266: synchronized (c.getTreeLock()) {
267: int ncomponents = c.getComponentCount();
268: Component[] component = c.getComponents();
269: for (int i = 0; i < ncomponents; i++) {
270: Component comp = component[i];
271: if (!comp.isVisible())
272: continue;
273: if (isExcludedComponent(comp)) {
274: continue;
275: }
276: // // TODO: this is not the right way to do things. Leave it for future enhancement to popup panel
277: // // don't hide popup when button of abstract combobox is pressed so that that button can toggle visiblility of popup panel
278: // if(comp instanceof AbstractButton && comp.getParent() instanceof AbstractComboBox) {
279: // if(_delegate.isAncestorOf(((AbstractComboBox) comp.getParent()).getPopupPanel())) {
280: // continue;
281: // }
282: // }
283: comp.addMouseListener(_mouseListener);
284: _grabbed.add(comp);
285: if (comp instanceof Container) {
286: Container cont = (Container) comp;
287: if (cont instanceof JLayeredPane) {
288: cont.addContainerListener(_containerListener);
289: }
290: grabContainer(cont);
291: }
292: }
293: }
294: }
295:
296: void releaseContainers() {
297: for (int i = 0; i < _grabbed.size(); i++) {
298: Component c = (Component) _grabbed.get(i);
299: if (c instanceof Window) {
300: ((Window) c).removeWindowListener(_windowListener);
301: c.removeComponentListener(_componentListener);
302: } else {
303: c.removeMouseListener(_mouseListener);
304: }
305:
306: if (c instanceof Container) {
307: if (c instanceof JLayeredPane) {
308: ((Container) c)
309: .removeContainerListener(_containerListener);
310: }
311: }
312: }
313: _grabbed.clear();
314: }
315:
316: /**
317: * Gets the visiblility of this popup.
318: *
319: * @return true if popup is visible
320: */
321: public boolean isVisible() {
322: return _delegate != null ? _delegate.isVisible() : false;
323: }
324:
325: /**
326: * Adds a <code>PopupMenu</code> listener which will listen to notification
327: * messages from the popup portion of the combo box.
328: * <p/>
329: * For all standard look and feels shipped with Java 2, the popup list
330: * portion of combo box is implemented as a <code>JPopupMenu</code>.
331: * A custom look and feel may not implement it this way and will
332: * therefore not receive the notification.
333: *
334: * @param l the <code>PopupMenuListener</code> to add
335: * @since 1.4
336: */
337: public void addPopupMenuListener(PopupMenuListener l) {
338: listenerList.add(PopupMenuListener.class, l);
339: }
340:
341: /**
342: * Removes a <code>PopupMenuListener</code>.
343: *
344: * @param l the <code>PopupMenuListener</code> to remove
345: * @see #addPopupMenuListener
346: * @since 1.4
347: */
348: public void removePopupMenuListener(PopupMenuListener l) {
349: listenerList.remove(PopupMenuListener.class, l);
350: }
351:
352: /**
353: * Returns an array of all the <code>PopupMenuListener</code>s added
354: * to this JComboBox with addPopupMenuListener().
355: *
356: * @return all of the <code>PopupMenuListener</code>s added or an empty
357: * array if no listeners have been added
358: * @since 1.4
359: */
360: public PopupMenuListener[] getPopupMenuListeners() {
361: return (PopupMenuListener[]) listenerList
362: .getListeners(PopupMenuListener.class);
363: }
364:
365: /**
366: * Notifies <code>PopupMenuListener</code>s that the popup portion of the
367: * combo box will become visible.
368: * <p/>
369: * This method is public but should not be called by anything other than
370: * the UI delegate.
371: *
372: * @see #addPopupMenuListener
373: * @since 1.4
374: */
375: public void firePopupMenuWillBecomeVisible() {
376: Object[] listeners = listenerList.getListenerList();
377: PopupMenuEvent e = null;
378: for (int i = listeners.length - 2; i >= 0; i -= 2) {
379: if (listeners[i] == PopupMenuListener.class) {
380: if (e == null)
381: e = new PopupMenuEvent(this );
382: ((PopupMenuListener) listeners[i + 1])
383: .popupMenuWillBecomeVisible(e);
384: }
385: }
386: }
387:
388: /**
389: * Notifies <code>PopupMenuListener</code>s that the popup portion of the
390: * combo box has become invisible.
391: * <p/>
392: * This method is public but should not be called by anything other than
393: * the UI delegate.
394: *
395: * @see #addPopupMenuListener
396: * @since 1.4
397: */
398: public void firePopupMenuWillBecomeInvisible() {
399: Object[] listeners = listenerList.getListenerList();
400: PopupMenuEvent e = null;
401: for (int i = listeners.length - 2; i >= 0; i -= 2) {
402: if (listeners[i] == PopupMenuListener.class) {
403: if (e == null)
404: e = new PopupMenuEvent(this );
405: ((PopupMenuListener) listeners[i + 1])
406: .popupMenuWillBecomeInvisible(e);
407: }
408: }
409: }
410:
411: /**
412: * Notifies <code>PopupMenuListener</code>s that the popup portion of the
413: * combo box has been canceled.
414: * <p/>
415: * This method is public but should not be called by anything other than
416: * the UI delegate.
417: *
418: * @see #addPopupMenuListener
419: * @since 1.4
420: */
421: public void firePopupMenuCanceled() {
422: Object[] listeners = listenerList.getListenerList();
423: PopupMenuEvent e = null;
424: for (int i = listeners.length - 2; i >= 0; i -= 2) {
425: if (listeners[i] == PopupMenuListener.class) {
426: if (e == null)
427: e = new PopupMenuEvent(this );
428: ((PopupMenuListener) listeners[i + 1])
429: .popupMenuCanceled(e);
430: }
431: }
432: }
433:
434: /**
435: * PopupWindow will add necessary listeners to some components
436: * so that mouse click etc can hide the popup window. However in
437: * certain case, you might not want this.
438: *
439: * @param comp component which will not hide popup when it is clicked.
440: */
441: public void addAsExcludedComponents(Component comp) {
442: if (_excluded.contains(comp)) {
443: return;
444: }
445: _excluded.add(comp);
446: }
447:
448: public void removeFromExcludedComponents(Component comp) {
449: if (!_excluded.contains(comp)) {
450: return;
451: }
452: _excluded.remove(comp);
453: }
454:
455: public boolean isExcludedComponent(Component comp) {
456: return _excluded.contains(comp);
457: }
458: }
|