001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor.ext;
015:
016: import java.awt.Color;
017: import java.awt.Component;
018: import java.awt.Dimension;
019: import java.awt.Point;
020: import java.awt.Rectangle;
021: import java.awt.event.ActionEvent;
022: import java.awt.event.FocusAdapter;
023: import java.awt.event.FocusEvent;
024: import java.awt.event.FocusListener;
025: import java.awt.event.KeyEvent;
026: import java.awt.event.MouseAdapter;
027: import java.awt.event.MouseEvent;
028: import java.beans.PropertyChangeEvent;
029: import java.beans.PropertyChangeListener;
030:
031: import javax.swing.Action;
032: import javax.swing.BorderFactory;
033: import javax.swing.JComponent;
034: import javax.swing.JLabel;
035: import javax.swing.JLayeredPane;
036: import javax.swing.JList;
037: import javax.swing.JRootPane;
038: import javax.swing.JScrollPane;
039: import javax.swing.KeyStroke;
040: import javax.swing.SwingUtilities;
041: import javax.swing.text.JTextComponent;
042:
043: import org.netbeans.editor.BaseKit;
044: import org.netbeans.editor.Settings;
045: import org.netbeans.editor.SettingsChangeEvent;
046: import org.netbeans.editor.SettingsChangeListener;
047: import org.netbeans.editor.SettingsUtil;
048: import org.netbeans.editor.Utilities;
049:
050: /**
051: * Pane displaying the completion view and accompanying components like label
052: * for title etc.
053: *
054: * @author Miloslav Metelka
055: * @version 1.00
056: */
057:
058: public class ScrollCompletionPane extends JScrollPane implements
059: CompletionPane, PropertyChangeListener, SettingsChangeListener {
060:
061: /** Additional dimension increase */
062: private static final Dimension PLUS_SIZE = new Dimension(20, 20);
063:
064: /** Reserved space around the caret */
065: private static final int CARET_THRESHOLD = 5;
066:
067: private ExtEditorUI extEditorUI;
068:
069: private JComponent view;
070:
071: private JLabel topLabel;
072:
073: private Dimension minSize;
074:
075: private Dimension maxSize;
076:
077: private FocusListener focusL;
078:
079: private ViewMouseListener viewMouseL;
080:
081: private Dimension scrollBarSize;
082:
083: public ScrollCompletionPane(ExtEditorUI extEditorUI) {
084: this .extEditorUI = extEditorUI;
085:
086: // Compute size of the scrollbars
087: Dimension smallSize = getPreferredSize();
088: setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_ALWAYS);
089: setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_ALWAYS);
090: scrollBarSize = getPreferredSize();
091: scrollBarSize.width -= smallSize.width;
092: scrollBarSize.height -= smallSize.height;
093: setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
094: setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
095:
096: // Make it invisible initially
097: super .setVisible(false);
098:
099: // Add the title component
100: installTitleComponent();
101:
102: // Add the completion view
103: CompletionView completionView = extEditorUI.getCompletion()
104: .getView();
105: if (completionView instanceof JComponent) {
106: view = (JComponent) completionView;
107: setViewportView(view);
108: }
109:
110: // Prevent the bug with displaying without the scrollbar
111: getViewport().setMinimumSize(new Dimension(4, 4));
112:
113: Settings.addSettingsChangeListener(this );
114:
115: focusL = new FocusAdapter() {
116: public void focusLost(FocusEvent evt) {
117: SwingUtilities.invokeLater(new Runnable() {
118: public void run() {
119: if (isVisible()) {
120: JTextComponent component = ScrollCompletionPane.this .extEditorUI
121: .getComponent();
122: if (component != null) {
123: java.awt.Window w = SwingUtilities
124: .windowForComponent(component);
125: Component focusOwner = (w == null) ? null
126: : w.getFocusOwner();
127: if (focusOwner == view) {
128: // Forwarding focus back to EditorPane
129: component.requestFocus();
130: } else if (focusOwner != component) {
131: setVisible(false); // both JC and component
132: // don't own the focus
133: }
134: }
135: }
136: }
137: });
138: }
139: };
140:
141: viewMouseL = new ViewMouseListener();
142: synchronized (extEditorUI.getComponentLock()) {
143: // if component already installed in ExtEditorUI simulate
144: // installation
145: JTextComponent component = extEditorUI.getComponent();
146: if (component != null) {
147: propertyChange(new PropertyChangeEvent(extEditorUI,
148: ExtEditorUI.COMPONENT_PROPERTY, null, component));
149: }
150:
151: extEditorUI.addPropertyChangeListener(this );
152: }
153:
154: putClientProperty("HelpID", ScrollCompletionPane.class
155: .getName()); // !!!
156: // NOI18N
157:
158: }
159:
160: public void settingsChange(SettingsChangeEvent evt) {
161: Class kitClass = Utilities.getKitClass(extEditorUI
162: .getComponent());
163:
164: if (kitClass != null) {
165: minSize = (Dimension) SettingsUtil.getValue(kitClass,
166: ExtSettingsNames.COMPLETION_PANE_MIN_SIZE,
167: ExtSettingsDefaults.defaultCompletionPaneMinSize);
168: maxSize = (Dimension) SettingsUtil.getValue(kitClass,
169: ExtSettingsNames.COMPLETION_PANE_MAX_SIZE,
170: ExtSettingsDefaults.defaultCompletionPaneMaxSize);
171: }
172:
173: }
174:
175: public void propertyChange(PropertyChangeEvent evt) {
176: String propName = evt.getPropertyName();
177:
178: if (ExtEditorUI.COMPONENT_PROPERTY.equals(propName)) {
179: if (evt.getNewValue() != null) { // just installed
180: JTextComponent component = extEditorUI.getComponent();
181:
182: settingsChange(null);
183:
184: if (view != null) {
185: // Register escape key
186: BaseKit kit = Utilities.getKit(component);
187: view
188: .registerKeyboardAction(
189: kit
190: .getActionByName(ExtKit.completionHideAction),
191: KeyStroke.getKeyStroke(
192: KeyEvent.VK_ESCAPE, 0),
193: JComponent.WHEN_FOCUSED);
194: // Add mouse listener
195: view.addMouseListener(viewMouseL);
196: }
197:
198: component.addFocusListener(focusL);
199:
200: installToRootPane(component);
201: } else { // just deinstalled
202: JTextComponent component = (JTextComponent) evt
203: .getOldValue();
204:
205: if (view != null) {
206: // Unregister Escape key
207: view.unregisterKeyboardAction(KeyStroke
208: .getKeyStroke(KeyEvent.VK_ESCAPE, 0));
209: view.removeMouseListener(viewMouseL);
210: }
211:
212: component.removeFocusListener(focusL);
213:
214: removeFromRootPane();
215: }
216:
217: }
218: }
219:
220: private void installToRootPane(JTextComponent component) {
221: JRootPane rp = component.getRootPane();
222: if (rp != null) {
223: rp.getLayeredPane().add(this , JLayeredPane.POPUP_LAYER, 0);
224: }
225: }
226:
227: private void removeFromRootPane() {
228: JRootPane rp = getRootPane();
229: if (rp != null) {
230: rp.getLayeredPane().remove(this );
231: }
232: }
233:
234: /** Set the pane to be visible. */
235: public void setVisible(boolean visible) {
236: super .setVisible(visible);
237:
238: if (visible) {
239: checkRootPane();
240: refresh();
241: } else { // making invisible
242: JTextComponent component = extEditorUI.getComponent();
243: if (component != null) {
244: component.requestFocus();
245: }
246: }
247: }
248:
249: private void checkRootPane() {
250: JTextComponent component = extEditorUI.getComponent();
251: if (component != null) {
252: if (component.getRootPane() != getRootPane()) {
253: removeFromRootPane();
254: installToRootPane(component);
255: }
256: }
257: }
258:
259: public void refresh() {
260: if (view instanceof JList) {
261: JList listView = (JList) view;
262: listView.ensureIndexIsVisible(listView.getSelectedIndex());
263: }
264:
265: SwingUtilities.invokeLater( // !!! ? is it needed
266: new Runnable() {
267: public void run() {
268: if (isShowing()) { // #18810
269: Rectangle bounds = getPreferredBounds();
270: setBounds(bounds);
271: revalidate();
272: }
273: }
274: });
275: }
276:
277: /** Set the title of the pane according to the completion query results. */
278: public void setTitle(String title) {
279: topLabel.setText(title);
280: }
281:
282: protected void installTitleComponent() {
283: topLabel = new JLabel();
284: topLabel.setForeground(Color.blue);
285: topLabel.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
286: setColumnHeaderView(topLabel);
287: }
288:
289: protected Dimension getTitleComponentPreferredSize() {
290: return topLabel.getPreferredSize();
291: }
292:
293: protected Rectangle getPreferredBounds() {
294: Rectangle ret = null;
295:
296: if (view != null) {
297: JTextComponent component = extEditorUI.getComponent();
298: Rectangle extBounds = extEditorUI.getExtentBounds();
299: Rectangle caretRect = (Rectangle) component.getCaret();
300:
301: // Compute available height above the caret
302: int aboveCaretY = caretRect.y - CARET_THRESHOLD;
303: int aboveCaretHeight = aboveCaretY - extBounds.y;
304:
305: // Compute available height below the caret
306: int belowCaretY = caretRect.y + caretRect.height
307: + CARET_THRESHOLD;
308: int belowCaretHeight = (extBounds.y + extBounds.height)
309: - belowCaretY;
310:
311: // Compute a maximum size the pane can occupy
312: int maxWidth = Math.min(extBounds.width, maxSize.width);
313: int maxHeight = Math.min(Math.max(aboveCaretHeight,
314: belowCaretHeight), maxSize.height);
315:
316: // Compute preferred size of pane
317: Dimension ps = getPreferredSize();
318:
319: /*
320: * Add size of the vertical scrollbar by default. This could be
321: * improved to be done only if the height exceeds the bounds.
322: */
323: ps.width += scrollBarSize.width;
324: ps.width = Math.max(Math.max(ps.width, minSize.width),
325: getTitleComponentPreferredSize().width);
326:
327: if (ps.width > maxWidth) {
328: ps.width = maxWidth;
329: ps.height += scrollBarSize.height; // will show horizontal
330: // scrollbar
331: }
332:
333: ps.height = Math.min(Math.max(ps.height, minSize.height),
334: maxHeight);
335:
336: ret = new Rectangle(ps);
337:
338: // issue 12763
339: // Check the position of the JTextComponent from the left top
340: // corner of the window. There might be toolbar or glyph gutter
341: // and these sizes must be added to the final position of the
342: // code completion
343: Point correction = new Point();
344: // compare the position of the JViewport (parent of
345: // the JTextComponent) with the root pane
346: correction.y = (component.getParent().getLocationOnScreen().y - component
347: .getRootPane().getLocationOnScreen().y) - 1;
348: correction.x = (component.getParent().getLocationOnScreen().x - component
349: .getRootPane().getLocationOnScreen().x) - 1;
350:
351: ret.x = Math.min(
352: (caretRect.x - extBounds.x) + correction.x,
353: extBounds.width - ret.width);
354:
355: // Now choose whether display the pane either above or below the
356: // caret
357: if (ret.height <= belowCaretHeight) { // display below caret
358: ret.y = belowCaretY - extBounds.y;
359: } else { // display above caret
360: ret.y = aboveCaretY - ret.height - extBounds.y;
361: }
362: ret.y += correction.y;
363: }
364:
365: return ret;
366: }
367:
368: class ViewMouseListener extends MouseAdapter {
369:
370: public void mouseClicked(MouseEvent evt) {
371: if (SwingUtilities.isLeftMouseButton(evt)) {
372: JTextComponent component = extEditorUI.getComponent();
373: if (component != null && evt.getClickCount() == 2) {
374: BaseKit kit = Utilities.getKit(component);
375: if (kit != null) {
376: Action a = kit
377: .getActionByName(BaseKit.insertBreakAction);
378: if (a != null) {
379: a.actionPerformed(new ActionEvent(
380: component,
381: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
382: }
383: }
384: }
385: }
386: }
387: }
388:
389: }
|