001: /*******************************************************************************
002: * Copyright (c) 2003, 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.pde.internal.ui.editor.context;
011:
012: import java.lang.reflect.InvocationTargetException;
013: import java.util.ArrayList;
014:
015: import org.eclipse.core.filebuffers.IDocumentSetupParticipant;
016: import org.eclipse.core.resources.IFile;
017: import org.eclipse.core.resources.IResource;
018: import org.eclipse.core.resources.IWorkspace;
019: import org.eclipse.core.resources.ResourcesPlugin;
020: import org.eclipse.core.runtime.CoreException;
021: import org.eclipse.core.runtime.IPath;
022: import org.eclipse.core.runtime.IProgressMonitor;
023: import org.eclipse.core.runtime.IStatus;
024: import org.eclipse.jface.dialogs.ErrorDialog;
025: import org.eclipse.jface.dialogs.IMessageProvider;
026: import org.eclipse.jface.text.BadLocationException;
027: import org.eclipse.jface.text.IDocument;
028: import org.eclipse.jface.text.TextUtilities;
029: import org.eclipse.jface.text.source.IAnnotationModel;
030: import org.eclipse.jface.window.Window;
031: import org.eclipse.osgi.util.NLS;
032: import org.eclipse.pde.core.IBaseModel;
033: import org.eclipse.pde.core.IEditable;
034: import org.eclipse.pde.core.IModelChangeProvider;
035: import org.eclipse.pde.core.IModelChangedEvent;
036: import org.eclipse.pde.core.IModelChangedListener;
037: import org.eclipse.pde.internal.core.text.IEditingModel;
038: import org.eclipse.pde.internal.core.util.PropertiesUtil;
039: import org.eclipse.pde.internal.ui.PDEPlugin;
040: import org.eclipse.pde.internal.ui.PDEUIMessages;
041: import org.eclipse.pde.internal.ui.editor.PDEFormEditor;
042: import org.eclipse.pde.internal.ui.editor.PDEStorageDocumentProvider;
043: import org.eclipse.swt.widgets.Shell;
044: import org.eclipse.text.edits.InsertEdit;
045: import org.eclipse.text.edits.MalformedTreeException;
046: import org.eclipse.text.edits.MoveSourceEdit;
047: import org.eclipse.text.edits.MultiTextEdit;
048: import org.eclipse.text.edits.TextEdit;
049: import org.eclipse.ui.IEditorInput;
050: import org.eclipse.ui.IFileEditorInput;
051: import org.eclipse.ui.PlatformUI;
052: import org.eclipse.ui.actions.WorkspaceModifyOperation;
053: import org.eclipse.ui.dialogs.SaveAsDialog;
054: import org.eclipse.ui.editors.text.ForwardingDocumentProvider;
055: import org.eclipse.ui.part.FileEditorInput;
056: import org.eclipse.ui.texteditor.IDocumentProvider;
057: import org.eclipse.ui.texteditor.IElementStateListener;
058:
059: /**
060: * This class maintains objects associated with a single editor input.
061: */
062: public abstract class InputContext {
063:
064: private PDEFormEditor fEditor;
065: private IEditorInput fEditorInput;
066: private IBaseModel fModel;
067: private IModelChangedListener fModelListener;
068: private IDocumentProvider fDocumentProvider;
069: private IElementStateListener fElementListener;
070: protected ArrayList fEditOperations = new ArrayList();
071:
072: private boolean fValidated;
073: private boolean fPrimary;
074: private boolean fIsSourceMode;
075: private boolean fMustSynchronize;
076:
077: class ElementListener implements IElementStateListener {
078: public void elementContentAboutToBeReplaced(Object element) {
079: }
080:
081: public void elementContentReplaced(Object element) {
082: if (element != null && element.equals(fEditorInput))
083: doRevert();
084: }
085:
086: public void elementDeleted(Object element) {
087: if (element != null && element.equals(fEditorInput))
088: dispose();
089: }
090:
091: public void elementDirtyStateChanged(Object element,
092: boolean isDirty) {
093: if (element != null && element.equals(fEditorInput))
094: fMustSynchronize = true;
095: }
096:
097: public void elementMoved(Object originalElement,
098: Object movedElement) {
099: if (originalElement != null
100: && originalElement.equals(fEditorInput)) {
101: dispose();
102: fEditor.close(true);
103: }
104: }
105: }
106:
107: public InputContext(PDEFormEditor editor, IEditorInput input,
108: boolean primary) {
109: this .fEditor = editor;
110: this .fEditorInput = input;
111: setPrimary(primary);
112: }
113:
114: public abstract String getId();
115:
116: public IEditorInput getInput() {
117: return fEditorInput;
118: }
119:
120: public PDEFormEditor getEditor() {
121: return fEditor;
122: }
123:
124: public IBaseModel getModel() {
125: return fModel;
126: }
127:
128: public IDocumentProvider getDocumentProvider() {
129: return fDocumentProvider;
130: }
131:
132: private IDocumentProvider createDocumentProvider(IEditorInput input) {
133: if (input instanceof IFileEditorInput) {
134: return new ForwardingDocumentProvider(getPartitionName(),
135: getDocumentSetupParticipant(), PDEPlugin
136: .getDefault().getTextFileDocumentProvider());
137: }
138: return new PDEStorageDocumentProvider(
139: getDocumentSetupParticipant());
140: }
141:
142: protected IDocumentSetupParticipant getDocumentSetupParticipant() {
143: return new IDocumentSetupParticipant() {
144: public void setup(IDocument document) {
145: }
146: };
147: }
148:
149: protected abstract String getPartitionName();
150:
151: protected abstract String getDefaultCharset();
152:
153: protected abstract IBaseModel createModel(IEditorInput input)
154: throws CoreException;
155:
156: protected void create() {
157: fDocumentProvider = createDocumentProvider(fEditorInput);
158: try {
159: fDocumentProvider.connect(fEditorInput);
160: fModel = createModel(fEditorInput);
161: if (fModel instanceof IModelChangeProvider) {
162: fModelListener = new IModelChangedListener() {
163: public void modelChanged(IModelChangedEvent e) {
164: if (e.getChangeType() != IModelChangedEvent.WORLD_CHANGED) {
165: if (!fEditor.getLastDirtyState())
166: fEditor.fireSaveNeeded(fEditorInput,
167: true);
168: IModelChangeProvider provider = e
169: .getChangeProvider();
170: if (provider instanceof IEditingModel) {
171: // this is to guard against false notifications
172: // when a revert operation is performed, focus is taken away from a FormEntry
173: // and a text edit operation is falsely requested
174: if (((IEditingModel) provider)
175: .isDirty())
176: addTextEditOperation(
177: fEditOperations, e);
178: }
179: }
180: }
181: };
182: ((IModelChangeProvider) fModel)
183: .addModelChangedListener(fModelListener);
184: }
185:
186: IAnnotationModel amodel = fDocumentProvider
187: .getAnnotationModel(fEditorInput);
188: if (amodel != null)
189: amodel.connect(fDocumentProvider
190: .getDocument(fEditorInput));
191: fElementListener = new ElementListener();
192: fDocumentProvider.addElementStateListener(fElementListener);
193: } catch (CoreException e) {
194: PDEPlugin.logException(e);
195: }
196:
197: }
198:
199: public synchronized boolean validateEdit() {
200: if (!fValidated) {
201: if (fEditorInput instanceof IFileEditorInput) {
202: IFile file = ((IFileEditorInput) fEditorInput)
203: .getFile();
204: if (file.isReadOnly()) {
205: Shell shell = fEditor.getEditorSite().getShell();
206: IStatus validateStatus = PDEPlugin.getWorkspace()
207: .validateEdit(new IFile[] { file }, shell);
208: fValidated = true; // to prevent loops
209: if (validateStatus.getSeverity() != IStatus.OK)
210: ErrorDialog.openError(shell,
211: fEditor.getTitle(), null,
212: validateStatus);
213: return validateStatus.getSeverity() == IStatus.OK;
214: }
215: }
216: }
217: return true;
218: }
219:
220: public void doSave(IProgressMonitor monitor) {
221: try {
222: IDocument doc = fDocumentProvider.getDocument(fEditorInput);
223: fDocumentProvider.aboutToChange(fEditorInput);
224: flushModel(doc);
225: fDocumentProvider.saveDocument(monitor, fEditorInput, doc,
226: true);
227: fDocumentProvider.changed(fEditorInput);
228: fValidated = false;
229: } catch (CoreException e) {
230: PDEPlugin.logException(e);
231: }
232: }
233:
234: protected abstract void addTextEditOperation(ArrayList ops,
235: IModelChangedEvent event);
236:
237: public void flushEditorInput() {
238: if (fEditOperations.size() > 0) {
239: IDocument doc = fDocumentProvider.getDocument(fEditorInput);
240: fDocumentProvider.aboutToChange(fEditorInput);
241: flushModel(doc);
242: fDocumentProvider.changed(fEditorInput);
243: fValidated = false;
244: } else if ((fModel instanceof IEditable)
245: && ((IEditable) fModel).isDirty()) {
246: // When text edit operations are made that cancel each other out,
247: // the editor is not undirtied
248: // e.g. Extensions page: Move an element up and then move it down
249: // back in the same position: Bug # 197831
250: ((IEditable) fModel).setDirty(false);
251: }
252: }
253:
254: protected void flushModel(IDocument doc) {
255: boolean flushed = true;
256: if (fEditOperations.size() > 0) {
257: try {
258: MultiTextEdit edit = new MultiTextEdit();
259: if (isNewlineNeeded(doc))
260: insert(edit, new InsertEdit(doc.getLength(),
261: TextUtilities.getDefaultLineDelimiter(doc)));
262: for (int i = 0; i < fEditOperations.size(); i++) {
263: insert(edit, (TextEdit) fEditOperations.get(i));
264: }
265: if (fModel instanceof IEditingModel)
266: ((IEditingModel) fModel).setStale(true);
267: edit.apply(doc);
268: fEditOperations.clear();
269: } catch (MalformedTreeException e) {
270: PDEPlugin.logException(e);
271: flushed = false;
272: } catch (BadLocationException e) {
273: PDEPlugin.logException(e);
274: flushed = false;
275: }
276: }
277: // If no errors were encountered flushing the model, then undirty the
278: // model. This needs to be done regardless of whether there are any
279: // edit operations or not; since, the contributed actions need to be
280: // updated and the editor needs to be undirtied
281: if (flushed && (fModel instanceof IEditable)) {
282: ((IEditable) fModel).setDirty(false);
283: }
284: }
285:
286: protected boolean isNewlineNeeded(IDocument doc)
287: throws BadLocationException {
288: return PropertiesUtil.isNewlineNeeded(doc);
289: }
290:
291: protected static void insert(TextEdit parent, TextEdit edit) {
292: if (!parent.hasChildren()) {
293: parent.addChild(edit);
294: if (edit instanceof MoveSourceEdit) {
295: parent
296: .addChild(((MoveSourceEdit) edit)
297: .getTargetEdit());
298: }
299: return;
300: }
301: TextEdit[] children = parent.getChildren();
302: // First dive down to find the right parent.
303: for (int i = 0; i < children.length; i++) {
304: TextEdit child = children[i];
305: if (covers(child, edit)) {
306: insert(child, edit);
307: return;
308: }
309: }
310: // We have the right parent. Now check if some of the children have to
311: // be moved under the new edit since it is covering it.
312: for (int i = children.length - 1; i >= 0; i--) {
313: TextEdit child = children[i];
314: if (covers(edit, child)) {
315: parent.removeChild(i);
316: edit.addChild(child);
317: }
318: }
319: parent.addChild(edit);
320: if (edit instanceof MoveSourceEdit) {
321: parent.addChild(((MoveSourceEdit) edit).getTargetEdit());
322: }
323: }
324:
325: protected static boolean covers(TextEdit this Edit,
326: TextEdit otherEdit) {
327: if (this Edit.getLength() == 0) // an insertion point can't cover anything
328: return false;
329:
330: int this Offset = this Edit.getOffset();
331: int this End = this Edit.getExclusiveEnd();
332: if (otherEdit.getLength() == 0) {
333: int otherOffset = otherEdit.getOffset();
334: return this Offset < otherOffset && otherOffset < this End;
335: }
336: int otherOffset = otherEdit.getOffset();
337: int otherEnd = otherEdit.getExclusiveEnd();
338: return this Offset <= otherOffset && otherEnd <= this End;
339: }
340:
341: public boolean mustSave() {
342: if (!fIsSourceMode) {
343: if (fModel instanceof IEditable) {
344: if (((IEditable) fModel).isDirty()) {
345: return true;
346: }
347: }
348: }
349: return fEditOperations.size() > 0
350: || fDocumentProvider.canSaveDocument(fEditorInput);
351: }
352:
353: public void dispose() {
354: IAnnotationModel amodel = fDocumentProvider
355: .getAnnotationModel(fEditorInput);
356: if (amodel != null)
357: amodel.disconnect(fDocumentProvider
358: .getDocument(fEditorInput));
359: fDocumentProvider.removeElementStateListener(fElementListener);
360: fDocumentProvider.disconnect(fEditorInput);
361: if (fModelListener != null
362: && fModel instanceof IModelChangeProvider) {
363: ((IModelChangeProvider) fModel)
364: .removeModelChangedListener(fModelListener);
365: //if (undoManager != null)
366: //undoManager.disconnect((IModelChangeProvider) model);
367: }
368: if (fModel != null)
369: fModel.dispose();
370: }
371:
372: /**
373: * @return Returns the primary.
374: */
375: public boolean isPrimary() {
376: return fPrimary;
377: }
378:
379: /**
380: * @param primary The primary to set.
381: */
382: public void setPrimary(boolean primary) {
383: this .fPrimary = primary;
384: }
385:
386: public boolean setSourceEditingMode(boolean sourceMode) {
387: fIsSourceMode = sourceMode;
388: if (sourceMode) {
389: // entered source editing mode; in this mode,
390: // this context's document will be edited directly
391: // in the source editor. All changes in the model
392: // are caused by reconciliation and should not be
393: // fired to the world.
394: flushModel(fDocumentProvider.getDocument(fEditorInput));
395: fMustSynchronize = true;
396: return true;
397: }
398: // leaving source editing mode; if the document
399: // has been modified while in this mode,
400: // fire the 'world changed' event from the model
401: // to cause all the model listeners to become stale.
402: return synchronizeModelIfNeeded();
403: }
404:
405: private boolean synchronizeModelIfNeeded() {
406: if (fMustSynchronize) {
407: boolean result = synchronizeModel(fDocumentProvider
408: .getDocument(fEditorInput));
409: fMustSynchronize = false;
410: return result;
411: }
412: return true;
413: }
414:
415: public void doRevert() {
416: fMustSynchronize = true;
417: synchronizeModelIfNeeded();
418: /*
419: if (model instanceof IEditable) {
420: ((IEditable)model).setDirty(false);
421: }
422: */
423: }
424:
425: public boolean isInSourceMode() {
426: return fIsSourceMode;
427: }
428:
429: public boolean isModelCorrect() {
430: synchronizeModelIfNeeded();
431: return fModel != null ? fModel.isValid() : false;
432: }
433:
434: protected boolean synchronizeModel(IDocument doc) {
435: return true;
436: }
437:
438: public boolean matches(IResource resource) {
439: if (fEditorInput instanceof IFileEditorInput) {
440: IFileEditorInput finput = (IFileEditorInput) fEditorInput;
441: IFile file = finput.getFile();
442: if (file.equals(resource))
443: return true;
444: }
445: return false;
446: }
447:
448: /**
449: * @return Returns the validated.
450: */
451: public boolean isValidated() {
452: return fValidated;
453: }
454:
455: /**
456: * @param validated The validated to set.
457: */
458: public void setValidated(boolean validated) {
459: this .fValidated = validated;
460: }
461:
462: public String getLineDelimiter() {
463: if (fDocumentProvider != null) {
464: IDocument document = fDocumentProvider
465: .getDocument(fEditorInput);
466: if (document != null) {
467: return TextUtilities.getDefaultLineDelimiter(document);
468: }
469: }
470: return System.getProperty("line.separator"); //$NON-NLS-1$
471: }
472:
473: /**
474: * @param input
475: * @throws CoreException
476: */
477: private void updateInput(IEditorInput newInput)
478: throws CoreException {
479: deinitializeDocumentProvider();
480: fEditorInput = newInput;
481: initializeDocumentProvider();
482: }
483:
484: /**
485: *
486: */
487: private void deinitializeDocumentProvider() {
488: IAnnotationModel amodel = fDocumentProvider
489: .getAnnotationModel(fEditorInput);
490: if (amodel != null) {
491: amodel.disconnect(fDocumentProvider
492: .getDocument(fEditorInput));
493: }
494: fDocumentProvider.removeElementStateListener(fElementListener);
495: fDocumentProvider.disconnect(fEditorInput);
496: }
497:
498: /**
499: * @throws CoreException
500: */
501: private void initializeDocumentProvider() throws CoreException {
502: fDocumentProvider.connect(fEditorInput);
503: IAnnotationModel amodel = fDocumentProvider
504: .getAnnotationModel(fEditorInput);
505: if (amodel != null) {
506: amodel.connect(fDocumentProvider.getDocument(fEditorInput));
507: }
508: fDocumentProvider.addElementStateListener(fElementListener);
509: }
510:
511: /**
512: * @param monitor
513: */
514: public void doSaveAs(IProgressMonitor monitor) throws Exception {
515: // Get the editor shell
516: Shell shell = getEditor().getSite().getShell();
517: // Create the save as dialog
518: SaveAsDialog dialog = new SaveAsDialog(shell);
519: // Set the initial file name to the original file name
520: IFile file = null;
521: if (fEditorInput instanceof IFileEditorInput) {
522: file = ((IFileEditorInput) fEditorInput).getFile();
523: dialog.setOriginalFile(file);
524: }
525: // Create the dialog
526: dialog.create();
527: // Warn the user if the underlying file does not exist
528: if (fDocumentProvider.isDeleted(fEditorInput) && (file != null)) {
529: String message = NLS
530: .bind(
531: PDEUIMessages.InputContext_errorMessageFileDoesNotExist,
532: file.getName());
533: dialog.setErrorMessage(null);
534: dialog.setMessage(message, IMessageProvider.WARNING);
535: }
536: // Open the dialog
537: if (dialog.open() == Window.OK) {
538: // Get the path to where the new file will be stored
539: IPath path = dialog.getResult();
540: handleSaveAs(monitor, path);
541: }
542: }
543:
544: /**
545: * @param monitor
546: * @param path
547: * @throws Exception
548: * @throws CoreException
549: * @throws InterruptedException
550: * @throws InvocationTargetException
551: */
552: private void handleSaveAs(IProgressMonitor monitor, IPath path)
553: throws Exception, CoreException, InterruptedException,
554: InvocationTargetException {
555: // Ensure a new location was selected
556: if (path == null) {
557: monitor.setCanceled(true);
558: throw new Exception(
559: PDEUIMessages.InputContext_errorMessageLocationNotSet);
560: }
561: // Resolve the new file location
562: IWorkspace workspace = ResourcesPlugin.getWorkspace();
563: IFile newFile = workspace.getRoot().getFile(path);
564: // Create the new editor input
565: final IEditorInput newInput = new FileEditorInput(newFile);
566: // Send notice of editor input changes
567: fDocumentProvider.aboutToChange(newInput);
568: // Flush any unsaved changes
569: flushModel(fDocumentProvider.getDocument(fEditorInput));
570: try {
571: // Execute the workspace modification in a separate thread
572: PlatformUI.getWorkbench().getProgressService()
573: .busyCursorWhile(
574: createWorkspaceModifyOperation(newInput));
575: monitor.setCanceled(false);
576: // Store the new editor input in this context
577: updateInput(newInput);
578: } catch (InterruptedException e) {
579: monitor.setCanceled(true);
580: throw e;
581: } catch (InvocationTargetException e) {
582: monitor.setCanceled(true);
583: throw e;
584: } finally {
585: fDocumentProvider.changed(newInput);
586: }
587: }
588:
589: /**
590: * @param newInput
591: * @return
592: */
593: private WorkspaceModifyOperation createWorkspaceModifyOperation(
594: final IEditorInput newInput) {
595: WorkspaceModifyOperation operation = new WorkspaceModifyOperation() {
596: public void execute(final IProgressMonitor monitor)
597: throws CoreException {
598: // Save the old editor input content to the new editor input
599: // location
600: fDocumentProvider.saveDocument(monitor,
601: // New editor input location
602: newInput,
603: // Old editor input content
604: fDocumentProvider.getDocument(fEditorInput),
605: true);
606: }
607: };
608: return operation;
609: }
610:
611: }
|