KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > PopupManager


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor;
21
22 import java.awt.Dimension JavaDoc;
23 import java.awt.Point JavaDoc;
24 import java.awt.Rectangle JavaDoc;
25 import java.awt.event.KeyEvent JavaDoc;
26 import java.awt.event.KeyListener JavaDoc;
27
28 import javax.swing.Action JavaDoc;
29 import javax.swing.ActionMap JavaDoc;
30 import javax.swing.InputMap JavaDoc;
31 import javax.swing.JComponent JavaDoc;
32 import javax.swing.JLayeredPane JavaDoc;
33 import javax.swing.JRootPane JavaDoc;
34 import javax.swing.KeyStroke JavaDoc;
35 import javax.swing.text.JTextComponent JavaDoc;
36
37 import org.netbeans.editor.EditorUI;
38 import org.netbeans.editor.Utilities;
39 import java.awt.event.ComponentAdapter JavaDoc;
40 import java.awt.event.ComponentEvent JavaDoc;
41 import javax.swing.SwingUtilities JavaDoc;
42 import java.awt.Component JavaDoc;
43 import javax.swing.JViewport JavaDoc;
44 import javax.swing.text.BadLocationException JavaDoc;
45
46
47 /**
48  * Popup manager allows to display an arbitrary popup component
49  * over the underlying text component.
50  *
51  * @author Martin Roskanin, Miloslav Metelka
52  * @since 03/2002
53  */

