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.ui.texteditor;
011:
012: import java.util.HashMap;
013: import java.util.Map;
014: import java.util.ResourceBundle;
015:
016: import org.osgi.framework.Bundle;
017:
018: import org.eclipse.swt.widgets.Shell;
019:
020: import org.eclipse.core.commands.ExecutionException;
021: import org.eclipse.core.commands.operations.IOperationHistory;
022: import org.eclipse.core.commands.operations.IUndoableOperation;
023:
024: import org.eclipse.core.runtime.IAdaptable;
025: import org.eclipse.core.runtime.ILog;
026: import org.eclipse.core.runtime.IStatus;
027: import org.eclipse.core.runtime.Platform;
028: import org.eclipse.core.runtime.Status;
029:
030: import org.eclipse.core.resources.IResource;
031:
032: import org.eclipse.jface.dialogs.IInputValidator;
033: import org.eclipse.jface.dialogs.InputDialog;
034: import org.eclipse.jface.window.Window;
035:
036: import org.eclipse.jface.text.BadLocationException;
037: import org.eclipse.jface.text.IDocument;
038: import org.eclipse.jface.text.ITextSelection;
039:
040: import org.eclipse.ui.IEditorInput;
041: import org.eclipse.ui.PlatformUI;
042: import org.eclipse.ui.ide.undo.CreateMarkersOperation;
043:
044: /**
045: * Action for creating a marker of a specified type for the editor's
046: * input element based on the editor's selection. If required, the
047: * action asks the user to provide a marker label. The action is initially
048: * associated with a text editor via the constructor, but that can be
049: * subsequently changed using <code>setEditor</code>.
050: * <p>
051: * The following keys, prepended by the given option prefix,
052: * are used for retrieving resources from the given bundle:
053: * <ul>
054: * <li><code>"dialog.title"</code> - the input dialog's title</li>
055: * <li><code>"dialog.message"</code> - the input dialog's message</li>
056: * <li><code>"error.dialog.title"</code> - the error dialog's title</li>
057: * <li><code>"error.dialog.message"</code> - the error dialog's message</li>
058: * </ul>
059: * This class may be instantiated but is not intended to be subclassed.
060: * </p>
061: */
062: public class AddMarkerAction extends TextEditorAction {
063:
064: /** The maximum length of an proposed label. */
065: private static final int MAX_LABEL_LENGTH = 80;
066: /** The type for newly created markers. */
067: private String fMarkerType;
068: /** Should the user be asked for a label? */
069: private boolean fAskForLabel;
070: /** The action's resource bundle. */
071: private ResourceBundle fBundle;
072: /** The prefix used for resource bundle lookup. */
073: private String fPrefix;
074:
075: /**
076: * Creates a new action for the given text editor. The action configures its
077: * visual representation from the given resource bundle.
078: *
079: * @param bundle the resource bundle
080: * @param prefix a prefix to be prepended to the various resource keys
081: * (described in <code>ResourceAction</code> constructor), or
082: * <code>null</code> if none
083: * @param textEditor the text editor
084: * @param markerType the type of marker to add
085: * @param askForLabel <code>true</code> if the user should be asked for
086: * a label for the new marker
087: * @see TextEditorAction#TextEditorAction(ResourceBundle, String, ITextEditor)
088: */
089: public AddMarkerAction(ResourceBundle bundle, String prefix,
090: ITextEditor textEditor, String markerType,
091: boolean askForLabel) {
092: super (bundle, prefix, textEditor);
093: fBundle = bundle;
094: fPrefix = prefix;
095: fMarkerType = markerType;
096: fAskForLabel = askForLabel;
097: }
098:
099: /**
100: * Returns this action's resource bundle.
101: *
102: * @return this action's resource bundle
103: */
104: protected ResourceBundle getResourceBundle() {
105: return fBundle;
106: }
107:
108: /**
109: * Returns this action's resource key prefix.
110: *
111: * @return this action's resource key prefix
112: */
113: protected String getResourceKeyPrefix() {
114: return fPrefix;
115: }
116:
117: /*
118: * @see IAction#run()
119: */
120: public void run() {
121: IResource resource = getResource();
122: if (resource == null)
123: return;
124: Map attributes = getInitialAttributes();
125: if (fAskForLabel) {
126: if (!askForLabel(attributes))
127: return;
128: }
129:
130: String name = getToolTipText();
131: name = name == null ? TextEditorMessages.AddMarkerAction_addMarker
132: : name;
133:
134: final Shell shell = getTextEditor().getSite().getShell();
135: IAdaptable context = new IAdaptable() {
136: public Object getAdapter(Class adapter) {
137: if (adapter == Shell.class)
138: return shell;
139: return null;
140: }
141: };
142:
143: IUndoableOperation operation = new CreateMarkersOperation(
144: fMarkerType, attributes, resource, name);
145: IOperationHistory operationHistory = PlatformUI.getWorkbench()
146: .getOperationSupport().getOperationHistory();
147: try {
148: operationHistory.execute(operation, null, context);
149: } catch (ExecutionException x) {
150: Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
151: ILog log = Platform.getLog(bundle);
152: String msg = getString(
153: fBundle,
154: fPrefix + "error.dialog.message", fPrefix + "error.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
155: log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
156: IStatus.OK, msg, x));
157: }
158: }
159:
160: /*
161: * @see TextEditorAction#update()
162: */
163: public void update() {
164: setEnabled(getResource() != null);
165: }
166:
167: /**
168: * Asks the user for a marker label. Returns <code>true</code> if a label
169: * is entered, <code>false</code> if the user cancels the input dialog.
170: * The value for the attribute <code>message</code> is modified in the given
171: * attribute map.
172: *
173: * @param attributes the attributes map
174: * @return <code>true</code> if a label has been entered
175: */
176: protected boolean askForLabel(Map attributes) {
177:
178: Object o = attributes.get("message"); //$NON-NLS-1$
179: String proposal = (o instanceof String) ? (String) o : ""; //$NON-NLS-1$
180: if (proposal == null)
181: proposal = ""; //$NON-NLS-1$
182:
183: String title = getString(fBundle,
184: fPrefix + "dialog.title", fPrefix + "dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$
185: String message = getString(fBundle,
186: fPrefix + "dialog.message", fPrefix + "dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
187: IInputValidator inputValidator = new IInputValidator() {
188: public String isValid(String newText) {
189: return (newText == null || newText.trim().length() == 0) ? " " : null; //$NON-NLS-1$
190: }
191: };
192: InputDialog dialog = new InputDialog(getTextEditor().getSite()
193: .getShell(), title, message, proposal, inputValidator);
194:
195: String label = null;
196: if (dialog.open() != Window.CANCEL)
197: label = dialog.getValue();
198:
199: if (label == null)
200: return false;
201:
202: label = label.trim();
203: if (label.length() == 0)
204: return false;
205:
206: attributes.put("message", label); //$NON-NLS-1$
207: return true;
208: }
209:
210: /**
211: * Returns the attributes the new marker will be initialized with.
212: * <p>
213: * Subclasses may extend or replace this method.</p>
214: *
215: * @return the attributes the new marker will be initialized with
216: */
217: protected Map getInitialAttributes() {
218:
219: Map attributes = new HashMap(11);
220:
221: ITextSelection selection = (ITextSelection) getTextEditor()
222: .getSelectionProvider().getSelection();
223: if (!selection.isEmpty()) {
224:
225: int start = selection.getOffset();
226: int length = selection.getLength();
227:
228: if (length < 0) {
229: length = -length;
230: start -= length;
231: }
232:
233: MarkerUtilities.setCharStart(attributes, start);
234: MarkerUtilities.setCharEnd(attributes, start + length);
235:
236: // marker line numbers are 1-based
237: int line = selection.getStartLine();
238: MarkerUtilities.setLineNumber(attributes, line == -1 ? -1
239: : line + 1);
240:
241: IDocument document = getTextEditor().getDocumentProvider()
242: .getDocument(getTextEditor().getEditorInput());
243: MarkerUtilities.setMessage(attributes, getLabelProposal(
244: document, start, length));
245:
246: }
247:
248: return attributes;
249: }
250:
251: /**
252: * Returns the initial label for the marker.
253: *
254: * @param document the document from which to extract a label proposal
255: * @param offset the document offset of the range from which to extract the label proposal
256: * @param length the length of the range from which to extract the label proposal
257: * @return the label proposal
258: */
259: protected String getLabelProposal(IDocument document, int offset,
260: int length) {
261:
262: try {
263:
264: if (length > 0) {
265:
266: // find first white char but skip leading white chars
267: int i = 0;
268: boolean skip = true;
269: while (i < length) {
270: boolean isWhitespace = Character
271: .isWhitespace(document.getChar(offset + i));
272: if (!skip && isWhitespace)
273: break;
274: if (skip && !isWhitespace)
275: skip = false;
276: i++;
277: }
278:
279: String label = document.get(offset, i);
280: return label.trim();
281: }
282:
283: char ch;
284:
285: // Get the first white char before the selection.
286: int left = offset;
287:
288: int line = document.getLineOfOffset(offset);
289: int limit = document.getLineOffset(line);
290:
291: while (left > limit) {
292: ch = document.getChar(left);
293: if (Character.isWhitespace(ch))
294: break;
295: --left;
296: }
297:
298: limit += document.getLineLength(line);
299:
300: // Now get the first letter.
301: while (left <= limit) {
302: ch = document.getChar(left);
303: if (!Character.isWhitespace(ch))
304: break;
305: ++left;
306: }
307:
308: if (left > limit)
309: return null;
310:
311: limit = Math.min(limit, left + MAX_LABEL_LENGTH);
312:
313: // Get the next white char.
314: int right = (offset + length > limit ? limit : offset
315: + length);
316: while (right < limit) {
317: ch = document.getChar(right);
318: if (Character.isWhitespace(ch))
319: break;
320: ++right;
321: }
322:
323: // Trim the string and return it.
324: if (left != right) {
325: String label = document.get(left, right - left);
326: return label.trim();
327: }
328:
329: } catch (BadLocationException x) {
330: // don't proposal label then
331: }
332:
333: return null;
334: }
335:
336: /**
337: * Returns the resource on which to create the marker,
338: * or <code>null</code> if there is no applicable resource. This
339: * queries the editor's input using <code>getAdapter(IResource.class)</code>.
340: * Subclasses may override this method.
341: *
342: * @return the resource to which to attach the newly created marker
343: */
344: protected IResource getResource() {
345: ITextEditor editor = getTextEditor();
346: if (editor != null) {
347: IEditorInput input = editor.getEditorInput();
348: return (IResource) ((IAdaptable) input)
349: .getAdapter(IResource.class);
350: }
351: return null;
352: }
353: }
|