KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > editor > completion > CompletionLayout


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.modules.editor.completion;
21
22 import java.awt.BorderLayout JavaDoc;
23 import java.awt.Color JavaDoc;
24 import java.awt.Dimension JavaDoc;
25 import java.awt.Rectangle JavaDoc;
26 import java.awt.event.ActionEvent JavaDoc;
27 import java.awt.event.KeyEvent JavaDoc;
28 import java.awt.event.MouseAdapter JavaDoc;
29 import java.awt.event.MouseEvent JavaDoc;
30 import java.lang.ref.Reference JavaDoc;
31 import java.lang.ref.WeakReference JavaDoc;
32 import java.util.List JavaDoc;
33 import java.util.Stack JavaDoc;
34 import javax.swing.Action JavaDoc;
35 import javax.swing.BorderFactory JavaDoc;
36 import javax.swing.JComponent JavaDoc;
37 import javax.swing.JLabel JavaDoc;
38 import javax.swing.JPanel JavaDoc;
39 import javax.swing.JToolTip JavaDoc;
40 import javax.swing.KeyStroke JavaDoc;
41 import javax.swing.SwingConstants JavaDoc;
42 import javax.swing.SwingUtilities JavaDoc;
43 import javax.swing.event.ListSelectionListener JavaDoc;
44 import javax.swing.text.JTextComponent JavaDoc;
45 import org.netbeans.spi.editor.completion.CompletionDocumentation;
46 import org.netbeans.spi.editor.completion.CompletionItem;
47 import org.openide.util.NbBundle;
48
49 /**
50  * Layout of the completion, documentation and tooltip popup windows.
51  *
52  * @author Dusan Balek, Miloslav Metelka
53  */

