001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.ui.text.template.contentassist;
011:
012: import java.util.HashMap;
013: import java.util.HashSet;
014: import java.util.Iterator;
015: import java.util.Map;
016: import java.util.Set;
017:
018: import org.eclipse.core.runtime.Assert;
019:
020: import org.eclipse.swt.graphics.Image;
021: import org.eclipse.swt.graphics.Point;
022:
023: import org.eclipse.jface.text.BadLocationException;
024: import org.eclipse.jface.text.DocumentEvent;
025: import org.eclipse.jface.text.IDocument;
026: import org.eclipse.jface.text.ITextViewer;
027: import org.eclipse.jface.text.contentassist.ICompletionProposal;
028: import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2;
029: import org.eclipse.jface.text.contentassist.IContextInformation;
030:
031: /**
032: * Global state for templates. Selecting a proposal for the master template variable
033: * will cause the value (and the proposals) for the slave variables to change.
034: *
035: * @see MultiVariable
036: */
037: public class MultiVariableGuess {
038:
039: /**
040: * Implementation of the <code>ICompletionProposal</code> interface and extension.
041: */
042: private static class Proposal implements ICompletionProposal,
043: ICompletionProposalExtension2 {
044:
045: /** The string to be displayed in the completion proposal popup */
046: private String fDisplayString;
047: /** The replacement string */
048: String fReplacementString;
049: /** The replacement offset */
050: private int fReplacementOffset;
051: /** The replacement length */
052: private int fReplacementLength;
053: /** The cursor position after this proposal has been applied */
054: private int fCursorPosition;
055: /** The image to be displayed in the completion proposal popup */
056: private Image fImage;
057: /** The context information of this proposal */
058: private IContextInformation fContextInformation;
059: /** The additional info of this proposal */
060: private String fAdditionalProposalInfo;
061:
062: /**
063: * Creates a new completion proposal based on the provided information. The replacement string is
064: * considered being the display string too. All remaining fields are set to <code>null</code>.
065: *
066: * @param replacementString the actual string to be inserted into the document
067: * @param replacementOffset the offset of the text to be replaced
068: * @param replacementLength the length of the text to be replaced
069: * @param cursorPosition the position of the cursor following the insert relative to replacementOffset
070: */
071: public Proposal(String replacementString,
072: int replacementOffset, int replacementLength,
073: int cursorPosition) {
074: this (replacementString, replacementOffset,
075: replacementLength, cursorPosition, null, null,
076: null, null);
077: }
078:
079: /**
080: * Creates a new completion proposal. All fields are initialized based on the provided information.
081: *
082: * @param replacementString the actual string to be inserted into the document
083: * @param replacementOffset the offset of the text to be replaced
084: * @param replacementLength the length of the text to be replaced
085: * @param cursorPosition the position of the cursor following the insert relative to replacementOffset
086: * @param image the image to display for this proposal
087: * @param displayString the string to be displayed for the proposal
088: * @param contextInformation the context information associated with this proposal
089: * @param additionalProposalInfo the additional information associated with this proposal
090: */
091: public Proposal(String replacementString,
092: int replacementOffset, int replacementLength,
093: int cursorPosition, Image image, String displayString,
094: IContextInformation contextInformation,
095: String additionalProposalInfo) {
096: Assert.isNotNull(replacementString);
097: Assert.isTrue(replacementOffset >= 0);
098: Assert.isTrue(replacementLength >= 0);
099: Assert.isTrue(cursorPosition >= 0);
100:
101: fReplacementString = replacementString;
102: fReplacementOffset = replacementOffset;
103: fReplacementLength = replacementLength;
104: fCursorPosition = cursorPosition;
105: fImage = image;
106: fDisplayString = displayString;
107: fContextInformation = contextInformation;
108: fAdditionalProposalInfo = additionalProposalInfo;
109: }
110:
111: /*
112: * @see ICompletionProposal#apply(IDocument)
113: */
114: public void apply(IDocument document) {
115: try {
116: document.replace(fReplacementOffset,
117: fReplacementLength, fReplacementString);
118: } catch (BadLocationException x) {
119: // ignore
120: }
121: }
122:
123: /*
124: * @see ICompletionProposal#getSelection(IDocument)
125: */
126: public Point getSelection(IDocument document) {
127: return new Point(fReplacementOffset + fCursorPosition, 0);
128: }
129:
130: /*
131: * @see ICompletionProposal#getContextInformation()
132: */
133: public IContextInformation getContextInformation() {
134: return fContextInformation;
135: }
136:
137: /*
138: * @see ICompletionProposal#getImage()
139: */
140: public Image getImage() {
141: return fImage;
142: }
143:
144: /*
145: * @see ICompletionProposal#getDisplayString()
146: */
147: public String getDisplayString() {
148: if (fDisplayString != null)
149: return fDisplayString;
150: return fReplacementString;
151: }
152:
153: /*
154: * @see ICompletionProposal#getAdditionalProposalInfo()
155: */
156: public String getAdditionalProposalInfo() {
157: return fAdditionalProposalInfo;
158: }
159:
160: /*
161: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#apply(org.eclipse.jface.text.ITextViewer, char, int, int)
162: */
163: public void apply(ITextViewer viewer, char trigger,
164: int stateMask, int offset) {
165: apply(viewer.getDocument());
166: }
167:
168: /*
169: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#selected(org.eclipse.jface.text.ITextViewer, boolean)
170: */
171: public void selected(ITextViewer viewer, boolean smartToggle) {
172: }
173:
174: /*
175: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#unselected(org.eclipse.jface.text.ITextViewer)
176: */
177: public void unselected(ITextViewer viewer) {
178: }
179:
180: /*
181: * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, int, org.eclipse.jface.text.DocumentEvent)
182: */
183: public boolean validate(IDocument document, int offset,
184: DocumentEvent event) {
185: try {
186: String content = document.get(fReplacementOffset,
187: fReplacementLength);
188: if (content.startsWith(fReplacementString))
189: return true;
190: } catch (BadLocationException e) {
191: // ignore concurrently modified document
192: }
193: return false;
194: }
195: }
196:
197: private final Map fDependencies = new HashMap();
198: private final Map fBackwardDeps = new HashMap();
199: private final Map fPositions = new HashMap();
200:
201: public MultiVariableGuess() {
202: }
203:
204: public ICompletionProposal[] getProposals(
205: final MultiVariable variable, int offset, int length) {
206: MultiVariable master = (MultiVariable) fBackwardDeps
207: .get(variable);
208: Object[] choices;
209: if (master == null)
210: choices = variable.getChoices();
211: else
212: choices = variable.getChoices(master.getCurrentChoice());
213:
214: if (choices == null)
215: return null;
216:
217: if (fDependencies.containsKey(variable)) {
218: ICompletionProposal[] ret = new ICompletionProposal[choices.length];
219: for (int i = 0; i < ret.length; i++) {
220: final Object choice = choices[i];
221: ret[i] = new Proposal(variable.toString(choice),
222: offset, length, offset + length) {
223: public void apply(IDocument document) {
224: super .apply(document);
225: Object oldChoice = variable.getCurrentChoice();
226: variable.setCurrentChoice(choice);
227: updateSlaves(variable, document, oldChoice);
228: }
229: };
230: }
231:
232: return ret;
233:
234: } else {
235: if (choices.length < 2)
236: return null;
237:
238: ICompletionProposal[] ret = new ICompletionProposal[choices.length];
239: for (int i = 0; i < ret.length; i++)
240: ret[i] = new Proposal(variable.toString(choices[i]),
241: offset, length, offset + length);
242:
243: return ret;
244: }
245: }
246:
247: private void updateSlaves(MultiVariable variable,
248: IDocument document, Object oldChoice) {
249: Object choice = variable.getCurrentChoice();
250: if (!oldChoice.equals(choice)) {
251: Set slaves = (Set) fDependencies.get(variable);
252: for (Iterator it = slaves.iterator(); it.hasNext();) {
253: MultiVariable slave = (MultiVariable) it.next();
254: VariablePosition pos = (VariablePosition) fPositions
255: .get(slave);
256:
257: Object slavesOldChoice = slave.getCurrentChoice();
258: slave.setKey(choice); // resets the current choice
259: try {
260: document.replace(pos.getOffset(), pos.getLength(),
261: slave.getDefaultValue());
262: } catch (BadLocationException x) {
263: // ignore and continue
264: }
265: // handle slaves recursively
266: if (fDependencies.containsKey(slave))
267: updateSlaves(slave, document, slavesOldChoice);
268: }
269: }
270: }
271:
272: /**
273: * @param position
274: */
275: public void addSlave(VariablePosition position) {
276: fPositions.put(position.getVariable(), position);
277: }
278:
279: /**
280: * @param master
281: * @param slave
282: * @since 3.3
283: */
284: public void addDependency(MultiVariable master, MultiVariable slave) {
285: // check for cycles and multi-slaves
286: if (fBackwardDeps.containsKey(slave))
287: throw new IllegalArgumentException(
288: "slave can only serve one master"); //$NON-NLS-1$
289: Object parent = master;
290: while (parent != null) {
291: parent = fBackwardDeps.get(parent);
292: if (parent == slave)
293: throw new IllegalArgumentException("cycle detected"); //$NON-NLS-1$
294: }
295:
296: Set slaves = (Set) fDependencies.get(master);
297: if (slaves == null) {
298: slaves = new HashSet();
299: fDependencies.put(master, slaves);
300: }
301: fBackwardDeps.put(slave, master);
302: slaves.add(slave);
303: }
304: }
|