001: /*
002: * :tabSize=8:indentSize=8:noTabs=false:
003: * :folding=explicit:collapseFolds=1:
004: *
005: * Copyright (C) 2007 KazutoshiSatoda
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * as published by the Free Software Foundation; either version 2
010: * of the License, or any later version.
011: *
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015: * GNU General Public License for more details.
016: *
017: * You should have received a copy of the GNU General Public License
018: * along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
020: */
021:
022: package org.gjt.sp.jedit.gui;
023:
024: //{{{ Imports
025: import java.awt.BorderLayout;
026: import java.awt.Component;
027: import java.awt.Point;
028: import java.awt.Rectangle;
029: import java.awt.Window;
030:
031: import java.awt.event.KeyAdapter;
032: import java.awt.event.KeyEvent;
033: import java.awt.event.KeyListener;
034: import java.awt.event.MouseAdapter;
035: import java.awt.event.MouseEvent;
036: import java.awt.event.WindowEvent;
037: import java.awt.event.WindowFocusListener;
038:
039: import javax.swing.AbstractListModel;
040: import javax.swing.JList;
041: import javax.swing.JPanel;
042: import javax.swing.JScrollPane;
043: import javax.swing.JWindow;
044: import javax.swing.ListSelectionModel;
045: import javax.swing.ListCellRenderer;
046: import javax.swing.ScrollPaneConstants;
047: import javax.swing.SwingUtilities;
048:
049: import org.gjt.sp.jedit.GUIUtilities;
050: import org.gjt.sp.jedit.View;
051:
052: //}}}
053:
054: /**
055: * Popup window for word completion in text area.
056: * This class provides basic UI of completion popup.
057: *
058: * @since jEdit 4.3pre11
059: */
060: public class CompletionPopup extends JWindow {
061: //{{{ interface Candidates
062: /**
063: * Candidates of completion.
064: */
065: public interface Candidates {
066: /**
067: * Returns the number of candidates.
068: */
069: public int getSize();
070:
071: /**
072: * Returns whether this completion is still valid.
073: */
074: public boolean isValid();
075:
076: /**
077: * Do the completion.
078: */
079: public void complete(int index);
080:
081: /**
082: * Returns a component to render a cell for the index
083: * in the popup.
084: */
085: public Component getCellRenderer(JList list, int index,
086: boolean isSelected, boolean cellHasFocus);
087:
088: /**
089: * Returns a description text shown when the index is
090: * selected in the popup, or null if no description is
091: * available.
092: */
093: public String getDescription(int index);
094: } //}}}
095:
096: //{{{ CompletionPopup constructor
097: /**
098: * Create a completion popup.
099: * It is not shown until reset() method is called with valid
100: * candidates. All key events for the view are intercepted by
101: * this popup untill end of completion.
102: */
103: public CompletionPopup(View view, Point location) {
104: super (view);
105: this .view = view;
106: this .candidates = null;
107: this .list = new JList();
108:
109: KeyHandler keyHandler = new KeyHandler();
110:
111: list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
112: list.setCellRenderer(new CellRenderer());
113: list.addKeyListener(keyHandler);
114: list.addMouseListener(new MouseHandler());
115:
116: JPanel content = new JPanel(new BorderLayout());
117: content.setFocusTraversalKeysEnabled(false);
118: // stupid scrollbar policy is an attempt to work around
119: // bugs people have been seeing with IBM's JDK -- 7 Sep 2000
120: JScrollPane scroller = new JScrollPane(list,
121: ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
122: ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
123: content.add(scroller, BorderLayout.CENTER);
124: setContentPane(content);
125: addWindowFocusListener(new WindowFocusHandler());
126:
127: setLocation(location);
128: view.setKeyEventInterceptor(keyHandler);
129: } //}}}
130:
131: //{{{ dispose() method
132: /**
133: * Quit completion.
134: */
135: public void dispose() {
136: if (isDisplayable()) {
137: view.setKeyEventInterceptor(null);
138: super .dispose();
139:
140: // This is a workaround to ensure setting the
141: // focus back to the textArea. Without this, the
142: // focus gets lost after closing the popup in
143: // some environments. It seems to be a bug in
144: // J2SE 1.4 or 5.0. Probably it relates to the
145: // following one.
146: // "Frame does not receives focus after closing
147: // of the owned window"
148: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4810575
149: SwingUtilities.invokeLater(new Runnable() {
150: public void run() {
151: view.getTextArea().requestFocus();
152: }
153: });
154: }
155: } //}}}
156:
157: //{{{ reset() method
158: /**
159: * Start completion.
160: * @param candidates The candidates of this completion
161: * @param active Ser focus to the popup
162: */
163: public void reset(Candidates candidates, boolean active) {
164: if (candidates == null || !candidates.isValid()
165: || candidates.getSize() <= 0) {
166: dispose();
167: return;
168: }
169:
170: this .candidates = candidates;
171: list.setModel(new CandidateListModel());
172: list.setVisibleRowCount(Math.min(candidates.getSize(), 8));
173: pack();
174: setLocation(fitInScreen(getLocation(null), this , view
175: .getTextArea().getPainter().getFontMetrics()
176: .getHeight()));
177: if (active) {
178: setSelectedIndex(0);
179: GUIUtilities.requestFocus(this , list);
180: }
181: setVisible(true);
182: } //}}}
183:
184: //{{{ getCandidates() method
185: /**
186: * Current candidates of completion.
187: */
188: public Candidates getCandidates() {
189: return candidates;
190: } //}}}
191:
192: //{{{ getSelectedIndex() method
193: /**
194: * Returns index of current selection.
195: * Returns -1 if nothing is selected.
196: */
197: public int getSelectedIndex() {
198: return list.getSelectedIndex();
199: } //}}}
200:
201: //{{{ setSelectedIndex() method
202: /**
203: * Set selection.
204: */
205: public void setSelectedIndex(int index) {
206: if (candidates != null && 0 <= index
207: && index < candidates.getSize()) {
208: list.setSelectedIndex(index);
209: list.ensureIndexIsVisible(index);
210: String description = candidates.getDescription(index);
211: if (description != null) {
212: view.getStatus().setMessageAndClear(description);
213: }
214: }
215: } //}}}
216:
217: //{{{ doSelectedCompletion() method
218: /**
219: * Do completion with current selection and quit.
220: */
221: public boolean doSelectedCompletion() {
222: int selected = list.getSelectedIndex();
223: if (candidates != null && 0 <= selected
224: && selected < candidates.getSize()) {
225: candidates.complete(selected);
226: dispose();
227: return true;
228: }
229: return false;
230: } //}}}
231:
232: //{{{ keyPressed() medhod
233: /**
234: * Handle key pressed events.
235: * Override this method to make additional key handing.
236: */
237: protected void keyPressed(KeyEvent e) {
238: } //}}}
239:
240: //{{{ keyTyped() medhod
241: /**
242: * Handle key typed events.
243: * Override this method to make additional key handing.
244: */
245: protected void keyTyped(KeyEvent e) {
246: } //}}}
247:
248: //{{{ Private members
249:
250: //{{{ Instance variables
251: private final View view;
252: private Candidates candidates;
253: private final JList list;
254:
255: //}}}
256:
257: //{{{ fitInScreen() method
258: private static Point fitInScreen(Point p, Window w, int lineHeight) {
259: Rectangle screenSize = w.getGraphicsConfiguration().getBounds();
260: if (p.y + w.getHeight() >= screenSize.height)
261: p.y = p.y - w.getHeight() - lineHeight;
262: return p;
263: } //}}}
264:
265: //{{{ moveRelative method()
266: private void moveRelative(int n) {
267: int selected = list.getSelectedIndex();
268:
269: int newSelect = selected + n;
270: if (newSelect < 0) {
271: newSelect = 0;
272: } else {
273: int numItems = list.getModel().getSize();
274: if (numItems < 1) {
275: return;
276: }
277: if (newSelect >= numItems) {
278: newSelect = numItems - 1;
279: }
280: }
281:
282: if (newSelect != selected) {
283: setSelectedIndex(newSelect);
284: }
285: } //}}}
286:
287: //{{{ moveRelativePages() method
288: private void moveRelativePages(int n) {
289: int pageSize = list.getVisibleRowCount() - 1;
290: moveRelative(pageSize * n);
291: } //}}}
292:
293: //{{{ passKeyEventToView() method
294: private void passKeyEventToView(KeyEvent e) {
295: // Remove intercepter to avoid infinite recursion.
296: KeyListener interceptor = view.getKeyEventInterceptor();
297: view.setKeyEventInterceptor(null);
298: view.getInputHandler().processKeyEvent(e, View.VIEW, false);
299: view.setKeyEventInterceptor(interceptor);
300: } //}}}
301:
302: //{{{ CandidateListModel class
303: private class CandidateListModel extends AbstractListModel {
304: public int getSize() {
305: return candidates.getSize();
306: }
307:
308: public Object getElementAt(int index) {
309: // This value is not used.
310: // The list is only rendered by components
311: // returned by getCellRenderer().
312: return candidates;
313: }
314: } //}}}
315:
316: //{{{ CellRenderer class
317: private class CellRenderer implements ListCellRenderer {
318: public Component getListCellRendererComponent(JList list,
319: Object value, int index, boolean isSelected,
320: boolean cellHasFocus) {
321: return candidates.getCellRenderer(list, index, isSelected,
322: cellHasFocus);
323: }
324: } //}}}
325:
326: //{{{ KeyHandler class
327: private class KeyHandler extends KeyAdapter {
328: //{{{ keyPressed() method
329: public void keyPressed(KeyEvent e) {
330: CompletionPopup.this .keyPressed(e);
331:
332: if (candidates == null || !candidates.isValid()) {
333: dispose();
334: } else if (!e.isConsumed()) {
335: switch (e.getKeyCode()) {
336: case KeyEvent.VK_TAB:
337: case KeyEvent.VK_ENTER:
338: if (doSelectedCompletion()) {
339: e.consume();
340: } else {
341: dispose();
342: }
343: break;
344: case KeyEvent.VK_ESCAPE:
345: dispose();
346: e.consume();
347: break;
348: case KeyEvent.VK_UP:
349: moveRelative(-1);
350: e.consume();
351: break;
352: case KeyEvent.VK_DOWN:
353: moveRelative(1);
354: e.consume();
355: break;
356: case KeyEvent.VK_PAGE_UP:
357: moveRelativePages(-1);
358: e.consume();
359: break;
360: case KeyEvent.VK_PAGE_DOWN:
361: moveRelativePages(1);
362: e.consume();
363: break;
364: default:
365: if (e.isActionKey() || e.isControlDown()
366: || e.isAltDown() || e.isMetaDown()) {
367: dispose();
368: }
369: break;
370: }
371: }
372:
373: if (!e.isConsumed()) {
374: passKeyEventToView(e);
375: }
376: } //}}}
377:
378: //{{{ keyTyped() method
379: public void keyTyped(KeyEvent e) {
380: CompletionPopup.this .keyTyped(e);
381:
382: if (candidates == null || !candidates.isValid()) {
383: dispose();
384: }
385:
386: if (!e.isConsumed()) {
387: passKeyEventToView(e);
388: }
389: } //}}}
390: } //}}}
391:
392: //{{{ MouseHandler class
393: private class MouseHandler extends MouseAdapter {
394: public void mouseClicked(MouseEvent e) {
395: if (doSelectedCompletion()) {
396: e.consume();
397: } else {
398: dispose();
399: }
400: }
401: } //}}}
402:
403: //{{{ WindowFocusHandler class
404: private class WindowFocusHandler implements WindowFocusListener {
405: public void windowGainedFocus(WindowEvent e) {
406: }
407:
408: public void windowLostFocus(WindowEvent e) {
409: dispose();
410: }
411: } //}}}
412:
413: //}}}
414: }
|