54 public class PopupManager {
55     
56     private JComponent JavaDoc popup = null;
57     private JTextComponent JavaDoc textComponent;
58
59     /** Place popup always above cursor */
60     public static final Placement Above = new Placement("Above"); //NOI18N
61

62     /** Place popup always below cursor */
63     public static final Placement Below = new Placement("Below"); //NOI18N
64

65     /** Place popup to larger area. i.e. if place below cursor is
66         larger than place above, then popup will be placed below cursor. */

67     public static final Placement Largest = new Placement("Largest"); //NOI18N
68

69     /** Place popup above cursor. If a place above cursor is insufficient,
70         then popup will be placed below cursor. */

71     public static final Placement AbovePreferred = new Placement("AbovePreferred"); //NOI18N
72

73     /** Place popup below cursor. If a place below cursor is insufficient,
74         then popup will be placed above cursor. */

75     public static final Placement BelowPreferred = new Placement("BelowPreferred"); //NOI18N
76

77     /** Place popup inside the scrollbar's viewport */
78     public static final HorizontalBounds ViewPortBounds = new HorizontalBounds("ViewPort"); //NOI18N
79

80     /** Place popup inside the whole scrollbar */
81     public static final HorizontalBounds ScrollBarBounds = new HorizontalBounds("ScrollBar"); //NOI18N
82

83     private KeyListener JavaDoc keyListener;
84     
85     private TextComponentListener componentListener;
86     
87     /** Creates a new instance of PopupManager */
88     public PopupManager(JTextComponent JavaDoc textComponent) {
89         this.textComponent = textComponent;
90         keyListener = new PopupKeyListener();
91         textComponent.addKeyListener(keyListener);
92         componentListener = new TextComponentListener();
93         textComponent.addComponentListener(componentListener);
94     }
95     
96     /** Install popup component to textComponent root pane
97      * based on caret coordinates with the <CODE>Largest</CODE> placement.
98      * Note: Make sure the component is properly uninstalled later,
99      * if it is not necessary. See issue #35325 for details.
100      * @param popup popup component to be installed into
101      * root pane of the text component.
102      */

103     public void install(JComponent JavaDoc popup) {
104         if (textComponent == null) return;
105         int caretPos = textComponent.getCaret().getDot();
106         try {
107             Rectangle JavaDoc caretBounds = textComponent.modelToView(caretPos);
108             install(popup, caretBounds, Largest);
109         } catch (BadLocationException JavaDoc e) {
110             // do not install if the caret position is invalid
111
}
112     }
113
114     /** Removes popup component from textComponent root pane
115      * @param popup popup component to be removed from
116      * root pane of the text component.
117      */

118     public void uninstall(JComponent JavaDoc popup){
119         if (this.popup != null){
120             if (this.popup.isVisible()) this.popup.setVisible(false);
121             removeFromRootPane(this.popup);
122         }
123         
124         if (popup!=this.popup && popup!= null){
125             if (popup.isVisible()) popup.setVisible(false);
126             removeFromRootPane(popup);
127         }
128     }
129
130     public void install(JComponent JavaDoc popup, Rectangle JavaDoc cursorBounds,
131     Placement placement, HorizontalBounds horizontalBounds, int horizontalAdjustment, int verticalAdjustment){
132         /* Uninstall the old popup from root pane
133          * and install the new one. Even in case
134          * they are the same objects it's necessary
135          * to cover the workspace switches etc.
136          */

137         if (this.popup != null) {
138             // if i.e. completion is visible and tooltip is being installed,
139
// completion popup should be closed.
140
if (this.popup.isVisible() && this.popup!=popup) this.popup.setVisible(false);
141             removeFromRootPane(this.popup);
142         }
143
144         this.popup = popup;
145
146         if (this.popup != null) {
147             installToRootPane(this.popup);
148         }
149         
150         // Update the bounds of the popup
151
Rectangle JavaDoc bounds = computeBounds(this.popup, textComponent,
152             cursorBounds, placement, horizontalBounds);
153
154         if (bounds != null){
155             // Convert to layered pane's coordinates
156

157             if (horizontalBounds == ScrollBarBounds){
158                 bounds.x = 0;
159             }
160             
161             JRootPane JavaDoc rp = textComponent.getRootPane();
162             if (rp!=null){
163                 bounds = SwingUtilities.convertRectangle(textComponent, bounds,
164                     rp.getLayeredPane());
165             }
166             
167             if (horizontalBounds == ScrollBarBounds){
168                 if (textComponent.getParent() instanceof JViewport JavaDoc){
169                     int shift = textComponent.getParent().getX();
170                     Rectangle JavaDoc viewBounds = ((JViewport JavaDoc)textComponent.getParent()).getViewRect();
171                     bounds.x += viewBounds.x;
172                     bounds.x -= shift;
173                     bounds.width += shift;
174                 }
175             }
176             
177             bounds.x = bounds.x + horizontalAdjustment;
178             bounds.y = bounds.y + verticalAdjustment;
179             bounds.width = bounds.width - horizontalAdjustment;
180             bounds.height = bounds.height - verticalAdjustment;
181             this.popup.setBounds(bounds);
182
183         } else { // can't fit -> hide
184
this.popup.setVisible(false);
185         }
186     }
187     
188     public void install(JComponent JavaDoc popup, Rectangle JavaDoc cursorBounds,
189     Placement placement, HorizontalBounds horizontalBounds){
190         install(popup, cursorBounds, placement, ViewPortBounds, 0, 0);
191     }
192     
193     
194     public void install(JComponent JavaDoc popup, Rectangle JavaDoc cursorBounds,
195     Placement placement){
196         install(popup, cursorBounds, placement, ViewPortBounds);
197     }
198     
199     /** Returns installed popup panel component */
200     public JComponent JavaDoc get(){
201         return popup;
202     }
203     
204
205     /** Install popup panel to current textComponent root pane */
206     private void installToRootPane(JComponent JavaDoc c) {
207         JRootPane JavaDoc rp = textComponent.getRootPane();
208         if (rp != null) {
209             rp.getLayeredPane().add(c, JLayeredPane.POPUP_LAYER, 0);
210         }
211     }
212
213     /** Remove popup panel from previous textComponent root pane */
214     private void removeFromRootPane(JComponent JavaDoc c) {
215         JRootPane JavaDoc rp = c.getRootPane();
216         if (rp != null) {
217             rp.getLayeredPane().remove(c);
218         }
219     }
220
221     /** Variation of the method for computing the bounds
222      * for the concrete view component. As the component can possibly
223      * be placed in a scroll pane it's first necessary
224      * to translate the cursor bounds and also translate
225      * back the resulting popup bounds.
226      * @param popup popup panel to be displayed
227      * @param view component over which the popup is displayed.
228      * @param cursorBounds the bounds of the caret or mouse cursor
229      * relative to the upper-left corner of the visible view.
230      * @param placement where to place the popup panel according to
231      * the cursor position.
232      * @return bounds of popup panel relative to the upper-left corner
233      * of the underlying view component.
234      * <CODE>null</CODE> if there is no place to display popup.
235      */

236     protected static Rectangle JavaDoc computeBounds(JComponent JavaDoc popup,
237     JComponent JavaDoc view, Rectangle JavaDoc cursorBounds, Placement placement, HorizontalBounds horizontalBounds) {
238         
239         if (horizontalBounds == null) horizontalBounds = ViewPortBounds;
240         
241         Rectangle JavaDoc ret;
242         Component JavaDoc viewParent = view.getParent();
243         
244         if (viewParent instanceof JViewport JavaDoc) {
245             Rectangle JavaDoc viewBounds = ((JViewport JavaDoc)viewParent).getViewRect();
246
247             Rectangle JavaDoc translatedCursorBounds = (Rectangle JavaDoc)cursorBounds.clone();
248             translatedCursorBounds.translate(-viewBounds.x, -viewBounds.y);
249
250             ret = computeBounds(popup, viewBounds.width, viewBounds.height,
251                 translatedCursorBounds, placement, horizontalBounds);
252             
253             if (ret != null) { // valid bounds
254
ret.translate(viewBounds.x, viewBounds.y);
255             }
256             
257         } else { // not in scroll pane
258
ret = computeBounds(popup, view.getWidth(), view.getHeight(),
259                 cursorBounds, placement);
260         }
261         
262         return ret;
263     }
264
265     protected static Rectangle JavaDoc computeBounds(JComponent JavaDoc popup,
266     JComponent JavaDoc view, Rectangle JavaDoc cursorBounds, Placement placement) {
267         return computeBounds(popup, view, cursorBounds, placement, ViewPortBounds);
268     }
269     
270     /** Computes a best-fit bounds of popup panel
271      * according to available space in the underlying view
272      * (visible part of the pane).
273      * The placement is first evaluated and put into the popup's client property
274      * by <CODE>popup.putClientProperty(Placement.class, actual-placement)</CODE>.
275      * The actual placement is <UL>
276      * <LI> <CODE>Above</CODE> if the original placement was <CODE>Above</CODE>.
277      * Or if the original placement was <CODE>AbovePreferred</CODE>
278      * or <CODE>Largest</CODE>
279      * and there is more space above the cursor than below it.
280      * <LI> <CODE>Below</CODE> if the original placement was <CODE>Below</CODE>.
281      * Or if the original placement was <CODE>BelowPreferred</CODE>
282      * or <CODE>Largest</CODE>
283      * and there is more space below the cursor than above it.
284      * <LI> <CODE>AbovePreferred</CODE> if the original placement
285      * was <CODE>AbovePreferred</CODE>
286      * and there is less space above the cursor than below it.
287      * <LI> <CODE>BelowPreferred</CODE> if the original placement
288      * was <CODE>BelowPreferred</CODE>
289      * and there is less space below the cursor than above it.
290      * <P>Once the placement client property is set
291      * the <CODE>popup.setSize()</CODE> is called with the size of the area
292      * above/below the cursor (indicated by the placement).
293      * The popup responds by updating its size to the equal or smaller
294      * size. If it cannot physically fit into the requested area
295      * it can call
296      * <CODE>putClientProperty(Placement.class, null)</CODE>
297      * on itself to indicate that it cannot fit. The method scans
298      * the content of the client property upon return from
299      * <CODE>popup.setSize()</CODE> and if it finds null there it returns
300      * null bounds in that case. The only exception is
301      * if the placement was either <CODE>AbovePreferred</CODE>
302      * or <CODE>BelowPreferred</CODE>. In that case the method
303      * gives it one more try
304      * by attempting to fit the popup into (bigger) complementary
305      * <CODE>Below</CODE> and <CODE>Above</CODE> areas (respectively).
306      * The popup either fits into these (bigger) areas or it again responds
307      * by returning <CODE>null</CODE> in the client property in which case
308      * the method finally gives up and returns null bounds.
309      *
310      * @param popup popup panel to be displayed
311      * @param viewWidth width of the visible view area.
312      * @param viewHeight height of the visible view area.
313      * @param cursorBounds the bounds of the caret or mouse cursor
314      * relative to the upper-left corner of the visible view
315      * @param placement where to place the popup panel according to
316      * the cursor position
317      * @return bounds of popup panel relative to the upper-left corner
318      * of the underlying view.
319      * <CODE>null</CODE> if there is no place to display popup.
320      */

321     protected static Rectangle JavaDoc computeBounds(JComponent JavaDoc popup,
322     int viewWidth, int viewHeight, Rectangle JavaDoc cursorBounds, Placement placement, HorizontalBounds horizontalBounds) {
323         
324         if (placement == null) {
325             throw new NullPointerException JavaDoc("placement cannot be null"); // NOI18N
326
}
327         
328         // Compute available height above the cursor
329
int aboveCursorHeight = cursorBounds.y;
330         int belowCursorY = cursorBounds.y + cursorBounds.height;
331         int belowCursorHeight = viewHeight - belowCursorY;
332         
333         // Resolve *Preferred placements first
334
if (placement == AbovePreferred || placement == BelowPreferred) {
335             int prefHeight = popup.getPreferredSize().height;
336             if (placement == AbovePreferred) {
337                 placement = (prefHeight <= aboveCursorHeight) ? Above : Largest;
338             } else { // BelowPreferred
339
placement = (prefHeight <= belowCursorHeight) ? Below : Largest;
340             }
341         }
342         
343         // Resolve Largest placement
344
if (placement == Largest) {
345             placement = (aboveCursorHeight < belowCursorHeight)
346                 ? Below
347                 : Above;
348         }
349         
350         Rectangle JavaDoc popupBounds = null;
351         
352         while (true) { // do one or two passes
353
popup.putClientProperty(Placement.class, placement);
354
355             int height = (placement == Above || placement == AbovePreferred)
356                 ? aboveCursorHeight
357                 : belowCursorHeight;
358
359             popup.setSize(viewWidth, height);
360             popupBounds = popup.getBounds();
361
362             Placement updatedPlacement = (Placement)popup.getClientProperty(Placement.class);
363
364             if (updatedPlacement != placement) { // popup does not fit with the orig placement
365
if (placement == AbovePreferred && updatedPlacement == null) {
366                     placement = Below;
367                     continue;
368                     
369                 } else if (placement == BelowPreferred && updatedPlacement == null) {
370                     placement = Above;
371                     continue;
372                 }
373             }
374             
375             if (updatedPlacement == null) {
376                 popupBounds = null;
377             }
378             
379             break;
380         }
381         
382         if (popupBounds != null) {
383             //place popup according to caret position and Placement
384
popupBounds.x = Math.min(cursorBounds.x, viewWidth - popupBounds.width);
385
386             popupBounds.y = (placement == Above || placement == AbovePreferred)
387                 ? (aboveCursorHeight - popupBounds.height)
388                 : belowCursorY;
389         }
390
391         return popupBounds;
392     }
393
394     protected static Rectangle JavaDoc computeBounds(JComponent JavaDoc popup,
395     int viewWidth, int viewHeight, Rectangle JavaDoc cursorBounds, Placement placement) {
396         return computeBounds(popup, viewWidth, viewHeight, cursorBounds, placement, ViewPortBounds);
397     }
398
399     /** Popup's key filter */
400     private class PopupKeyListener implements KeyListener JavaDoc{
401         
402         public void keyTyped(KeyEvent JavaDoc e){}
403         public void keyReleased(KeyEvent JavaDoc e){}
404         
405         public void keyPressed(KeyEvent JavaDoc e){
406             if (e == null) return;
407             if (popup != null && popup.isShowing()){
408                 
409                 // get popup's registered keyboard actions
410
ActionMap JavaDoc am = popup.getActionMap();
411                 InputMap JavaDoc im = popup.getInputMap();
412                 
413                 // check whether popup registers keystroke
414
Object JavaDoc obj = im.get(KeyStroke.getKeyStrokeForEvent(e));
415                 if (obj!=null){
416                     // if yes, gets the popup's action for this keystroke, perform it
417
// and consume key event
418
Action JavaDoc action = am.get(obj);
419                     if (action != null) {
420                         action.actionPerformed(null);
421                         e.consume();
422                     }
423                 }
424             }
425         }
426
427     }
428     
429     private final class TextComponentListener extends ComponentAdapter JavaDoc {
430
431         public void componentHidden(ComponentEvent JavaDoc evt) {
432             install(null); // hide popup
433
}
434
435     }
436     
437     /** Placement of popup panel specification */
438     public static final class Placement {
439         
440         private final String JavaDoc representation;
441         
442         private Placement(String JavaDoc representation) {
443             this.representation = representation;
444         }
445         
446         public String JavaDoc toString() {
447             return representation;
448         }
449         
450     }
451     
452     /** Horizontal bounds of popup panel specification */
453     public static final class HorizontalBounds {
454         
455         private final String JavaDoc representation;
456         
457         private HorizontalBounds(String JavaDoc representation) {
458             this.representation = representation;
459         }
460         
461         public String JavaDoc toString() {
462             return representation;
463         }
464         
465     }
466     
467 }
468
469
Popular Tags