source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rsyntaxtextarea/focusabletip/TipWindow.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 9.7 KB
Line 
1/*
2 * 07/29/2009
3 *
4 * TipWindow.java - The actual window component representing the tool tip.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rsyntaxtextarea.focusabletip;
10
11import java.awt.BorderLayout;
12import java.awt.Color;
13import java.awt.Component;
14import java.awt.Container;
15import java.awt.Dimension;
16import java.awt.Font;
17import java.awt.Point;
18import java.awt.Rectangle;
19import java.awt.Window;
20import java.awt.event.ActionEvent;
21import java.awt.event.ActionListener;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24import java.awt.event.MouseAdapter;
25import java.awt.event.MouseEvent;
26import java.awt.event.WindowAdapter;
27import java.awt.event.WindowEvent;
28import javax.swing.BorderFactory;
29import javax.swing.JEditorPane;
30import javax.swing.JLabel;
31import javax.swing.JPanel;
32import javax.swing.JScrollPane;
33import javax.swing.JSeparator;
34import javax.swing.JWindow;
35import javax.swing.SwingConstants;
36import javax.swing.SwingUtilities;
37import javax.swing.UIManager;
38import javax.swing.border.Border;
39import javax.swing.event.HyperlinkEvent;
40import javax.swing.event.HyperlinkListener;
41import javax.swing.event.MouseInputAdapter;
42import javax.swing.text.BadLocationException;
43import javax.swing.text.html.HTMLDocument;
44
45import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
46
47
48/**
49 * The actual tool tip component.
50 *
51 * @author Robert Futrell
52 * @version 1.0
53 */
54class TipWindow extends JWindow implements ActionListener {
55
56 private FocusableTip ft;
57 private JEditorPane textArea;
58 private String text;
59 private TipListener tipListener;
60 private HyperlinkListener userHyperlinkListener;
61
62 private static TipWindow visibleInstance;
63
64
65 /**
66 * Constructor.
67 *
68 * @param owner The parent window.
69 * @param msg The text of the tool tip. This can be HTML.
70 */
71 public TipWindow(Window owner, FocusableTip ft, String msg) {
72
73 super(owner);
74 this.ft = ft;
75 // Render plain text tool tips correctly.
76 if (msg!=null && msg.length()>=6 &&
77 !msg.substring(0,6).toLowerCase().equals("<html>")) {
78 msg = "<html>" + RSyntaxUtilities.escapeForHtml(msg, "<br>", false);
79 }
80 this.text = msg;
81 tipListener = new TipListener();
82
83 JPanel cp = new JPanel(new BorderLayout());
84 cp.setBorder(TipUtil.getToolTipBorder());
85 cp.setBackground(TipUtil.getToolTipBackground());
86 textArea = new JEditorPane("text/html", text);
87 TipUtil.tweakTipEditorPane(textArea);
88 if (ft.getImageBase()!=null) { // Base URL for images
89 ((HTMLDocument)textArea.getDocument()).setBase(ft.getImageBase());
90 }
91 textArea.addMouseListener(tipListener);
92 textArea.addHyperlinkListener(new HyperlinkListener() {
93 public void hyperlinkUpdate(HyperlinkEvent e) {
94 if (e.getEventType()==HyperlinkEvent.EventType.ACTIVATED) {
95 TipWindow.this.ft.possiblyDisposeOfTipWindow();
96 }
97 }
98 });
99 cp.add(textArea);
100
101 setFocusableWindowState(false);
102 setContentPane(cp);
103 setBottomPanel(); // Must do after setContentPane()
104 pack();
105
106 // InputMap/ActionMap combo doesn't work for JWindows (even when
107 // using the JWindow's JRootPane), so we'll resort to KeyListener
108 KeyAdapter ka = new KeyAdapter() {
109 public void keyPressed(KeyEvent e) {
110 if (e.getKeyCode()==KeyEvent.VK_ESCAPE) {
111 TipWindow.this.ft.possiblyDisposeOfTipWindow();
112 }
113 }
114 };
115 addKeyListener(ka);
116 textArea.addKeyListener(ka);
117
118 // Ensure only 1 TipWindow is ever visible. If the caller does what
119 // they're supposed to and only creates these on the EDT, the
120 // synchronization isn't necessary, but we'll be extra safe.
121 synchronized (TipWindow.class) {
122 if (visibleInstance!=null) {
123 visibleInstance.dispose();
124 }
125 visibleInstance = this;
126 }
127
128 }
129
130
131 public void actionPerformed(ActionEvent e) {
132
133 if (!getFocusableWindowState()) {
134 setFocusableWindowState(true);
135 setBottomPanel();
136 textArea.removeMouseListener(tipListener);
137 pack();
138 addWindowFocusListener(new WindowAdapter() {
139 public void windowLostFocus(WindowEvent e) {
140 ft.possiblyDisposeOfTipWindow();
141 }
142 });
143 ft.removeListeners();
144 if (e==null) { // Didn't get here via our mouseover timer
145 requestFocus();
146 }
147 }
148
149 }
150
151
152 /**
153 * Disposes of this window.
154 */
155 public void dispose() {
156 //System.out.println("[DEBUG]: Disposing...");
157 Container cp = getContentPane();
158 for (int i=0; i<cp.getComponentCount(); i++) {
159 // Okay if listener is already removed
160 cp.getComponent(i).removeMouseListener(tipListener);
161 }
162 ft.removeListeners();
163 super.dispose();
164 }
165
166
167 /**
168 * Workaround for JEditorPane not returning its proper preferred size
169 * when rendering HTML until after layout already done. See
170 * http://forums.sun.com/thread.jspa?forumID=57&threadID=574810 for a
171 * discussion.
172 */
173 void fixSize() {
174
175 Dimension d = textArea.getPreferredSize();
176 Rectangle r = null;
177 try {
178
179 // modelToView call is required for this hack, never remove!
180 r = textArea.modelToView(textArea.getDocument().getLength()-1);
181
182 // Ensure the text area doesn't start out too tall or wide.
183 d = textArea.getPreferredSize();
184 d.width += 25; // Just a little extra space
185 final int MAX_WINDOW_W = 600;
186 d.width = Math.min(d.width, MAX_WINDOW_W);
187 d.height = Math.min(d.height, 400);
188
189 // Both needed for modelToView() calculation below...
190 textArea.setPreferredSize(d);
191 textArea.setSize(d);
192
193 // if the new textArea width causes our text to wrap, we must
194 // compute a new preferred size to get all our physical lines.
195 r = textArea.modelToView(textArea.getDocument().getLength()-1);
196 if (r.y+r.height>d.height) {
197 d.height = r.y + r.height + 5;
198 textArea.setPreferredSize(d);
199 }
200
201 } catch (BadLocationException ble) { // Never happens
202 ble.printStackTrace();
203 }
204
205 pack(); // Must re-pack to calculate proper size.
206
207 }
208
209
210 public String getText() {
211 return text;
212 }
213
214
215 private void setBottomPanel() {
216
217 final JPanel panel = new JPanel(new BorderLayout());
218 panel.add(new JSeparator(), BorderLayout.NORTH);
219
220 boolean focusable = getFocusableWindowState();
221 if (focusable) {
222 SizeGrip sg = new SizeGrip();
223 sg.applyComponentOrientation(sg.getComponentOrientation()); // Workaround
224 panel.add(sg, BorderLayout.LINE_END);
225 MouseInputAdapter adapter = new MouseInputAdapter() {
226 private Point lastPoint;
227 public void mouseDragged(MouseEvent e) {
228 Point p = e.getPoint();
229 SwingUtilities.convertPointToScreen(p, panel);
230 if (lastPoint==null) {
231 lastPoint = p;
232 }
233 else {
234 int dx = p.x - lastPoint.x;
235 int dy = p.y - lastPoint.y;
236 setLocation(getX()+dx, getY()+dy);
237 lastPoint = p;
238 }
239 }
240 public void mousePressed(MouseEvent e) {
241 lastPoint = e.getPoint();
242 SwingUtilities.convertPointToScreen(lastPoint, panel);
243 }
244 };
245 panel.addMouseListener(adapter);
246 panel.addMouseMotionListener(adapter);
247 // Don't add tipListener to the panel or SizeGrip
248 }
249 else {
250 panel.setOpaque(false);
251 JLabel label = new JLabel(FocusableTip.getString("FocusHotkey"));
252 Color fg = UIManager.getColor("Label.disabledForeground");
253 Font font = textArea.getFont();
254 font = font.deriveFont(font.getSize2D() - 1.0f);
255 label.setFont(font);
256 if (fg==null) { // Non BasicLookAndFeel-derived Looks
257 fg = Color.GRAY;
258 }
259 label.setOpaque(true);
260 Color bg = TipUtil.getToolTipBackground();
261 label.setBackground(bg);
262 label.setForeground(fg);
263 label.setHorizontalAlignment(SwingConstants.TRAILING);
264 label.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
265 panel.add(label);
266 panel.addMouseListener(tipListener);
267 }
268
269 // Replace the previous SOUTH Component with the new one.
270 Container cp = getContentPane();
271 if (cp.getComponentCount()==2) { // Skip first time through
272 Component comp = cp.getComponent(0);
273 cp.remove(0);
274 JScrollPane sp = new JScrollPane(comp);
275 Border emptyBorder = BorderFactory.createEmptyBorder();
276 sp.setBorder(emptyBorder);
277 sp.setViewportBorder(emptyBorder);
278 sp.setBackground(textArea.getBackground());
279 sp.getViewport().setBackground(textArea.getBackground());
280 cp.add(sp);
281 // What was component 1 is now 0.
282 cp.getComponent(0).removeMouseListener(tipListener);
283 cp.remove(0);
284 }
285
286 cp.add(panel, BorderLayout.SOUTH);
287
288 }
289
290
291 /**
292 * Sets the listener for hyperlink events in this tip window.
293 *
294 * @param listener The new listener. The old listener (if any) is
295 * removed. A value of <code>null</code> means "no listener."
296 */
297 public void setHyperlinkListener(HyperlinkListener listener) {
298 // We've added a separate listener, so remove only the user's.
299 if (userHyperlinkListener!=null) {
300 textArea.removeHyperlinkListener(userHyperlinkListener);
301 }
302 userHyperlinkListener = listener;
303 if (userHyperlinkListener!=null) {
304 textArea.addHyperlinkListener(userHyperlinkListener);
305 }
306 }
307
308
309 /**
310 * Listens for events in this window.
311 */
312 private class TipListener extends MouseAdapter {
313
314 public TipListener() {
315 }
316
317 public void mousePressed(MouseEvent e) {
318 actionPerformed(null); // Manually create "real" window
319 }
320
321 public void mouseExited(MouseEvent e) {
322 // Since we registered this listener on the child components of
323 // the JWindow, not the JWindow iteself, we have to be careful.
324 Component source = (Component)e.getSource();
325 Point p = e.getPoint();
326 SwingUtilities.convertPointToScreen(p, source);
327 if (!TipWindow.this.getBounds().contains(p)) {
328 ft.possiblyDisposeOfTipWindow();
329 }
330 }
331
332 }
333
334}
Note: See TracBrowser for help on using the repository browser.