/******************************************************************************* * Copyright (c) 2008, 2011 Thomas Holland (thomas@innot.de) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Thomas Holland - initial API and implementation *******************************************************************************/ package de.innot.avreclipse.ui.editors; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.ide.IDE; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.texteditor.IElementStateListener; import de.innot.avreclipse.AVRPlugin; import de.innot.avreclipse.core.toolinfo.fuses.ByteValues; /** * The FuseByte File Editor. * <p> * This editor has two pages * <ul> * <li>page 0 contains the form based editor</li> * <li>page 1 is a simple text editor to edit the raw file</li> * </ul> * While the first one is a real IFormPage, the latter is only a TextEditor wrapped in an IFormPage * to make the interfaces consistent. * </p> * <p> * The basic source for this editor is an <code>IFile</code>, extracted from the * <code>IEditorInput</code> to this editor. With the <code>IEditorInput</code> the * {@link FuseFileDocumentProvider} can provide both the <code>IDocument</code> required for the * source text editor as well as the {@link ByteValues} for the form editor. Both target formats are * linked internally and changes to one are also applied to the other. * </p> * <p> * As the source file is a text document, the source editor acts as a master for this editor. The * save, save as and revert user actions are sent to the source editor and handled there. * </p> * <p> * This editor implements two listeners: * <ul> * <li>A resource change listener which will close the editor when the parent project is closed.</li> * <li>An element state listener which will close the editor when the source file is deleted and * which will update the EditorInput when the source file is renamed / moved.</li> * </ul> * * @see LockbitsEditor * * @author Thomas Holland * @since 2.3 * */ public class FusesEditor extends FormEditor implements IResourceChangeListener, IElementStateListener, IGotoMarker { private final static String FORMEDITOR_ID = "formEditorPageId"; private final static String SOURCEEDITOR_ID = "sourceEditorPageId"; /** The form based editor for the source ByteValues. */ private ByteValuesFormEditor fFuseEditor; /** The source text editor for the source file. */ private ByteValuesSourceEditor fSourceEditor; /** * The document provider for fuse file documents. Only used to add a Element state listener to * be notified when the source file is renamed, moved or deleted. */ private FuseFileDocumentProvider fDocumentProvider; /** * Creates a fuse bytes editor. * <p> * Registers this editor as an <code>ResourceChangeListener</code> to be informed about * project closure. * </p> */ public FusesEditor() { super(); ResourcesPlugin.getWorkspace().addResourceChangeListener(this); } /* * (non-Javadoc) * * @see org.eclipse.ui.forms.editor.FormEditor#init(org.eclipse.ui.IEditorSite, * org.eclipse.ui.IEditorInput) */ @Override public void init(IEditorSite site, IEditorInput editorInput) throws PartInitException { if (!(editorInput instanceof IFileEditorInput)) throw new PartInitException("Invalid Input: Must be IFileEditorInput"); super.init(site, editorInput); IFile file = (IFile) editorInput.getAdapter(IFile.class); if (file == null) { throw new PartInitException("Invalid Input: Must be a IFile"); } if (!file.exists()) { throw new PartInitException("Invalid Input: File does not exist."); } // Get the document provider and add ourself as a listener, so we are informed if the editor // input file is moved, renamed or deleted. fDocumentProvider = FuseFileDocumentProvider.getDefault(); fDocumentProvider.addElementStateListener(this); // Use the file name as a part name setPartName(editorInput.getName()); // Description is not required as it should be obvious to the user what he is editing. // setContentDescription("Edit Fuse Bytes"); } /* * (non-Javadoc) * * @see org.eclipse.ui.forms.editor.FormEditor#addPages() */ @Override protected void addPages() { try { fFuseEditor = new ByteValuesFormEditor(this, FORMEDITOR_ID, "BitFields"); addPage(fFuseEditor); fSourceEditor = new ByteValuesSourceEditor(this, SOURCEEDITOR_ID, "Source"); addPage(fSourceEditor, getEditorInput()); } catch (PartInitException pie) { IStatus status = new Status(IStatus.ERROR, AVRPlugin.PLUGIN_ID, "Could not add editor page to the FusesEditor", pie); AVRPlugin.getDefault().log(status); } } /* * (non-Javadoc) * * @see org.eclipse.ui.forms.editor.FormEditor#dispose() */ @Override public void dispose() { fDocumentProvider.removeElementStateListener(this); ResourcesPlugin.getWorkspace().removeResourceChangeListener(this); super.dispose(); } /* * (non-Javadoc) * * @see org.eclipse.ui.part.EditorPart#doSave(org.eclipse.core.runtime.IProgressMonitor) */ @Override public void doSave(IProgressMonitor monitor) { IWorkspaceRunnable batchSave = new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) { // Commit the pages fFuseEditor.doSave(monitor); fSourceEditor.doSave(monitor); editorDirtyStateChanged(); } }; try { ResourcesPlugin.getWorkspace().run(batchSave, null, IWorkspace.AVOID_UPDATE, monitor); } catch (CoreException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /* * (non-Javadoc) * * @see org.eclipse.ui.part.EditorPart#doSaveAs() */ @Override public void doSaveAs() { { // TODO: implement our own Save As dialog to inhibit changes to the extension. // The current implementation lets the source text editor do the save as operation and // gets the new IEditorInput from the source editor. // However, if the user changes the extension the file will not be recognized as a // fuse/locks anymore once the editor is closed. This side effect has to be document in // the user guide, but it still would be nicer if the user could not change the // extension at all. fSourceEditor.doSaveAs(); IEditorInput newinput = fSourceEditor.getEditorInput(); fFuseEditor.setEditorInput(newinput); setInput(newinput); editorDirtyStateChanged(); IFileEditorInput newfileinput = (IFileEditorInput) newinput; setPartName(newfileinput.getName()); } } /* * (non-Javadoc) * * @see org.eclipse.ui.ide.IGotoMarker#gotoMarker(org.eclipse.core.resources.IMarker) */ public void gotoMarker(IMarker marker) { // The only markers we create are the problem markers from the source editor. So we activate // this editor if the user wants to go to a marker. // Change to the source editor and goto to the marker setActivePage(SOURCEEDITOR_ID); IDE.gotoMarker(fSourceEditor, marker); } /* * (non-Javadoc) * * @see org.eclipse.ui.part.EditorPart#isSaveAsAllowed() */ @Override public boolean isSaveAsAllowed() { // see #doSaveAs() above for the caveat with "save as" return true; } // ---- Resource Change Listener Method ------ /* * (non-Javadoc) * * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent) */ public void resourceChanged(final IResourceChangeEvent event) { switch (event.getType()) { case IResourceChangeEvent.PRE_CLOSE: handleCloseEvent(event); break; case IResourceChangeEvent.POST_CHANGE: // We could try to listen for resource move / rename / delete events here. However I // found it easier to listen to the element change events from the document // provider, because these events work with IEditorInput objects directly. OTOH the // resource change events only have IPaths and its a bit tedious to go from an IPath // to an IEditorInput. break; default: // Other types of Resource change events are ignored. } } /** * Handle a <code>PRE_CLOSE</code> resource change event. * <p> * This method will go through all workbench pages, test if they are for the project to be * closed, and close the Editor of the page. * </p> * * @param event */ private void handleCloseEvent(final IResourceChangeEvent event) { Display.getDefault().asyncExec(new Runnable() { public void run() { IWorkbenchPage[] pages = getSite().getWorkbenchWindow().getPages(); for (int i = 0; i < pages.length; i++) { IFile sourcefile = (IFile) getEditorInput().getAdapter(IFile.class); if (sourcefile != null && sourcefile.getProject().equals(event.getResource())) { IEditorPart editorPart = pages[i].findEditor(getEditorInput()); pages[i].closeEditor(editorPart, true); } } } }); } // ---- DocumentProvider Element Change Listener Methods ------ /* * (non-Javadoc) * * @see org.eclipse.ui.texteditor.IElementStateListener#elementDeleted(java.lang.Object) */ public void elementDeleted(Object element) { // Close the editor if the file has been deleted. // If there are unsaved changed the source file editor will inhibit the closure of this // editor and will ask the user to save the changes to a different file once the editor is // closed by the user. if (element != null && element.equals(getEditorInput())) { close(true); } } /* * (non-Javadoc) * * @see org.eclipse.ui.texteditor.IElementStateListener#elementMoved(java.lang.Object, * java.lang.Object) */ public void elementMoved(Object originalElement, Object movedElement) { // sou if (movedElement == null) { // An element has been moved to file nirvana. Handle this as a delete. elementDeleted(originalElement); } // Check that our source file has been moved IEditorInput original = null; IEditorInput moved = null; if (originalElement instanceof IEditorInput) { original = (IEditorInput) originalElement; } if (movedElement instanceof IEditorInput) { moved = (IEditorInput) movedElement; } if (moved != null && original != null && original.equals(getEditorInput())) { // OK - our source file has moved. // The source editor has its own listener, so we don't need to tell him. // Just tell the form editor and update the filename. fFuseEditor.setEditorInput(moved); setPartName(moved.getName()); setInputWithNotify(moved); } } public void elementContentAboutToBeReplaced(Object element) { // no actions required } public void elementContentReplaced(Object element) { // no actions required } public void elementDirtyStateChanged(Object element, boolean isDirty) { // no actions required } }