54 public final class CompletionLayout {
55     
56     public static final int COMPLETION_ITEM_HEIGHT = 16;
57     
58     /**
59      * Visual shift of the completion window to the left
60      * so that the text in the rendered completion items.aligns horizontally
61      * with the text in the document.
62      */

63     private static final int COMPLETION_ANCHOR_HORIZONTAL_SHIFT = 22;
64     
65     /**
66      * Gap between caret and the displayed popup.
67      */

68     static final int POPUP_VERTICAL_GAP = 1;
69
70     private Reference JavaDoc<JTextComponent JavaDoc> editorComponentRef;
71
72     private final CompletionPopup completionPopup;
73     private final DocPopup docPopup;
74     private final TipPopup tipPopup;
75     
76     private Stack JavaDoc<CompletionLayoutPopup> visiblePopups;
77     
78     CompletionLayout() {
79         completionPopup = new CompletionPopup();
80         completionPopup.setLayout(this);
81         completionPopup.setPreferDisplayAboveCaret(true);
82         docPopup = new DocPopup();
83         docPopup.setLayout(this);
84         docPopup.setPreferDisplayAboveCaret(false);
85         tipPopup = new TipPopup();
86         tipPopup.setLayout(this);
87         tipPopup.setPreferDisplayAboveCaret(true);
88         visiblePopups = new Stack JavaDoc<CompletionLayoutPopup>();
89     }
90     
91     public JTextComponent JavaDoc getEditorComponent() {
92         return (editorComponentRef != null)
93         ? editorComponentRef.get()
94         : null;
95     }
96
97     public void setEditorComponent(JTextComponent JavaDoc editorComponent) {
98         hideAll();
99         this.editorComponentRef = new WeakReference JavaDoc<JTextComponent JavaDoc>(editorComponent);
100     }
101
102     private void hideAll() {
103         completionPopup.hide();
104         docPopup.hide();
105         tipPopup.hide();
106         visiblePopups.clear();
107     }
108
109     public void showCompletion(List JavaDoc data, String JavaDoc title, int anchorOffset,
110     ListSelectionListener JavaDoc listSelectionListener, boolean showShortcutHints, int selectedIndex) {
111         completionPopup.show(data, title, anchorOffset, listSelectionListener, showShortcutHints, selectedIndex);
112         if (!visiblePopups.contains(completionPopup))
113             visiblePopups.push(completionPopup);
114     }
115     
116     public boolean hideCompletion() {
117         if (completionPopup.isVisible()) {
118             completionPopup.hide();
119             completionPopup.completionScrollPane = null;
120             visiblePopups.remove(completionPopup);
121             return true;
122         } else { // not visible
123
return false;
124         }
125     }
126     
127     public boolean isCompletionVisible() {
128         return completionPopup.isVisible();
129     }
130     
131     public CompletionItem getSelectedCompletionItem() {
132         return completionPopup.getSelectedCompletionItem();
133     }
134     
135     public void processKeyEvent(KeyEvent JavaDoc evt) {
136         for (int i = visiblePopups.size() - 1; i >= 0; i--) {
137             CompletionLayoutPopup popup = visiblePopups.get(i);
138             popup.processKeyEvent(evt);
139             if (evt.isConsumed())
140                 return;
141         }
142     }
143
144     public void showDocumentation(CompletionDocumentation doc, int anchorOffset) {
145         docPopup.show(doc, anchorOffset);
146         if (!visiblePopups.contains(docPopup))
147             visiblePopups.push(docPopup);
148     }
149     
150     public boolean hideDocumentation() {
151         if (docPopup.isVisible()) {
152             docPopup.getDocumentationScrollPane().currentDocumentation = null;
153             docPopup.clearHistory();
154             docPopup.hide();
155             visiblePopups.remove(docPopup);
156             return true;
157         } else { // not visible
158
return false;
159         }
160     }
161     
162     public boolean isDocumentationVisible() {
163         return docPopup.isVisible();
164     }
165     
166     public void clearDocumentationHistory() {
167         docPopup.clearHistory();
168     }
169     
170     public void showToolTip(JToolTip JavaDoc toolTip, int anchorOffset) {
171         tipPopup.show(toolTip, anchorOffset);
172         if (!visiblePopups.contains(tipPopup))
173             visiblePopups.push(tipPopup);
174     }
175     
176     public boolean hideToolTip() {
177         if (tipPopup.isVisible()) {
178             tipPopup.hide();
179             visiblePopups.remove(tipPopup);
180             return true;
181         } else { // not visible
182
return false;
183         }
184     }
185     
186     public boolean isToolTipVisible() {
187         return tipPopup.isVisible();
188     }
189
190     /**
191      * Layout either of the copmletion, documentation or tooltip popup.
192      * <br>
193      * This method can be called recursively to update other popups
194      * once certain popup was updated.
195      *
196      * <p>
197      * The rules for the displayment are the following:
198      * <ul>
199      * <li> The tooltip popup should be above caret if there is enough space.
200      * <li> The completion popup should be above caret if there is enough space
201      * and the tooltip window is not displayed.
202      * <li> If both tooltip and completion popups are visible then vertically
203      * each should be on opposite side of the anchor bounds (caret).
204      * <li> Documentation should be preferrably shrinked if there is not enough
205      * vertical space.
206      * <li> Documentation anchoring should be aligned with completion.
207      * </ul>
208      */

209     void updateLayout(CompletionLayoutPopup popup) {
210         // Make sure the popup returns its natural preferred size
211
popup.resetPreferredSize();
212
213         if (popup == completionPopup) { // completion popup
214
if (isToolTipVisible()) {
215                 // Display on opposite side than tooltip
216
boolean wantAboveCaret = !tipPopup.isDisplayAboveCaret();
217                 if (completionPopup.isEnoughSpace(wantAboveCaret)) {
218                     completionPopup.showAlongAnchorBounds(wantAboveCaret);
219                 } else { // not enough space -> show on same side
220
Rectangle JavaDoc occupiedBounds = popup.getAnchorOffsetBounds();
221                     occupiedBounds = tipPopup.unionBounds(occupiedBounds);
222                     completionPopup.showAlongOccupiedBounds(occupiedBounds,
223                             tipPopup.isDisplayAboveCaret());
224                 }
225                 
226             } else { // tooltip not visible
227
popup.showAlongAnchorBounds();
228             }
229             
230             // Update docPopup layout if necessary
231
if (docPopup.isVisible()
232                 && (docPopup.isOverlapped(popup) || docPopup.isOverlapped(tipPopup)
233                     || docPopup.getAnchorOffset() != completionPopup.getAnchorOffset()
234                     || !docPopup.isShowRetainedPreferredSize())
235             ) {
236                 updateLayout(docPopup);
237             }
238             
239         } else if (popup == docPopup) { // documentation popup
240
if (isCompletionVisible()) {
241                 // Documentation must sync anchoring with completion
242
popup.setAnchorOffset(completionPopup.getAnchorOffset());
243             }
244             
245             Rectangle JavaDoc occupiedBounds = popup.getAnchorOffsetBounds();
246             occupiedBounds = tipPopup.unionBounds(completionPopup.unionBounds(occupiedBounds));
247             docPopup.showAlongOccupiedBounds(occupiedBounds);
248
249         } else if (popup == tipPopup) { // tooltip popup
250
popup.showAlongAnchorBounds(); // show possibly above the caret
251
if (completionPopup.isOverlapped(popup) || docPopup.isOverlapped(popup)) {
252                 // docPopup layout will be handled as part of completion popup layout
253
updateLayout(completionPopup);
254             }
255         }
256     }
257     
258     CompletionPopup testGetCompletionPopup() {
259         return completionPopup;
260     }
261     
262     private static final class CompletionPopup extends CompletionLayoutPopup {
263         
264         private CompletionScrollPane completionScrollPane;
265         
266         public void show(List JavaDoc data, String JavaDoc title, int anchorOffset,
267         ListSelectionListener JavaDoc listSelectionListener, boolean showShortcutHints, int selectedIndex) {
268             
269         JTextComponent JavaDoc editorComponent = getEditorComponent();
270         if (editorComponent == null) {
271         return;
272         }
273
274             Dimension JavaDoc lastSize;
275             int lastAnchorOffset = getAnchorOffset();
276
277             if (isVisible() && ((getContentComponent() == completionScrollPane)^(showShortcutHints))) {
278                 lastSize = getContentComponent().getSize();
279                 resetPreferredSize();
280
281             } else { // not yet visible => create completion scrollpane
282
lastSize = new Dimension JavaDoc(0, 0); // no last size => use (0,0)
283

284                 completionScrollPane = new CompletionScrollPane(
285                     editorComponent, listSelectionListener,
286                     new MouseAdapter JavaDoc() {
287                         public void mouseClicked(MouseEvent JavaDoc evt) {
288                 JTextComponent JavaDoc c = getEditorComponent();
289                             if (SwingUtilities.isLeftMouseButton(evt)) {
290                                 if (c != null && evt.getClickCount() == 2 ) {
291                                     CompletionItem selectedItem
292                                             = completionScrollPane.getSelectedCompletionItem();
293                                     if (selectedItem != null) {
294                                         selectedItem.defaultAction(c);
295                                     }
296                                 }
297                             }
298                         }
299                     }
300                 );
301                 
302                 completionScrollPane.getViewport().getView().setFont(getEditorComponent().getFont());
303                 
304                 if (showShortcutHints) {
305                     JPanel JavaDoc panel = new JPanel JavaDoc();
306                     panel.setLayout(new BorderLayout JavaDoc());
307                     panel.add(completionScrollPane, BorderLayout.CENTER);
308                     JLabel JavaDoc label = new JLabel JavaDoc();
309                     label.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, Color.white),
310                             BorderFactory.createCompoundBorder(BorderFactory.createMatteBorder(0, 1, 1, 1, Color.gray), BorderFactory.createEmptyBorder(2, 2, 2, 2))));
311                     label.setFont(label.getFont().deriveFont((float)label.getFont().getSize() - 2));
312                     label.setHorizontalAlignment(SwingConstants.RIGHT);
313                     label.setText(NbBundle.getMessage(CompletionLayout.class, "TXT_completion_shrtcut_tips")); //NOI18N
314
panel.add(label, BorderLayout.SOUTH);
315                     setContentComponent(panel);
316                 } else {
317                     setContentComponent(completionScrollPane);
318                 }
319             }
320             // Set the new data
321
completionScrollPane.setData(data, title, selectedIndex);
322             setAnchorOffset(anchorOffset);
323
324             Dimension JavaDoc prefSize = getPreferredSize();
325
326             boolean changePopupSize;
327             if (isVisible()) {
328                 changePopupSize = (prefSize.height != lastSize.height)
329                         || (prefSize.width != lastSize.width)
330                         || anchorOffset != lastAnchorOffset;
331
332             } else { // not visible yet
333
changePopupSize = true;
334             }
335
336             if (changePopupSize) {
337                 // Do not change the popup's above/below caret positioning
338
// when the popup is already displayed
339
getLayout().updateLayout(this);
340                 
341             } // otherwise present popup size will be retained
342
}
343
344         public CompletionItem getSelectedCompletionItem() {
345             return isVisible() ? completionScrollPane.getSelectedCompletionItem() : null;
346         }
347
348         public void processKeyEvent(KeyEvent JavaDoc evt) {
349             if (isVisible()) {
350                 Object JavaDoc actionMapKey = completionScrollPane.getInputMap().get(
351                         KeyStroke.getKeyStrokeForEvent(evt));
352                 
353                 if (actionMapKey != null) {
354                     Action JavaDoc action = completionScrollPane.getActionMap().get(actionMapKey);
355                     if (action != null) {
356                         action.actionPerformed(new ActionEvent JavaDoc(completionScrollPane, 0, null));
357                         evt.consume();
358                     }
359                 }
360             }
361         }
362
363         protected int getAnchorHorizontalShift() {
364             return COMPLETION_ANCHOR_HORIZONTAL_SHIFT;
365         }
366
367     }
368     
369     private static final class DocPopup extends CompletionLayoutPopup {
370         
371         private DocumentationScrollPane getDocumentationScrollPane() {
372             return (DocumentationScrollPane)getContentComponent();
373         }
374         
375         protected void show(CompletionDocumentation doc, int anchorOffset) {
376         JTextComponent JavaDoc editorComponent = getEditorComponent();
377         if (editorComponent == null) {
378         return;
379         }
380
381             if (!isVisible()) { // documentation already visible
382
setContentComponent(new DocumentationScrollPane(editorComponent));
383             }
384             
385             getDocumentationScrollPane().setData(doc);
386             
387             if (!isVisible()) { // do not check for size as it should remain the same
388
// Set anchoring only if not displayed yet because completion
389
// may have overriden the anchoring
390
setAnchorOffset(anchorOffset);
391                 getLayout().updateLayout(this);
392             } // otherwise leave present doc displayed
393
}
394
395         public void processKeyEvent(KeyEvent JavaDoc evt) {
396             if (isVisible()) {
397                 Object JavaDoc actionMapKey = getDocumentationScrollPane().getInputMap().get(
398                         KeyStroke.getKeyStrokeForEvent(evt));
399                 
400                 if (actionMapKey != null) {
401                     Action JavaDoc action = getDocumentationScrollPane().getActionMap().get(actionMapKey);
402                     if (action != null) {
403                         action.actionPerformed(new ActionEvent JavaDoc(getDocumentationScrollPane(), 0, null));
404                         evt.consume();
405                     }
406                 }
407             }
408         }
409         
410         public void clearHistory() {
411             if (isVisible()) {
412                 getDocumentationScrollPane().clearHistory();
413             }
414         }
415
416         protected int getAnchorHorizontalShift() {
417             return COMPLETION_ANCHOR_HORIZONTAL_SHIFT;
418         }
419
420     }
421     
422     private static final class TipPopup extends CompletionLayoutPopup {
423         
424         protected void show(JToolTip JavaDoc toolTip, int anchorOffset) {
425             JComponent JavaDoc lastComponent = null;
426             if (isVisible()) { // tooltip already visible
427
lastComponent = getContentComponent();
428             }
429             
430             setContentComponent(toolTip);
431             setAnchorOffset(anchorOffset);
432
433             // Check whether doc is visible and if so then display
434
// on the opposite side
435
if (lastComponent != toolTip) {
436                 getLayout().updateLayout(this);
437             }
438     }
439
440         public void processKeyEvent(KeyEvent JavaDoc evt) {
441             if (isVisible()) {
442         if (KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0).equals(
443             KeyStroke.getKeyStrokeForEvent(evt))
444         ) {
445             evt.consume();
446             CompletionImpl.get().hideToolTip();
447         }
448             }
449         }
450         
451     }
452     
453 }
454
Popular Tags