View Javadoc

1   /*
2    *  XNap Commons
3    *
4    *  Copyright (C) 2005  Felix Berger
5    *
6    *  This library is free software; you can redistribute it and/or
7    *  modify it under the terms of the GNU Lesser General Public
8    *  License as published by the Free Software Foundation; either
9    *  version 2.1 of the License, or (at your option) any later version.
10   *
11   *  This library is distributed in the hope that it will be useful,
12   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14   *  Lesser General Public License for more details.
15   *
16   *  You should have received a copy of the GNU Lesser General Public
17   *  License along with this library; if not, write to the Free Software
18   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19   */
20  package org.xnap.commons.gui.util;
21  
22  import java.awt.AWTEvent;
23  import java.awt.Component;
24  import java.awt.Cursor;
25  import java.awt.Point;
26  import java.awt.Toolkit;
27  import java.awt.event.AWTEventListener;
28  import java.awt.event.KeyEvent;
29  import java.awt.event.MouseEvent;
30  import java.util.HashMap;
31  import java.util.Iterator;
32  import java.util.Map;
33  import java.util.Set;
34  import javax.swing.AbstractButton;
35  import javax.swing.Action;
36  import javax.swing.ImageIcon;
37  import javax.swing.JComponent;
38  import javax.swing.JPopupMenu;
39  import javax.swing.JToolTip;
40  import javax.swing.SwingUtilities;
41  import javax.swing.border.EmptyBorder;
42  
43  /***
44   * Static helper class, that allows to enrich components with What's This help.
45   * <p>
46   * See {@link #setText(JComponent, String)} and {@link #enterWhatsThisMode()}.
47   *
48   * @see org.xnap.commons.gui.util.WhatsThisAction
49   * 
50   * @author Felix Berger
51   */
52  public class WhatsThis {
53  
54  	private static EventHandler listener = new EventHandler();
55  
56  	private static HashMap<Component, Cursor> cursorsByComponent = 
57  		new HashMap<Component, Cursor>();
58  	
59  	private static boolean justEntered;
60  	
61  	// TODO cleanup
62  	private static Cursor gotHelpCursor = Toolkit.getDefaultToolkit().
63  		createCustomCursor(((ImageIcon)IconHelper.getIcon("idea.png", 16)).getImage(),
64  				new Point(0, 0), "gotHelpCursor"); 
65  	
66  	private static Cursor noHelpCursor = Toolkit.getDefaultToolkit().
67  		createCustomCursor(((ImageIcon)IconHelper.getIcon("contexthelp.png", 16)).getImage(),
68  				new Point(0, 0), "noHelpCursor");
69  	
70  	/***
71  	 * Sets the what's this text for the component.
72  	 * @param c the component
73  	 * @param text the text that should be displayed in the what's this popup
74  	 * for this component
75  	 */
76  	public static void setText(JComponent c, String text) 
77  	{
78  		c.putClientProperty(Action.LONG_DESCRIPTION, text);
79  	}
80  	
81  	/***
82  	 * Removes the what's this text for the component
83  	 * @param c the component
84  	 */
85  	public static void removeText(JComponent c)
86  	{
87  		c.putClientProperty(Action.LONG_DESCRIPTION, null);
88  	}
89  	
90  	/***
91  	 * Retrieves the what's this text for the component. If 
92  	 * <code>searchParents</code> is true their text is retrieved as fallback.
93  	 * 
94  	 * @param c the component
95  	 * @param searchParents if true the component hierarchy is searched
96  	 * @return the text; null, if no what's this text has been set
97  	 */
98  	public static String getText(Component c, boolean searchParents) 
99  	{
100 		String text = null;
101 		if (c instanceof AbstractButton) {
102 			// ask action
103 			AbstractButton button = (AbstractButton)c;
104 			if (button.getAction() != null) {
105 				text = (String)button.getAction().getValue(Action.LONG_DESCRIPTION);
106 			}
107 		}
108 		if (text == null && c instanceof JComponent) {
109 			JComponent jc = (JComponent)c;
110 			text = (String)jc.getClientProperty(Action.LONG_DESCRIPTION);
111 		}
112 		if (text == null && searchParents && c.getParent() != null) {
113 			return getText(c.getParent(), searchParents);
114 		}
115 		return text;
116 	}
117 	
118 	/***
119 	 * Enters What's This mode.
120 	 * <p>
121 	 * A global mouse listener is added to the awt event queue looking for
122 	 * mouse movement and mouse clicks.
123 	 * <p>
124 	 * A special cursor is displayed for components that provide a What's this
125 	 * help text, another special cursor is displayed for components that don't.
126 	 * <p>
127 	 * The mode is exited automatically after the first mouse button click on
128 	 * any component. If the component in question has a What's this help
129 	 * text, the text is displayed in a popup.
130 	 */
131 	public static void enterWhatsThisMode()
132 	{
133 		SwingUtilities.invokeLater(new Runnable() {
134 			public void run() {
135 				justEntered = true;
136 				Toolkit.getDefaultToolkit().addAWTEventListener(listener,
137 						AWTEvent.MOUSE_EVENT_MASK | AWTEvent.KEY_EVENT_MASK);
138 			}
139 		});
140 		
141 	}
142 	
143 	/***
144 	 * Exits What's this mode.
145 	 * <p>
146 	 * See {@link #enterWhatsThisMode()}. The global mouse listener is
147 	 * deregistered and the original cursors for all components are restored.
148 	 */
149 	public static void exitWhatsThisMode()
150 	{
151 		Toolkit.getDefaultToolkit().removeAWTEventListener(listener);
152 		Set<Map.Entry<Component, Cursor>> pairs = cursorsByComponent.entrySet();
153 		for (Iterator<Map.Entry<Component, Cursor>> i = pairs.iterator(); i.hasNext();) {
154 			Map.Entry<Component, Cursor> entry = i.next();
155 			entry.getKey().setCursor(entry.getValue());
156 		}
157 		cursorsByComponent.clear();
158 	}
159 	
160 	private static class WhatsThisPopup extends JPopupMenu
161 	{
162 		public WhatsThisPopup(String text)
163 		{
164 			setBorder(new EmptyBorder(0, 0, 0, 0));
165 			JToolTip tip = new JToolTip();
166 			add(tip);
167 			tip.setTipText(GUIHelper.tt(text, 200));
168 		}
169 	}
170 	
171 	private static class EventHandler implements AWTEventListener
172 	{
173 
174 		public void eventDispatched(AWTEvent event) 
175 		{
176 			
177 			switch (event.getID()) {
178 			case MouseEvent.MOUSE_MOVED:
179 				if (justEntered) {
180 					justEntered = false;
181 					entered((MouseEvent)event);
182 				}
183 			case MouseEvent.MOUSE_ENTERED:
184 				entered((MouseEvent)event);
185 				break;
186 			case MouseEvent.MOUSE_EXITED:
187 				exited((MouseEvent)event);
188 				break;
189 			case MouseEvent.MOUSE_CLICKED:
190 				((MouseEvent)event).consume();
191 				break;
192 			case MouseEvent.MOUSE_PRESSED:
193 				showPopup((MouseEvent)event);
194 				break;
195 			case MouseEvent.MOUSE_RELEASED:
196 				((MouseEvent)event).consume();
197 				break;
198 			case KeyEvent.KEY_PRESSED:
199 				keyPressed((KeyEvent)event);
200 				break;
201 			}
202 		}
203 		
204 		private void showPopup(MouseEvent e)
205 		{
206 			e.consume();
207 					
208 			if (e.getSource() instanceof Component) {
209 				Component c = (Component)e.getSource();
210 				String whatsThisText = getText(c, true);
211 				if (whatsThisText != null) {
212 					WhatsThisPopup popup = new WhatsThisPopup(whatsThisText);
213 					popup.show(c, e.getX(), e.getY());
214 				}
215 			}
216 			
217 			exited(e);
218 			
219 			WhatsThis.exitWhatsThisMode();
220 		}
221 		
222 		private void keyPressed(KeyEvent e)
223 		{
224 			if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
225 				WhatsThis.exitWhatsThisMode();
226 				e.consume();
227 			}
228 		}
229 		
230 		private void entered(MouseEvent e)
231 		{
232 			if (e.getSource() instanceof Component) {
233 				Component c = (Component)e.getSource();
234 				if (c.isCursorSet()) {
235 					if (cursorsByComponent.containsKey(c)) {
236 						// TODO bflat1
237 					}
238 					cursorsByComponent.put(c, c.getCursor());
239 				}
240 				String whatsThisText = getText(c, true);
241 				if (whatsThisText != null) {
242 //					c.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
243 					c.setCursor(gotHelpCursor);
244 				}
245 				else {
246 //					c.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
247 					c.setCursor(noHelpCursor);
248 				}
249 			}
250 		}
251 		
252 		private void exited(MouseEvent e)
253 		{
254 			// set mouse cursor back to what is was, which is either
255 			// stored in the map or null
256 			if (e.getSource() instanceof Component) {
257 				((Component)e.getSource()).setCursor((Cursor)cursorsByComponent.get(e.getSource()));
258 			}
259 		}
260 	}
261 }