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.util.Iterator;
019: import java.util.List;
020:
021: import javax.swing.JLabel;
022: import javax.swing.JList;
023: import javax.swing.text.BadLocationException;
024: import javax.swing.text.JTextComponent;
025:
026: import org.netbeans.editor.BaseDocument;
027: import org.netbeans.editor.SyntaxSupport;
028:
029: /**
030: * Code completion querying support.
031: *
032: * @author Miloslav Metelka
033: * @version 1.00
034: */
035:
036: public interface CompletionQuery {
037:
038: /**
039: * Perform the query on the given component. The query usually gets the
040: * component's document, the caret position and searches back to find the
041: * last command start. Then it inspects the text up to the caret position
042: * and returns the result.
043: *
044: * @param component
045: * the component to use in this query.
046: * @param offset
047: * position in the component's document to which the query will
048: * be performed. Usually it's a caret position.
049: * @param support
050: * syntax-support that will be used during resolving of the
051: * query.
052: * @return result of the query or null if there's no result.
053: */
054: public Result query(JTextComponent component, int offset,
055: SyntaxSupport support);
056:
057: /**
058: * Result of the query or expression evaluation. Simply said it consists of
059: * the list of the data and title and an internal information about how to
060: * substitute the text.
061: */
062: public interface Result {
063:
064: /**
065: * Get the list with the items satisfying the query. The list must
066: * always be non-null. If there are no data it will have a zero size.
067: *
068: * @return List of objects implementing ResultItem.
069: */
070: public List getData();
071:
072: /** Get the title describing the result or null if there's no title. */
073: public String getTitle();
074:
075: /**
076: * Substitute the text in the document if the user picks the item from
077: * the data with the given index either by pressing ENTER or
078: * doubleclicking the item by mouse.
079: *
080: * @param dataIndex
081: * current selected item index in the current data list. It
082: * can be used for making the substitution.
083: * @param shift
084: * indicates request for some kind of different behaviour,
085: * means that e.g. user hold shift while pressing ENTER.
086: * @return whether the text was substituted or not
087: */
088: public boolean substituteText(int dataIndex, boolean shift);
089:
090: /**
091: * Substitute the text that is common for all the data entries. This is
092: * used to update the document with the common text when the user
093: * presses the TAB key.
094: *
095: * @param dataIndex
096: * current selected item index in the current data list.
097: * Although normally it shouldn't be necessary for making the
098: * substitution, the completion implementations can use it
099: * for customized behavior.
100: * @return whether the text was substituted or not
101: */
102: public boolean substituteCommonText(int dataIndex);
103:
104: }
105:
106: /**
107: * The very basic funztionality of Result is implemented by this class, but
108: * parts general enough to not need to be overriden.
109: */
110: public static abstract class AbstractResult implements Result {
111:
112: /** The List of the ResultItem instances - the content of the result */
113: private List data;
114:
115: /** The title of the result */
116: private String title;
117:
118: public AbstractResult(List data, String title) {
119: this .data = data;
120: this .title = title;
121: }
122:
123: public List getData() {
124: return data;
125: }
126:
127: public String getTitle() {
128: return title;
129: }
130:
131: }
132:
133: /**
134: * Full implementation of Result, managing substitution of the text and
135: * finding and substituting common prefix of items
136: */
137: public static class DefaultResult extends AbstractResult {
138:
139: private JTextComponent component;
140: private int offset;
141: private int len;
142:
143: /**
144: * Constructor for DefaultResult
145: *
146: * @param component
147: * the JTextComponent the result is tightened with, used for
148: * operations on its Document, caret, selection and so.
149: * @param title
150: * the title displayed in header of completion window
151: * @param data
152: * the list of ResultItem instances to be displayed in
153: * completion window, may be null.
154: * @param the
155: * offset in the document corresponding to the start of the
156: * text occassionally replaced by the result.
157: * @param the
158: * length of the text to be replaced.
159: */
160: public DefaultResult(JTextComponent component, String title,
161: List data, int offset, int len) {
162: super (data, title);
163: this .component = component;
164: this .offset = offset;
165: this .len = len;
166: }
167:
168: /**
169: * Internal method used to find longest common prefix of two Strings. it
170: * is made private, because I'm going to change its interface for better
171: * performance.
172: */
173: private int getCommonPrefixLength(char[] commonPrefix, int len,
174: String s) {
175: char[] c = s.toCharArray();
176: int i = 0;
177: if (len > c.length)
178: len = c.length;
179: for (; i < len; i++) {
180: if (commonPrefix[i] != c[i])
181: break;
182: }
183: return i;
184: }
185:
186: /**
187: * Update the text in response to pressing TAB key. Searches through all
188: * items of this result looking for longest common prefix and then calls
189: * the substitution method on selected item providing it with the length
190: * of common part.
191: *
192: * @return whether the text was successfully updated
193: */
194: public boolean substituteCommonText(int dataIndex) {
195: List data = getData();
196: if (data.size() == 0)
197: return false;
198:
199: Iterator i = data.iterator();
200: char[] commonPrefix = ((CompletionQuery.ResultItem) i
201: .next()).getItemText().toCharArray();
202: int commonLength = commonPrefix.length;
203:
204: for (; i.hasNext();) {
205: String second = ((CompletionQuery.ResultItem) i.next())
206: .getItemText();
207: commonLength = getCommonPrefixLength(commonPrefix,
208: commonLength, second);
209: }
210: CompletionQuery.ResultItem actData = (CompletionQuery.ResultItem) data
211: .get(dataIndex);
212: return actData.substituteCommonText(component, offset, len,
213: commonLength);
214: }
215:
216: /**
217: * Update the text in response to pressing ENTER.
218: *
219: * @return whether the text was successfully updated
220: */
221: public boolean substituteText(int dataIndex, boolean shift) {
222: Object actData = getData().get(dataIndex);
223: return ((CompletionQuery.ResultItem) actData)
224: .substituteText(component, offset, len, shift);
225: }
226: }
227:
228: /**
229: * An interface used as an item of List returned by
230: * CompletionQuery.Result.getData() Such items are then able to their part
231: * in Completion process themselves
232: */
233: public static interface ResultItem {
234: /**
235: * Update the text in response to pressing TAB key (or any key mapped to
236: * this function) on this element
237: *
238: * @param c
239: * the text component to operate on, enables implementation
240: * to do things like movement of caret.
241: * @param offset
242: * the offset where the item should be placed
243: * @param len
244: * the length of recognized text which should be replaced
245: * @param subLen
246: * the length of common part - the length of text that should
247: * be inserted after removal of recognized text
248: * @return whether the text was successfully updated
249: */
250: public boolean substituteCommonText(JTextComponent c,
251: int offset, int len, int subLen);
252:
253: /**
254: * Update the text in response to pressing ENTER on this element.
255: *
256: * @param c
257: * the text component to operate on, enables implementation
258: * to do things like movement of caret.
259: * @param offset
260: * the offset where the item should be placed
261: * @param len
262: * the length of recognized text which should be replaced
263: * @param shift
264: * the flag that instructs completion to behave somehow
265: * differently - enables more kinds of invocation of
266: * substituteText
267: * @return whether the text was successfully updated
268: */
269: public boolean substituteText(JTextComponent c, int offset,
270: int len, boolean shift);
271:
272: /**
273: * Says what text would this Element use if substituteText is called.
274: *
275: * @return the substitution text, usable e.g. for finding common
276: * text/its' length
277: */
278: public String getItemText();
279:
280: /**
281: * Prepare proper component for painting value of <CODE>this</CODE>.
282: *
283: * @param JList
284: * the list this item will be drawn into, usefull e.g. for
285: * obtaining preferred colors.
286: * @param isSelected
287: * tells if this item is just selected, for using proper
288: * color scheme.
289: * @param cellHasFocus
290: * tells it this item is just focused.
291: * @return the component usable for painting this value
292: */
293: public Component getPaintComponent(JList list,
294: boolean isSelected, boolean cellHasFocus);
295: }
296:
297: /**
298: * A class providing generic, nearly full implementation of ResultItem
299: */
300: public abstract static class AbstractResultItem implements
301: CompletionQuery.ResultItem {
302: /* The text this item would expand to */
303: protected String text;
304:
305: /**
306: * Create new ResultItem for given text, should be used in subclass
307: * constructors
308: */
309: public AbstractResultItem(String text) {
310: this .text = text;
311: }
312:
313: /**
314: * Generic implementation, behaves just as described in specification in
315: * substituteCommonText() - removes <CODE>len</CODE> characters at
316: * <CODE>offset</CODE> out of document and then inserts <CODE>subLen<CODE>
317: * characters from the <CODE>text</CODE>
318: */
319: public boolean substituteCommonText(JTextComponent c,
320: int offset, int len, int subLen) {
321: BaseDocument doc = (BaseDocument) c.getDocument();
322: try {
323: doc.atomicLock();
324: try {
325: doc.remove(offset, len);
326: doc.insertString(offset, text.substring(0, subLen),
327: null);
328: } finally {
329: doc.atomicUnlock();
330: }
331: } catch (BadLocationException exc) {
332: return false; // not sucessfull
333: }
334: return true;
335: }
336:
337: /**
338: * Generic implementation, behaves just as described in specification in
339: * substituteText() - removes <CODE>len</CODE> characters at <CODE>offset</CODE>
340: * out of document and then inserts whole <CODE>text</CODE>. Ignores
341: * <CODE>shift</CODE> argument.
342: */
343: public boolean substituteText(JTextComponent c, int offset,
344: int len, boolean shift) {
345: BaseDocument doc = (BaseDocument) c.getDocument();
346: try {
347: doc.atomicLock();
348: try {
349: doc.remove(offset, len);
350: doc.insertString(offset, text, null);
351: } finally {
352: doc.atomicUnlock();
353: }
354: } catch (BadLocationException exc) {
355: return false; // not sucessfull
356: }
357: return true;
358: }
359:
360: /**
361: * @return the text this item would expand to.
362: */
363: public String getItemText() {
364: return text;
365: }
366:
367: }
368:
369: public static class DefaultResultItem extends
370: CompletionQuery.AbstractResultItem {
371: /**
372: * The cache for component used for painting value of <CODE>this</CODE>
373: * this component is reused, on every call to getPaintComponent it is
374: * set up and then painted. By default, this component is hold opaque.
375: */
376: static JLabel rubberStamp = new JLabel();
377:
378: static {
379: rubberStamp.setOpaque(true);
380: }
381:
382: /** Color used for painting text of non-selected item */
383: protected Color foreColor;
384:
385: public DefaultResultItem(String text, Color foreColor) {
386: super (text);
387: this .foreColor = foreColor;
388: }
389:
390: public Component getPaintComponent(JList list,
391: boolean isSelected, boolean cellHasFocus) {
392: rubberStamp.setText(" " + text);
393: if (isSelected) {
394: rubberStamp
395: .setBackground(list.getSelectionBackground());
396: rubberStamp
397: .setForeground(list.getSelectionForeground());
398: } else {
399: rubberStamp.setBackground(list.getBackground());
400: rubberStamp.setForeground(foreColor);
401: }
402: return rubberStamp;
403: }
404: }
405:
406: }
|