ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor.java Source code

Java tutorial

Introduction

Here is the source code for ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor.java

Source

/*******************************************************************************
 * Copyright (c) 2010 McGill University
 * 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:
 *     Jonathan Faubert
 *******************************************************************************/
package ca.mcgill.cs.swevo.qualyzer.editors;

import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;

import net.sf.colorer.eclipse.ColorerPlugin;
import net.sf.colorer.eclipse.editors.ColorerEditor;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;

import ca.mcgill.cs.swevo.qualyzer.IQualyzerPreferenceConstants;
import ca.mcgill.cs.swevo.qualyzer.QualyzerActivator;
import ca.mcgill.cs.swevo.qualyzer.editors.inputs.RTFEditorInput;
import ca.mcgill.cs.swevo.qualyzer.model.Code;
import ca.mcgill.cs.swevo.qualyzer.model.CodeListener;
import ca.mcgill.cs.swevo.qualyzer.model.Facade;
import ca.mcgill.cs.swevo.qualyzer.model.Fragment;
import ca.mcgill.cs.swevo.qualyzer.model.IAnnotatedDocument;
import ca.mcgill.cs.swevo.qualyzer.model.Investigator;
import ca.mcgill.cs.swevo.qualyzer.model.ListenerManager;
import ca.mcgill.cs.swevo.qualyzer.model.Project;
import ca.mcgill.cs.swevo.qualyzer.model.ProjectListener;
import ca.mcgill.cs.swevo.qualyzer.model.Transcript;
import ca.mcgill.cs.swevo.qualyzer.model.ListenerManager.ChangeType;
import ca.mcgill.cs.swevo.qualyzer.ui.ResourcesUtil;
import ca.mcgill.cs.swevo.qualyzer.util.FileUtil;

/**
 * A Rich Text Editor.
 *
 */
public class RTFEditor extends ColorerEditor implements ProjectListener, CodeListener {
    public static final String ID = "ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor"; //$NON-NLS-1$

    private static final char UNDERLINE_CHAR = (char) 21;
    private static final char ITALIC_CHAR = (char) 9;
    private static final char BOLD_CHAR = (char) 2;
    private static final char FRAGMENT_CHAR = (char) 11;

    protected ImageRegistry fRegistry;

    private Action fBoldAction;
    private Action fItalicAction;
    private Action fUnderlineAction;
    private Action fMarkTextAction;
    private Action fRemoveCodeAction;
    private Action fRemoveAllCodesAction;
    private Action fViewFragmentsAction;

    private boolean fIsDirty;
    private IAnnotatedDocument fDocument;
    private Investigator fActiveInvestigator;

    /**
     * Constructor.
     * Initialises the actions.
     */
    public RTFEditor() {
        super();
        setSourceViewerConfiguration(new RTFSourceViewerConfiguration(getTextColorer()));
        setDocumentProvider(new RTFDocumentProvider2());

        fIsDirty = false;
        fRegistry = QualyzerActivator.getDefault().getImageRegistry();

        getPreferenceStore().setValue(AbstractDecoratedTextEditorPreferenceConstants.QUICK_DIFF_ALWAYS_ON, false);
        QualyzerActivator.getDefault().getPreferenceStore().addPropertyChangeListener(this);
        FontData font = PreferenceConverter.getFontData(QualyzerActivator.getDefault().getPreferenceStore(),
                IQualyzerPreferenceConstants.FONT);
        PreferenceConverter.setValue(getPreferenceStore(), JFaceResources.TEXT_FONT, font);
    }

    /**
     * Add an image to the registry.
     * @param key
     * @param pluginID
     * @param path
     */
    protected void addImage(String key, String pluginID, String path) {
        String fullKey = computeKey(key, pluginID);
        ImageDescriptor descriptor = fRegistry.getDescriptor(fullKey);
        if (descriptor == null) {
            fRegistry.put(fullKey, AbstractUIPlugin.imageDescriptorFromPlugin(pluginID, path));
        }
    }

    private String computeKey(String key, String pluginID) {
        return pluginID + "_" + key; //$NON-NLS-1$
    }

