001: /*******************************************************************************
002: * Copyright (c) 2000, 2007 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 org.eclipse.swt.SWTError;
013: import org.eclipse.swt.custom.StyledText;
014: import org.eclipse.swt.dnd.Clipboard;
015: import org.eclipse.swt.dnd.DND;
016: import org.eclipse.swt.dnd.TextTransfer;
017: import org.eclipse.swt.dnd.Transfer;
018: import org.eclipse.swt.events.FocusEvent;
019: import org.eclipse.swt.events.FocusListener;
020: import org.eclipse.swt.events.ModifyEvent;
021: import org.eclipse.swt.events.ModifyListener;
022: import org.eclipse.swt.events.MouseEvent;
023: import org.eclipse.swt.events.MouseListener;
024:
025: import org.eclipse.core.runtime.Assert;
026: import org.eclipse.core.runtime.IStatus;
027: import org.eclipse.core.runtime.Status;
028:
029: import org.eclipse.jface.viewers.ISelectionChangedListener;
030: import org.eclipse.jface.viewers.SelectionChangedEvent;
031:
032: import org.eclipse.jface.text.BadLocationException;
033: import org.eclipse.jface.text.IDocument;
034: import org.eclipse.jface.text.IRegion;
035: import org.eclipse.jface.text.ITextListener;
036: import org.eclipse.jface.text.ITextViewer;
037: import org.eclipse.jface.text.ITextViewerExtension5;
038: import org.eclipse.jface.text.Region;
039: import org.eclipse.jface.text.TextEvent;
040:
041: import org.eclipse.ui.internal.texteditor.TextEditorPlugin;
042:
043: /**
044: * A delete line target.
045: * @since 2.1
046: */
047: class DeleteLineTarget {
048:
049: /**
050: * A clipboard which concatenates subsequent delete line actions.
051: */
052: private static class DeleteLineClipboard implements MouseListener,
053: ModifyListener, ISelectionChangedListener, ITextListener,
054: FocusListener {
055:
056: /** The text viewer. */
057: private final ITextViewer fViewer;
058: /*
059: * This is a hack to stop a string of deletions when the user moves
060: * the caret. This kludge is necessary since:
061: * 1) Moving the caret does not fire a selection event
062: * 2) There is no support in StyledText for a CaretListener
063: * 3) The AcceleratorScope and KeybindingService classes are internal
064: *
065: * This kludge works by comparing the offset of the caret to the offset
066: * recorded the last time the action was run. If they differ, we do not
067: * continue the session.
068: *
069: * @see #saveState
070: * @see #checkState
071: */
072: /** The last known offset of the caret */
073: private int fIndex = -1;
074: /** The clip board. */
075: private Clipboard fClipboard;
076: /** A string buffer. */
077: private final StringBuffer fBuffer = new StringBuffer();
078: /** The delete flag indicates if a deletion is in progress. */
079: private boolean fDeleting;
080:
081: /**
082: * Creates the clipboard.
083: *
084: * @param viewer the text viewer
085: */
086: public DeleteLineClipboard(ITextViewer viewer) {
087: Assert.isNotNull(viewer);
088: fViewer = viewer;
089: }
090:
091: /**
092: * Returns the text viewer.
093: *
094: * @return the text viewer
095: */
096: public ITextViewer getViewer() {
097: return fViewer;
098: }
099:
100: /**
101: * Saves the current state, to be compared later using
102: * <code>checkState</code>.
103: */
104: private void saveState() {
105: fIndex = fViewer.getTextWidget().getCaretOffset();
106: }
107:
108: /**
109: * Checks that the state has not changed since it was saved.
110: *
111: * @return returns <code>true</code> if the current state is the same as
112: * when it was last saved.
113: */
114: private boolean hasSameState() {
115: return fIndex == fViewer.getTextWidget().getCaretOffset();
116: }
117:
118: /**
119: * Checks the state of the clipboard.
120: */
121: public void checkState() {
122:
123: if (fClipboard == null) {
124: StyledText text = fViewer.getTextWidget();
125: if (text == null)
126: return;
127:
128: fViewer.getSelectionProvider()
129: .addSelectionChangedListener(this );
130: text.addFocusListener(this );
131: text.addMouseListener(this );
132: text.addModifyListener(this );
133:
134: fClipboard = new Clipboard(text.getDisplay());
135: fBuffer.setLength(0);
136:
137: } else if (!hasSameState()) {
138: fBuffer.setLength(0);
139: }
140: }
141:
142: /**
143: * Appends the given string to this clipboard.
144: *
145: * @param deltaString the string to append
146: */
147: public void append(String deltaString) {
148: fBuffer.append(deltaString);
149: String string = fBuffer.toString();
150: Transfer[] dataTypes = new Transfer[] { TextTransfer
151: .getInstance() };
152: Object[] data = new Object[] { string };
153: fClipboard.setContents(data, dataTypes);
154: }
155:
156: /**
157: * Uninstalls this action.
158: */
159: private void uninstall() {
160:
161: if (fClipboard == null)
162: return;
163:
164: StyledText text = fViewer.getTextWidget();
165: if (text == null)
166: return;
167:
168: fViewer.getSelectionProvider()
169: .removeSelectionChangedListener(this );
170: text.removeFocusListener(this );
171: text.removeMouseListener(this );
172: text.removeModifyListener(this );
173:
174: fClipboard.dispose();
175: fClipboard = null;
176: }
177:
178: /**
179: * Mark whether a deletion is in progress.
180: *
181: * @param deleting <code>true</code> if a deletion is in progress
182: */
183: public void setDeleting(boolean deleting) {
184: fDeleting = deleting;
185: }
186:
187: /*
188: * @see org.eclipse.swt.events.MouseListener#mouseDoubleClick(MouseEvent)
189: */
190: public void mouseDoubleClick(MouseEvent e) {
191: uninstall();
192: }
193:
194: /*
195: * @see org.eclipse.swt.events.MouseListener#mouseDown(MouseEvent)
196: */
197: public void mouseDown(MouseEvent e) {
198: uninstall();
199: }
200:
201: /*
202: * @see org.eclipse.swt.events.MouseListener#mouseUp(MouseEvent)
203: */
204: public void mouseUp(MouseEvent e) {
205: uninstall();
206: }
207:
208: /*
209: * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(SelectionChangedEvent)
210: */
211: public void selectionChanged(SelectionChangedEvent event) {
212: uninstall();
213: }
214:
215: /*
216: * @see org.eclipse.swt.events.FocusListener#focusGained(FocusEvent)
217: */
218: public void focusGained(FocusEvent e) {
219: uninstall();
220: }
221:
222: /*
223: * @see org.eclipse.swt.events.FocusListener#focusLost(FocusEvent)
224: */
225: public void focusLost(FocusEvent e) {
226: uninstall();
227: }
228:
229: /*
230: * @see org.eclipse.jface.text.ITextListener#textChanged(TextEvent)
231: */
232: public void textChanged(TextEvent event) {
233: uninstall();
234: }
235:
236: /*
237: * @see org.eclipse.swt.events.ModifyListener#modifyText(ModifyEvent)
238: */
239: public void modifyText(ModifyEvent e) {
240: if (!fDeleting)
241: uninstall();
242: }
243: }
244:
245: /**
246: * The clipboard manager.
247: */
248: private final DeleteLineClipboard fClipboard;
249:
250: /**
251: * Creates a new target.
252: *
253: * @param viewer the viewer that the new target operates on
254: */
255: public DeleteLineTarget(ITextViewer viewer) {
256: fClipboard = new DeleteLineClipboard(viewer);
257: }
258:
259: /**
260: * Returns the document's delete region specified by position and type.
261: *
262: * @param document the document
263: * @param offset the offset
264: * @param length the length
265: * @param type the line deletion type, must be one of
266: * <code>WHOLE_LINE</code>, <code>TO_BEGINNING</code> or <code>TO_END</code>
267: * @return the document's delete region
268: * @throws BadLocationException
269: */
270: private IRegion getDeleteRegion(IDocument document, int offset,
271: int length, int type) throws BadLocationException {
272:
273: int line = document.getLineOfOffset(offset);
274: int resultOffset = 0;
275: int resultLength = 0;
276:
277: switch (type) {
278: case DeleteLineAction.WHOLE:
279: resultOffset = document.getLineOffset(line);
280: int endOffset = offset + length;
281: IRegion endLineInfo = document
282: .getLineInformationOfOffset(endOffset);
283: int endLine = document.getLineOfOffset(endLineInfo
284: .getOffset());
285: if (endLineInfo.getOffset() == endOffset && endLine > 0
286: && length > 0)
287: endLine = endLine - 1;
288: resultLength = document.getLineOffset(endLine)
289: + document.getLineLength(endLine) - resultOffset;
290: break;
291:
292: case DeleteLineAction.TO_BEGINNING:
293: resultOffset = document.getLineOffset(line);
294: resultLength = offset - resultOffset;
295: break;
296:
297: case DeleteLineAction.TO_END:
298: resultOffset = offset;
299:
300: IRegion lineRegion = document.getLineInformation(line);
301: int end = lineRegion.getOffset() + lineRegion.getLength();
302:
303: if (offset == end) {
304: String lineDelimiter = document.getLineDelimiter(line);
305: resultLength = lineDelimiter == null ? 0
306: : lineDelimiter.length();
307:
308: } else {
309: resultLength = end - resultOffset;
310: }
311: break;
312:
313: default:
314: throw new IllegalArgumentException();
315: }
316:
317: return clipToVisibleRegion(resultOffset, resultOffset
318: + resultLength);
319: }
320:
321: /**
322: * Clips the given start and end offset to the visible viewer region.
323: *
324: * @param startOffset the start offset
325: * @param endOffset the end offset
326: * @return the clipped region
327: * @since 3.3.2
328: */
329: private IRegion clipToVisibleRegion(int startOffset, int endOffset) {
330: ITextViewer viewer = fClipboard.getViewer();
331: IRegion visibleRegion;
332: if (viewer instanceof ITextViewerExtension5)
333: visibleRegion = ((ITextViewerExtension5) viewer)
334: .getModelCoverage();
335: else
336: visibleRegion = viewer.getVisibleRegion();
337:
338: int visibleStart = visibleRegion.getOffset();
339: int visibleLength = visibleRegion.getLength();
340:
341: startOffset = Math.max(startOffset, visibleStart);
342: endOffset = Math.min(endOffset, visibleStart + visibleLength);
343: return new Region(startOffset, endOffset - startOffset);
344: }
345:
346: /**
347: * Deletes the specified fraction of the line of the given offset.
348: *
349: * @param document the document
350: * @param offset the offset
351: * @param length the length
352: * @param type the line deletion type, must be one of
353: * <code>WHOLE_LINE</code>, <code>TO_BEGINNING</code> or <code>TO_END</code>
354: * @param copyToClipboard <code>true</code> if the deleted line should be copied to the clipboard
355: * @throws BadLocationException if position is not valid in the given document
356: */
357: public void deleteLine(IDocument document, int offset, int length,
358: int type, boolean copyToClipboard)
359: throws BadLocationException {
360:
361: IRegion deleteRegion = getDeleteRegion(document, offset,
362: length, type);
363: int deleteOffset = deleteRegion.getOffset();
364: int deleteLength = deleteRegion.getLength();
365:
366: if (deleteLength == 0)
367: return;
368:
369: if (copyToClipboard) {
370:
371: fClipboard.checkState();
372: try {
373: fClipboard.append(document.get(deleteOffset,
374: deleteLength));
375: } catch (SWTError e) {
376: if (e.code != DND.ERROR_CANNOT_SET_CLIPBOARD)
377: throw e;
378: // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=59459
379: // don't delete if copy to clipboard fails, rather log & abort
380:
381: // log
382: Status status = new Status(
383: IStatus.ERROR,
384: TextEditorPlugin.PLUGIN_ID,
385: e.code,
386: EditorMessages.Editor_error_clipboard_copy_failed_message,
387: e);
388: TextEditorPlugin.getDefault().getLog().log(status);
389:
390: fClipboard.uninstall();
391: return; // don't delete
392: }
393:
394: fClipboard.setDeleting(true);
395: document.replace(deleteOffset, deleteLength, null);
396: fClipboard.setDeleting(false);
397:
398: fClipboard.saveState();
399:
400: } else {
401: document.replace(deleteOffset, deleteLength, ""); //$NON-NLS-1$
402: }
403: }
404: }
|