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: * Sebastian Davids <sdavids@gmx.de> bug 38745
011: *******************************************************************************/package org.eclipse.ui.texteditor;
012:
013: import java.util.ArrayList;
014: import java.util.HashMap;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.ResourceBundle;
019:
020: import org.osgi.framework.Bundle;
021:
022: import org.eclipse.swt.widgets.Shell;
023:
024: import org.eclipse.core.commands.ExecutionException;
025: import org.eclipse.core.commands.operations.IOperationHistory;
026: import org.eclipse.core.commands.operations.IUndoableOperation;
027:
028: import org.eclipse.core.runtime.Assert;
029: import org.eclipse.core.runtime.CoreException;
030: import org.eclipse.core.runtime.IAdaptable;
031: import org.eclipse.core.runtime.ILog;
032: import org.eclipse.core.runtime.IStatus;
033: import org.eclipse.core.runtime.Platform;
034: import org.eclipse.core.runtime.Status;
035:
036: import org.eclipse.core.resources.IFile;
037: import org.eclipse.core.resources.IMarker;
038: import org.eclipse.core.resources.IResource;
039:
040: import org.eclipse.jface.dialogs.ErrorDialog;
041: import org.eclipse.jface.dialogs.IInputValidator;
042: import org.eclipse.jface.dialogs.InputDialog;
043: import org.eclipse.jface.window.Window;
044:
045: import org.eclipse.jface.text.BadLocationException;
046: import org.eclipse.jface.text.IDocument;
047: import org.eclipse.jface.text.IRegion;
048: import org.eclipse.jface.text.Position;
049: import org.eclipse.jface.text.source.IAnnotationModel;
050: import org.eclipse.jface.text.source.IVerticalRuler;
051: import org.eclipse.jface.text.source.IVerticalRulerInfo;
052:
053: import org.eclipse.ui.IEditorInput;
054: import org.eclipse.ui.PlatformUI;
055: import org.eclipse.ui.ide.undo.CreateMarkersOperation;
056: import org.eclipse.ui.ide.undo.DeleteMarkersOperation;
057:
058: /**
059: * A ruler action which can add and remove markers which have a visual
060: * representation in the ruler.
061: * <p>
062: * This class may be instantiated but is not intended for sub-classing.
063: * </p>
064: */
065: public class MarkerRulerAction extends ResourceAction implements
066: IUpdate {
067:
068: /** The maximum length of an proposed label. */
069: private static final int MAX_LABEL_LENGTH = 80;
070:
071: /** The vertical ruler info of the editor. */
072: private IVerticalRulerInfo fRuler;
073: /** The associated editor */
074: private ITextEditor fTextEditor;
075: /** The of the marker to be created/removed. */
076: private String fMarkerType;
077: /** The cached list of markers covering a particular vertical ruler position. */
078: private List fMarkers;
079: /** The flag indicating whether user interaction is required. */
080: private boolean fAskForLabel;
081: /** The action's resource bundle. */
082: private ResourceBundle fBundle;
083: /** The prefix used for resource bundle look ups. */
084: private String fPrefix;
085: /** The cached action label when adding a marker. */
086: private String fAddLabel;
087: /** The cached action label when removing a marker. */
088: private String fRemoveLabel;
089:
090: /**
091: * Creates a new action for the given ruler and editor. The action configures
092: * its visual representation from the given resource bundle.
093: *
094: * @param bundle the resource bundle
095: * @param prefix a prefix to be prepended to the various resource keys
096: * (described in {@link org.eclipse.ui.texteditor.ResourceAction} constructor), or <code>null</code> if none
097: * @param editor the editor
098: * @param ruler the ruler
099: * @param markerType the type of marker
100: * @param askForLabel <code>true</code> if the user should be asked for a label when a new marker is created
101: * @see ResourceAction#ResourceAction(ResourceBundle, String)
102: * @since 2.0
103: */
104: public MarkerRulerAction(ResourceBundle bundle, String prefix,
105: ITextEditor editor, IVerticalRulerInfo ruler,
106: String markerType, boolean askForLabel) {
107: super (bundle, prefix);
108: Assert.isLegal(editor != null);
109:
110: fRuler = ruler;
111: fTextEditor = editor;
112: fMarkerType = markerType;
113: fAskForLabel = askForLabel;
114:
115: fBundle = bundle;
116: fPrefix = prefix;
117:
118: fAddLabel = getString(bundle,
119: prefix + "add.label", prefix + "add.label"); //$NON-NLS-2$ //$NON-NLS-1$
120: fRemoveLabel = getString(bundle,
121: prefix + "remove.label", prefix + "remove.label"); //$NON-NLS-2$ //$NON-NLS-1$
122: }
123:
124: /**
125: * Creates a new action for the given ruler and editor. The action configures
126: * its visual representation from the given resource bundle.
127: *
128: * @param bundle the resource bundle
129: * @param prefix a prefix to be prepended to the various resource keys
130: * @param ruler the ruler
131: * @param editor the editor
132: * @param markerType the type of the marker
133: * @param askForLabel <code>true</code> if the user should be asked for a label
134: * @deprecated use <code>MarkerRulerAction(ResourceBundle, String, ITextEditor, IVerticalRulerInfo, String, boolean)</code> instead
135: */
136: public MarkerRulerAction(ResourceBundle bundle, String prefix,
137: IVerticalRuler ruler, ITextEditor editor,
138: String markerType, boolean askForLabel) {
139: this (bundle, prefix, editor, ruler, markerType, askForLabel);
140: }
141:
142: /**
143: * Returns this action's text editor.
144: *
145: * @return this action's text editor
146: */
147: protected ITextEditor getTextEditor() {
148: return fTextEditor;
149: }
150:
151: /**
152: * Returns this action's vertical ruler.
153: *
154: * @return this action's vertical ruler
155: * @deprecated use <code>getVerticalRulerInfo</code> instead
156: */
157: protected IVerticalRuler getVerticalRuler() {
158: if (fRuler instanceof IVerticalRuler)
159: return (IVerticalRuler) fRuler;
160: return null;
161: }
162:
163: /**
164: * Returns this action's vertical ruler info.
165: *
166: * @return this action's vertical ruler info
167: * @since 2.0
168: */
169: protected IVerticalRulerInfo getVerticalRulerInfo() {
170: return fRuler;
171: }
172:
173: /**
174: * Returns this action's resource bundle.
175: *
176: * @return this action's resource bundle
177: */
178: protected ResourceBundle getResourceBundle() {
179: return fBundle;
180: }
181:
182: /**
183: * Returns this action's resource key prefix.
184: *
185: * @return this action's resource key prefix
186: */
187: protected String getResourceKeyPrefix() {
188: return fPrefix;
189: }
190:
191: /*
192: * @see IUpdate#update()
193: */
194: public void update() {
195: //bug 38745
196: int line = getVerticalRuler()
197: .getLineOfLastMouseButtonActivity() + 1;
198: IDocument document = getDocument();
199: if (document != null) {
200: if (line > getDocument().getNumberOfLines()) {
201: setEnabled(false);
202: setText(fAddLabel);
203: } else {
204: fMarkers = getMarkers();
205: setEnabled(getResource() != null
206: && (fMarkers.isEmpty() || markersUserEditable(fMarkers)));
207: setText(fMarkers.isEmpty() ? fAddLabel : fRemoveLabel);
208: }
209: }
210: }
211:
212: /**
213: * Returns whether the given markers are all editable by the user.
214: *
215: * @param markers the list of markers to test
216: * @return boolean <code>true</code> if they are all editable
217: * @since 3.2
218: */
219: private boolean markersUserEditable(List markers) {
220: Iterator iter = markers.iterator();
221: while (iter.hasNext()) {
222: if (!isUserEditable((IMarker) iter.next()))
223: return false;
224: }
225: return true;
226: }
227:
228: /**
229: * Returns whether the given marker is editable by the user.
230: *
231: * @param marker the marker to test
232: * @return boolean <code>true</code> if it is editable
233: * @since 3.2
234: */
235: private boolean isUserEditable(IMarker marker) {
236: return marker != null && marker.exists()
237: && marker.getAttribute(IMarker.USER_EDITABLE, true);
238: }
239:
240: /*
241: * @see Action#run()
242: */
243: public void run() {
244: if (fMarkers.isEmpty())
245: addMarker();
246: else
247: removeMarkers(fMarkers);
248: }
249:
250: /**
251: * Returns the resource for which to create the marker,
252: * or <code>null</code> if there is no applicable resource.
253: *
254: * @return the resource for which to create the marker or <code>null</code>
255: */
256: protected IResource getResource() {
257: IEditorInput input = fTextEditor.getEditorInput();
258:
259: IResource resource = (IResource) input.getAdapter(IFile.class);
260:
261: if (resource == null)
262: resource = (IResource) input.getAdapter(IResource.class);
263:
264: return resource;
265: }
266:
267: /**
268: * Returns the <code>AbstractMarkerAnnotationModel</code> of the editor's input.
269: *
270: * @return the marker annotation model
271: */
272: protected AbstractMarkerAnnotationModel getAnnotationModel() {
273: IDocumentProvider provider = fTextEditor.getDocumentProvider();
274: IAnnotationModel model = provider
275: .getAnnotationModel(fTextEditor.getEditorInput());
276: if (model instanceof AbstractMarkerAnnotationModel)
277: return (AbstractMarkerAnnotationModel) model;
278: return null;
279: }
280:
281: /**
282: * Returns the <code>IDocument</code> of the editor's input.
283: *
284: * @return the document of the editor's input
285: */
286: protected IDocument getDocument() {
287: IDocumentProvider provider = fTextEditor.getDocumentProvider();
288: return provider.getDocument(fTextEditor.getEditorInput());
289: }
290:
291: /**
292: * Checks whether a position includes the ruler's line of activity.
293: *
294: * @param position the position to be checked
295: * @param document the document the position refers to
296: * @return <code>true</code> if the line is included by the given position
297: */
298: protected boolean includesRulerLine(Position position,
299: IDocument document) {
300:
301: if (position != null) {
302: try {
303: int markerLine = document.getLineOfOffset(position
304: .getOffset());
305: int line = fRuler.getLineOfLastMouseButtonActivity();
306: if (line == markerLine)
307: return true;
308: // commented because of "1GEUOZ9: ITPJUI:ALL - Confusing UI for multi-line Bookmarks and Tasks"
309: // return (markerLine <= line && line <= document.getLineOfOffset(position.getOffset() + position.getLength()));
310: } catch (BadLocationException x) {
311: }
312: }
313:
314: return false;
315: }
316:
317: /**
318: * Handles core exceptions. This implementation logs the exceptions
319: * with the workbench plug-in and shows an error dialog.
320: *
321: * @param exception the exception to be handled
322: * @param message the message to be logged with the given exception
323: */
324: protected void handleCoreException(CoreException exception,
325: String message) {
326: Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
327: ILog log = Platform.getLog(bundle);
328:
329: if (message != null)
330: log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
331: IStatus.OK, message, exception));
332: else
333: log.log(exception.getStatus());
334:
335: Shell shell = getTextEditor().getSite().getShell();
336: String title = getString(fBundle, fPrefix
337: + "error.dialog.title", fPrefix + "error.dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$
338: String msg = getString(
339: fBundle,
340: fPrefix + "error.dialog.message", fPrefix + "error.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
341:
342: ErrorDialog.openError(shell, title, msg, exception.getStatus());
343: }
344:
345: /**
346: * Returns all markers which include the ruler's line of activity.
347: *
348: * @return all a list of markers which include the ruler's line of activity
349: */
350: protected List getMarkers() {
351:
352: List markers = new ArrayList();
353:
354: IResource resource = getResource();
355: IDocument document = getDocument();
356: AbstractMarkerAnnotationModel model = getAnnotationModel();
357:
358: if (resource != null && model != null && resource.exists()) {
359: try {
360: IMarker[] allMarkers = resource.findMarkers(
361: fMarkerType, true, IResource.DEPTH_ZERO);
362: if (allMarkers != null) {
363: for (int i = 0; i < allMarkers.length; i++) {
364: if (includesRulerLine(model
365: .getMarkerPosition(allMarkers[i]),
366: document)) {
367: markers.add(allMarkers[i]);
368: }
369: }
370: }
371: } catch (CoreException x) {
372: handleCoreException(x,
373: TextEditorMessages.MarkerRulerAction_getMarker);
374: }
375: }
376:
377: return markers;
378: }
379:
380: /**
381: * Creates a new marker according to the specification of this action and
382: * adds it to the marker resource.
383: */
384: protected void addMarker() {
385: IResource resource = getResource();
386: if (resource == null)
387: return;
388: Map attributes = getInitialAttributes();
389: if (fAskForLabel) {
390: if (!askForLabel(attributes))
391: return;
392: }
393: execute(new CreateMarkersOperation(fMarkerType, attributes,
394: resource, getOperationName()));
395: }
396:
397: /**
398: * Removes the given markers.
399: *
400: * @param markers the markers to be deleted
401: */
402: protected void removeMarkers(final List markers) {
403: IMarker[] markersArray = (IMarker[]) markers
404: .toArray(new IMarker[markers.size()]);
405: execute(new DeleteMarkersOperation(markersArray,
406: getOperationName()));
407: }
408:
409: /**
410: * Asks the user for a marker label. Returns <code>true</code> if a label
411: * is entered, <code>false</code> if the user cancels the input dialog.
412: * Sets the value of the attribute <code>message</code> in the given
413: * map of attributes.
414: *
415: * @param attributes the map of attributes
416: * @return <code>true</code> if the map of attributes has successfully been initialized
417: */
418: protected boolean askForLabel(Map attributes) {
419:
420: Object o = attributes.get("message"); //$NON-NLS-1$
421: String proposal = (o instanceof String) ? (String) o : ""; //$NON-NLS-1$
422: if (proposal == null)
423: proposal = ""; //$NON-NLS-1$
424:
425: String title = getString(
426: fBundle,
427: fPrefix + "add.dialog.title", fPrefix + "add.dialog.title"); //$NON-NLS-2$ //$NON-NLS-1$
428: String message = getString(fBundle, fPrefix
429: + "add.dialog.message", fPrefix + "add.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
430: IInputValidator inputValidator = new IInputValidator() {
431: public String isValid(String newText) {
432: return (newText == null || newText.trim().length() == 0) ? " " : null; //$NON-NLS-1$
433: }
434: };
435: InputDialog dialog = new InputDialog(fTextEditor.getSite()
436: .getShell(), title, message, proposal, inputValidator);
437:
438: String label = null;
439: if (dialog.open() != Window.CANCEL)
440: label = dialog.getValue();
441:
442: if (label == null)
443: return false;
444:
445: label = label.trim();
446: if (label.length() == 0)
447: return false;
448:
449: MarkerUtilities.setMessage(attributes, label);
450: return true;
451: }
452:
453: /**
454: * Returns the attributes with which a newly created marker will be
455: * initialized.
456: *
457: * @return the initial marker attributes
458: */
459: protected Map getInitialAttributes() {
460:
461: Map attributes = new HashMap(11);
462:
463: IDocumentProvider provider = fTextEditor.getDocumentProvider();
464: IDocument document = provider.getDocument(fTextEditor
465: .getEditorInput());
466: int line = fRuler.getLineOfLastMouseButtonActivity();
467: int start = -1;
468: int end = -1;
469: int length = 0;
470:
471: try {
472:
473: IRegion lineInformation = document.getLineInformation(line);
474: start = lineInformation.getOffset();
475: length = lineInformation.getLength();
476:
477: end = start + length;
478:
479: } catch (BadLocationException x) {
480: }
481:
482: // marker line numbers are 1-based
483: MarkerUtilities.setMessage(attributes, getLabelProposal(
484: document, start, length));
485: MarkerUtilities.setLineNumber(attributes, line + 1);
486: MarkerUtilities.setCharStart(attributes, start);
487: MarkerUtilities.setCharEnd(attributes, end);
488:
489: return attributes;
490: }
491:
492: /**
493: * Returns the initial label for the marker.
494: *
495: * @param document the document from which to extract a label proposal
496: * @param offset the document offset of the range from which to extract the label proposal
497: * @param length the length of the range from which to extract the label proposal
498: * @return the label proposal
499: * @since 3.0
500: */
501: protected String getLabelProposal(IDocument document, int offset,
502: int length) {
503: try {
504: String label = document.get(offset, length).trim();
505: if (label.length() <= MAX_LABEL_LENGTH)
506: return label;
507: return label.substring(0, MAX_LABEL_LENGTH);
508: } catch (BadLocationException x) {
509: // don't propose label then
510: return null;
511: }
512: }
513:
514: /**
515: * Returns the name to be used for the operation.
516: *
517: * @return the operation name
518: * @since 3.3
519: */
520: private String getOperationName() {
521: String name = getText();
522: return name == null ? TextEditorMessages.AddMarkerAction_addMarker
523: : name;
524: }
525:
526: /**
527: * Execute the specified undoable operation.
528: *
529: * @param operation the operation to execute
530: * @since 3.3
531: */
532: private void execute(IUndoableOperation operation) {
533: final Shell shell = getTextEditor().getSite().getShell();
534: IAdaptable context = new IAdaptable() {
535: public Object getAdapter(Class adapter) {
536: if (adapter == Shell.class)
537: return shell;
538: return null;
539: }
540: };
541:
542: IOperationHistory operationHistory = PlatformUI.getWorkbench()
543: .getOperationSupport().getOperationHistory();
544: try {
545: operationHistory.execute(operation, null, context);
546: } catch (ExecutionException e) {
547: Bundle bundle = Platform.getBundle(PlatformUI.PLUGIN_ID);
548: ILog log = Platform.getLog(bundle);
549: String msg = getString(
550: fBundle,
551: fPrefix + "error.dialog.message", fPrefix + "error.dialog.message"); //$NON-NLS-2$ //$NON-NLS-1$
552: log.log(new Status(IStatus.ERROR, PlatformUI.PLUGIN_ID,
553: IStatus.OK, msg, e));
554: }
555: }
556: }
|