    /**
     * Get an image from the registry.
     * @param key
     * @param pluginID
     * @return
     */
    protected Image getImage(String key, String pluginID) {
        return fRegistry.get(computeKey(key, pluginID));
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#createSourceViewer(org.eclipse.swt.widgets.Composite, 
     * org.eclipse.jface.text.source.IVerticalRuler, int)
     */
    @Override
    protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
        fAnnotationAccess = getAnnotationAccess();
        fOverviewRuler = createOverviewRuler(getSharedColors());

        final SourceViewer viewer = new RTFSourceViewer(parent, ruler, fOverviewRuler, isOverviewRulerVisible(),
                styles);

        getSourceViewerDecorationSupport(viewer);

        viewer.showAnnotations(true);
        viewer.addSelectionChangedListener(createSelectionListener(viewer));

        return viewer;
    }

    /**
     * Handles enablement of the various actions when the selection changes in the source viewer.
     * @param viewer
     * @return
     */
    private ISelectionChangedListener createSelectionListener(final SourceViewer viewer) {
        return new ISelectionChangedListener() {

            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                Point selection = viewer.getSelectedRange();
                IAnnotationModel model = getSourceViewer().getAnnotationModel();

                boolean enabled = selection.y != 0;

                boolean boldEnabled = isBoldEnabled(model, selection);
                boolean italicEnabled = isItalicEnabled(model, selection);
                boolean underlineEnabled = isUnderlineEnabled(model, selection);
                boolean markTextEnabled = isMarkEnabled(model, selection);

                fBoldAction.setEnabled(enabled && boldEnabled);
                fItalicAction.setEnabled(enabled && italicEnabled);
                fUnderlineAction.setEnabled(enabled && underlineEnabled);
                fMarkTextAction.setEnabled(enabled && markTextEnabled);
            }
        };
    }

    /**
     * 
     * @param model
     * @param selection
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isMarkEnabled(IAnnotationModel model, Point selection) {
        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (annotation instanceof FragmentAnnotation) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        if (positions.size() == 0) {
            return true;
        } else if (positions.size() == 1) {
            Position position = positions.get(0);
            return position.offset == selection.x && position.length == selection.y;
        } else {
            return false;
        }

    }

    /**
     * @param model
     * @param selection
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isUnderlineEnabled(IAnnotationModel model, Point selection) {
        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (isUnderline(annotation)) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        return handleOverlaps(positions, selection);
    }

    /**
     * @param model
     * @param selection
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isItalicEnabled(IAnnotationModel model, Point selection) {
        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (isItalic(annotation)) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        return handleOverlaps(positions, selection);
    }

    /**
     * @param model
     * @param selection
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isBoldEnabled(IAnnotationModel model, Point selection) {
        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (isBold(annotation)) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        return handleOverlaps(positions, selection);
    }

    /**
     * Deal with the various cases of the selection overlapping any number of existing positions.
     * True if there are no overlapping positions or if there is one that completely spans the selection,
     * or if there are many that span the selection with no gaps.
     * False otherwise.
     * @param positions
     * @param selection
     * @return
     */
    private boolean handleOverlaps(ArrayList<Position> positions, Point selection) {
        boolean toReturn = false;

        if (positions.size() == 0) {
            toReturn = true;
        } else if (positions.size() == 1) {
            Position position = positions.get(0);
            toReturn = position.offset <= selection.x
                    && position.offset + position.length >= selection.x + selection.y;
        } else {
            sort(positions);
            Position startPos = positions.get(0);
            Position endPos = positions.get(positions.size() - 1);
            if (startPos.offset <= selection.x && endPos.offset + endPos.length >= selection.x + selection.y) {
                for (int i = 0; i < positions.size() - 1; i++) {
                    Position p1 = positions.get(i);

                    if (p1.offset + p1.length != positions.get(i + 1).offset) {
                        return false;
                    }
                }
                toReturn = true;
            }
        }

        return toReturn;
    }

    /**
     * Sort an arraylist of positions.
     * @param overlap
     * @param positions
     */
    private void sort(ArrayList<Position> positions) {
        Collections.sort(positions, new Comparator<Position>() {

            @Override
            public int compare(Position o1, Position o2) {
                if (o1.offset < o2.offset) {
                    return -1;
                } else if (o1.offset == o2.offset) {
                    return 0;
                } else {
                    return 1;
                }
            }
        });

    }

    /**
     * Checks if an annotation is bold.
     * @param annotation
     * @return
     */
    protected static boolean isBold(Annotation annotation) {
        String type = annotation.getType();
        return type.equals(RTFConstants.BOLD_TYPE) || type.equals(RTFConstants.BOLD_ITALIC_TYPE)
                || type.equals(RTFConstants.BOLD_UNDERLINE_TYPE)
                || type.equals(RTFConstants.BOLD_ITALIC_UNDERLINE_TYPE);
    }

    /**
     * Checks if an annotation is italic.
     * @param annotation
     * @return
     */
    protected static boolean isItalic(Annotation annotation) {
        String type = annotation.getType();
        return type.equals(RTFConstants.ITALIC_TYPE) || type.equals(RTFConstants.BOLD_ITALIC_TYPE)
                || type.equals(RTFConstants.ITALIC_UNDERLINE_TYPE)
                || type.equals(RTFConstants.BOLD_ITALIC_UNDERLINE_TYPE);
    }

    /**
     * Checks if an annotation is underlined.
     * @param annotation
     * @return
     */
    protected static boolean isUnderline(Annotation annotation) {
        String type = annotation.getType();
        return type.equals(RTFConstants.UNDERLINE_TYPE) || type.equals(RTFConstants.BOLD_UNDERLINE_TYPE)
                || type.equals(RTFConstants.ITALIC_UNDERLINE_TYPE)
                || type.equals(RTFConstants.BOLD_ITALIC_UNDERLINE_TYPE);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#getSourceViewerDecorationSupport(
     * org.eclipse.jface.text.source.ISourceViewer)
     */
    @Override
    protected SourceViewerDecorationSupport getSourceViewerDecorationSupport(ISourceViewer viewer) {
        if (fSourceViewerDecorationSupport == null) {
            fSourceViewerDecorationSupport = new RTFDecorationSupport(viewer, fOverviewRuler, fAnnotationAccess,
                    getSharedColors());
            configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport);
        }

        return fSourceViewerDecorationSupport;
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#isLineNumberRulerVisible()
     */
    @Override
    protected boolean isLineNumberRulerVisible() {
        return false;
    }

    /**
     * Initialises all the actions and their key bindings.
     * @see org.eclipse.ui.editors.text.TextEditor#createActions()
     */
    @Override
    protected void createActions() {
        super.createActions();

        RTFSourceViewer sourceViewer = (RTFSourceViewer) getSourceViewer();
        fBoldAction = new BoldAction(this, sourceViewer);
        fItalicAction = new ItalicAction(this, sourceViewer);
        fUnderlineAction = new UnderlineAction(this, sourceViewer);

        fMarkTextAction = new MarkTextAction(this, sourceViewer);
        fRemoveCodeAction = new RemoveCodeAction(this, sourceViewer);
        fRemoveAllCodesAction = new RemoveAllCodesAction(this, sourceViewer);
        fViewFragmentsAction = new ViewFragmentsAction(this, sourceViewer);

        setAction(RTFConstants.BOLD_ACTION_ID, fBoldAction);
        setAction(RTFConstants.UNDERLINE_ACTION_ID, fUnderlineAction);
        setAction(RTFConstants.ITALIC_ACTION_ID, fItalicAction);
        setAction(RTFConstants.FRAGMENT_ACTION_ID, fMarkTextAction);
        setAction(RTFConstants.REMOVE_ALL_CODES_ACTION_ID, fRemoveAllCodesAction);
        setAction(RTFConstants.REMOVE_CODE_ACTION_ID, fRemoveCodeAction);
        setAction(RTFConstants.VIEW_FRAGMENTS_ACTION_ID, fViewFragmentsAction);

        setActionActivationCode(RTFConstants.BOLD_ACTION_ID, BOLD_CHAR, 'b', SWT.CONTROL);
        setActionActivationCode(RTFConstants.ITALIC_ACTION_ID, ITALIC_CHAR, 'i', SWT.CONTROL);
        setActionActivationCode(RTFConstants.UNDERLINE_ACTION_ID, UNDERLINE_CHAR, 'u', SWT.CONTROL);
        setActionActivationCode(RTFConstants.FRAGMENT_ACTION_ID, FRAGMENT_CHAR, 'k', SWT.CONTROL);
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#rulerContextMenuAboutToShow(
     * org.eclipse.jface.action.IMenuManager)
     */
    @Override
    protected void rulerContextMenuAboutToShow(IMenuManager menu) {
        //super.rulerContextMenuAboutToShow(menu);
        //This removes the ruler context menu.
        //Do we want to add any of our own actions here?
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void createPartControl(Composite parent) {
        setRulerContextMenuId("#RTFRulerContext"); //$NON-NLS-1$
        setOverviewRulerContextMenuId("#RTFOverviewRulerContext"); //$NON-NLS-1$
        setEditorContextMenuId("#RTFEditorContext"); //$NON-NLS-1$

        super.createPartControl(parent);

        fDocument = ((RTFEditorInput) getEditorInput()).getDocument();
        fActiveInvestigator = recoverActiveInvestigator();

        ListenerManager listenerManager = Facade.getInstance().getListenerManager();
        listenerManager.registerProjectListener(fDocument.getProject(), this);
        listenerManager.registerCodeListener(fDocument.getProject(), this);
        ColorerPlugin.getDefault().setPropertyWordWrap(getTextColorer().getFileType(), 1);
        getSourceViewer().getTextWidget().setWordWrap(true);

        setPartName(fDocument.getName());
    }

    /**
     * Check the project description to find out who the active investigator is.
     * @return
     */
    private Investigator recoverActiveInvestigator() {
        Project project = fDocument.getProject();
        IProject wProject = ResourcesPlugin.getWorkspace().getRoot().getProject(project.getFolderName());
        String name = ""; //$NON-NLS-1$

        try {
            name = FileUtil.getProjectProperty(wProject, FileUtil.ACTIVE_INV);
        } catch (CoreException e) {
            return project.getInvestigators().get(0);
        }

        for (Investigator investigator : project.getInvestigators()) {
            if (investigator.getNickName().equals(name)) {
                return investigator;
            }
        }

        return project.getInvestigators().get(0);
    }

    /**
     * Returns the current Active Investigator. Mainly for use by MarkTextAction.
     * @return
     */
    protected Investigator getActiveInvestigator() {
        return fActiveInvestigator;
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#overviewRulerContextMenuAboutToShow(
     * org.eclipse.jface.action.IMenuManager)
     */
    @Override
    protected void overviewRulerContextMenuAboutToShow(IMenuManager menu) {
        // super.overviewRulerContextMenuAboutToShow(menu);
        // This removes the overview ruler context menu.
        // Do we want to add any of our own actions?
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.editors.text.TextEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager)
     */
    @Override
    protected void editorContextMenuAboutToShow(IMenuManager menu) {
        super.editorContextMenuAboutToShow(menu);

        menu.remove(ITextEditorActionConstants.GROUP_OPEN);
        menu.remove(ITextEditorActionConstants.GROUP_PRINT);
        menu.remove(ITextEditorActionConstants.GROUP_ADD);
        menu.remove(ITextEditorActionConstants.GROUP_REST);
        menu.remove(ITextEditorActionConstants.SHIFT_RIGHT);
        menu.remove(ITextEditorActionConstants.SHIFT_LEFT);
        menu.remove(ITextEditorActionConstants.GROUP_FIND);
        menu.remove(IWorkbenchActionConstants.MB_ADDITIONS);
        menu.remove(ITextEditorActionConstants.GROUP_SETTINGS);
        menu.remove(ITextEditorActionConstants.CONTEXT_PREFERENCES);

        for (IContributionItem item : menu.getItems()) {
            if (item.getId() == null) {
                menu.remove(item);
            }
        }

        addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.BOLD_ACTION_ID);
        addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.ITALIC_ACTION_ID);
        addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.UNDERLINE_ACTION_ID);
        addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.FRAGMENT_ACTION_ID);

        if (isRemoveVisible()) {
            addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.VIEW_FRAGMENTS_ACTION_ID);
            addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.REMOVE_CODE_ACTION_ID);
            addAction(menu, ITextEditorActionConstants.GROUP_EDIT, RTFConstants.REMOVE_ALL_CODES_ACTION_ID);
        }

        //check according to selection
        fBoldAction.setChecked(isBoldChecked());
        fItalicAction.setChecked(isItalicChecked());
        fUnderlineAction.setChecked(isUnderlineChecked());
    }

    /**
     * The Remove Code actions should only be visible when a fragment is selected.
     * @return
     */
    @SuppressWarnings("unchecked")
    private boolean isRemoveVisible() {
        IAnnotationModel model = getSourceViewer().getAnnotationModel();
        Point selection = getSourceViewer().getSelectedRange();

        if (selection.y == 0) {
            return false;
        } else {
            Iterator<Annotation> iter = model.getAnnotationIterator();
            while (iter.hasNext()) {
                Annotation annotation = iter.next();
                if (annotation instanceof FragmentAnnotation) {
                    Position position = model.getPosition(annotation);
                    if (position.offset == selection.x && position.length == selection.y) {
                        return true;
                    }
                }
            }
            return false;
        }
    }

    /**
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isUnderlineChecked() {
        IAnnotationModel model = getSourceViewer().getAnnotationModel();
        Point selection = getSourceViewer().getSelectedRange();

        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (isUnderline(annotation)) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        return positions.size() > 0;
    }

    /**
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isItalicChecked() {
        IAnnotationModel model = getSourceViewer().getAnnotationModel();
        Point selection = getSourceViewer().getSelectedRange();

        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (isItalic(annotation)) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        return positions.size() > 0;
    }

    /**
     * @return
     */
    @SuppressWarnings("unchecked")
    protected boolean isBoldChecked() {
        IAnnotationModel model = getSourceViewer().getAnnotationModel();
        Point selection = getSourceViewer().getSelectedRange();

        ArrayList<Position> positions = new ArrayList<Position>();
        Iterator<Annotation> iter = model.getAnnotationIterator();

        while (iter.hasNext()) {
            Annotation annotation = iter.next();
            if (isBold(annotation)) {
                Position position = model.getPosition(annotation);
                if (position.overlapsWith(selection.x, selection.y)) {
                    positions.add(position);
                }
            }
        }

        return positions.size() > 0;
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractTextEditor#doSave(org.eclipse.core.runtime.IProgressMonitor)
     */
    @Override
    public void doSave(IProgressMonitor progressMonitor) {
        super.doSave(progressMonitor);
        fIsDirty = false;
    }

    /**
     * 
     */
    protected void setDirty() {
        if (!isDirty()) {
            fIsDirty = true;
            firePropertyChange(PROP_DIRTY);
        }
    }

    /* (non-Javadoc)
     * @see org.eclipse.ui.texteditor.AbstractTextEditor#isDirty()
     */
    @Override
    public boolean isDirty() {
        return super.isDirty() || fIsDirty;
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.model.ProjectListener#projectChanged(
     * ca.mcgill.cs.swevo.qualyzer.model.ListenerManager.ChangeType, ca.mcgill.cs.swevo.qualyzer.model.Project, 
     * ca.mcgill.cs.swevo.qualyzer.model.Facade)
     */
    @Override
    public void projectChanged(ChangeType cType, Project project, Facade facade) {
        if (ChangeType.DELETE == cType) //Close without saving.
        {
            getSite().getPage().closeEditor(this, false);
        } else if (ChangeType.RENAME == cType) //Close and save ask to save.
        {
            ResourcesUtil.closeEditor(getSite().getPage(), getEditorInput().getName());
        } else if (ChangeType.MODIFY == cType) //Active investigator has changed.
        {
            fDocument.setProject(project);
            refreshInput();
            fActiveInvestigator = recoverActiveInvestigator();
        }

    }

    /**
     * Makes sure that all the objects are fresh from the database.
     */
    private void refreshInput() {
        IProject project = ResourcesPlugin.getWorkspace().getRoot()
                .getProject(fDocument.getProject().getFolderName());
        IFile file;
        if (fDocument instanceof Transcript) {
            file = project.getFile("transcripts" + File.separator + fDocument.getFileName()); //$NON-NLS-1$
        } else {
            file = project.getFile("memos" + File.separator + fDocument.getFileName()); //$NON-NLS-1$
        }
        RTFEditorInput input = new RTFEditorInput(file, fDocument);

        setInput(input);
        setPartName(fDocument.getName());

    }

    /* (non-Javadoc)
     * @see net.sf.colorer.eclipse.editors.ColorerEditor#dispose()
     */
    @Override
    public void dispose() {
        ListenerManager listenerManager = Facade.getInstance().getListenerManager();
        listenerManager.unregisterProjectListener(fDocument.getProject(), this);
        listenerManager.unregisterCodeListener(fDocument.getProject(), this);
        QualyzerActivator.getDefault().getPreferenceStore().removePropertyChangeListener(this);
        super.dispose();
    }

    /**
     * If a code has been modified or deleted, goes through all the fragments and updates them.
     */
    @SuppressWarnings("unchecked")
    @Override
    public void codeChanged(ChangeType cType, Code[] codes, Facade facade) {
        RTFAnnotationModel model = (RTFAnnotationModel) getSourceViewer().getAnnotationModel();
        Iterator<Annotation> iter = model.getAnnotationIterator();
        fDocument.setProject(codes[0].getProject());

        if (cType == ChangeType.DELETE || cType == ChangeType.MODIFY) {
            IAnnotatedDocument document = Facade.getInstance().forceDocumentLoad(fDocument);
            Map<Integer, Fragment> newMap = document.getFragments();
            fDocument.setFragments(newMap);

            while (iter.hasNext()) {
                Annotation annotation = iter.next();
                if (annotation instanceof FragmentAnnotation) {
                    Fragment fragment = ((FragmentAnnotation) annotation).getFragment();
                    if (!newMap.containsKey(fragment.getOffset())) {
                        model.removeAnnotationOnly(annotation);
                    } else {
                        Fragment newFragment = newMap.get(fragment.getOffset());
                        ((FragmentAnnotation) annotation).setFragment(newFragment);
                    }
                }
            }
        }
    }

    /**
     * Get the Transcript or Memo.
     * @return
     */
    public IAnnotatedDocument getDocument() {
        return fDocument;
    }

    /**
     * Get the BoldAction associated with this editor.
     * @return
     */
    public Action getBoldAction() {
        return fBoldAction;
    }

    /**
     * Get the ItalicAction associated with this editor.
     * @return
     */
    public Action getItalicAction() {
        return fItalicAction;
    }

    /**
     * Get the UnderlineAction associated with this editor.
     * @return
     */
    public Action getUnderlineAction() {
        return fUnderlineAction;
    }

    /**
     * Get the MarkTextAction associated with this editor.
     * @return
     */
    public Action getMarkTextAction() {
        return fMarkTextAction;
    }

    /**
     * Get the RemoveCodeAction associated with this editor.
     * @return
     */
    public Action getRemoveCodeAction() {
        return fRemoveCodeAction;
    }

    /**
     * Get the RemoveAllCodesAction associated with this editor.
     * @return
     */
    public Action getRemoveAllCodesAction() {
        return fRemoveAllCodesAction;
    }

    /**
     * Propagates a change in the font preference to the editor preference store.
     */
    @Override
    public void propertyChange(PropertyChangeEvent e) {
        if (e == null || e.getProperty().equals(IQualyzerPreferenceConstants.FONT)) {
            IPreferenceStore pref = getPreferenceStore();
            FontData oldFont = PreferenceConverter.getFontData(pref, JFaceResources.TEXT_FONT);
            FontData font = PreferenceConverter.getFontData(QualyzerActivator.getDefault().getPreferenceStore(),
                    IQualyzerPreferenceConstants.FONT);
            PreferenceConverter.setValue(pref, JFaceResources.TEXT_FONT, font);
            pref.firePropertyChangeEvent(JFaceResources.TEXT_FONT, oldFont, font);
        } else {
            super.propertyChange(e);
        }
    }
}