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

Java tutorial

Introduction

Here is the source code for ca.mcgill.cs.swevo.qualyzer.editors.TranscriptEditor.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.HashMap;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.dialogs.MessageDialog;
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.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Scale;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ca.mcgill.cs.swevo.qualyzer.IQualyzerPreferenceConstants;
import ca.mcgill.cs.swevo.qualyzer.QualyzerActivator;
import ca.mcgill.cs.swevo.qualyzer.QualyzerException;
import ca.mcgill.cs.swevo.qualyzer.editors.inputs.RTFEditorInput;
import ca.mcgill.cs.swevo.qualyzer.model.Facade;
import ca.mcgill.cs.swevo.qualyzer.model.Timestamp;
import ca.mcgill.cs.swevo.qualyzer.model.Transcript;
import ca.mcgill.cs.swevo.qualyzer.model.TranscriptListener;
import ca.mcgill.cs.swevo.qualyzer.model.ListenerManager.ChangeType;

/**
 *   Extends the RTFEditor with a formatting bar and an audio bar.
 */
public class TranscriptEditor extends RTFEditor implements TranscriptListener {
    public static final String ID = "ca.mcgill.cs.swevo.qualyzer.editors.transcriptEditor"; //$NON-NLS-1$

    private static Logger gLogger = LoggerFactory.getLogger(TranscriptEditor.class);

    private static final String UNABLE_TO_ACCESS_MARKER = "Unable to access marker"; //$NON-NLS-1$
    private static final int NUM_COLS = 10;
    private static final int SECONDS_PER_MINUTE = 60;
    private static final int TEN = 10;

    private static final String PLAY_IMG = "PLAY_IMG"; //$NON-NLS-1$
    private static final String PAUSE_IMG = "PAUSE_IMG"; //$NON-NLS-1$
    private static final String STOP_IMG = "STOP_IMG"; //$NON-NLS-1$
    private static final String BOLD_IMG = "BOLD_IMG"; //$NON-NLS-1$
    private static final String ITALIC_IMG = "ITALIC_IMG"; //$NON-NLS-1$
    private static final String UNDERLINE_IMG = "UNDERLINE_IMG"; //$NON-NLS-1$
    private static final String CODE_IMG = "CODE_IMG"; //$NON-NLS-1$
    private static final String BACK_IMG = "BACK_IMG"; //$NON-NLS-1$
    private static final String FORWARD_IMG = "FORWARD_IMG"; //$NON-NLS-1$

    private Button fBoldButton;
    private Button fUnderlineButton;
    private Button fItalicButton;
    private Button fCodeButton;
    private Button fPlayButton;
    private Button fStopButton;
    private Button fBackButton;
    private Button fForwardButton;
    private AudioPlayer fAudioPlayer;

    private Text fTimeLabel;
    private Text fLengthLabel;
    private Scale fAudioSlider;
    private int fAudioLength;

    private Composite fTopBar;

    private int fSeekTime;

    private Action fAddTimeStampAction;
    private Action fRemoveTimeStampAction;

    private boolean fInRuler;

    /**
     * Constructor.
     */
    public TranscriptEditor() {
        addImage(PLAY_IMG, QualyzerActivator.PLUGIN_ID, "icons/play.png"); //$NON-NLS-1$
        addImage(PAUSE_IMG, QualyzerActivator.PLUGIN_ID, "icons/pause.png"); //$NON-NLS-1$
        addImage(STOP_IMG, QualyzerActivator.PLUGIN_ID, "icons/stop.png"); //$NON-NLS-1$
        addImage(BOLD_IMG, QualyzerActivator.PLUGIN_ID, "icons/text_bold.png"); //$NON-NLS-1$
        addImage(ITALIC_IMG, QualyzerActivator.PLUGIN_ID, "icons/text_italic.png"); //$NON-NLS-1$
        addImage(UNDERLINE_IMG, QualyzerActivator.PLUGIN_ID, "icons/text_underline.png"); //$NON-NLS-1$
        addImage(CODE_IMG, QualyzerActivator.PLUGIN_ID, "icons/code_obj.gif"); //$NON-NLS-1$
        addImage(BACK_IMG, QualyzerActivator.PLUGIN_ID, "icons/seek-backward.png"); //$NON-NLS-1$
        addImage(FORWARD_IMG, QualyzerActivator.PLUGIN_ID, "icons/seek-forward.png"); //$NON-NLS-1$

        fSeekTime = QualyzerActivator.getDefault().getPreferenceStore()
                .getInt(IQualyzerPreferenceConstants.SEEK_TIME);
        fInRuler = false;
    }

    /**
     * 
     */
    private void createTimeStampAction() {
        fAddTimeStampAction = new Action(Messages.getString("editors.TranscriptEditor.addTimeStamp")) //$NON-NLS-1$
        {
            @Override
            public void run() {
                addTimeStamp();
            }
        };
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor#createSourceViewer(org.eclipse.swt.widgets.Composite,
     *  org.eclipse.jface.text.source.IVerticalRuler, int)
     */
    @Override
    protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
        final SourceViewer viewer = (SourceViewer) super.createSourceViewer(parent, ruler, styles);
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {
            @Override
            public void selectionChanged(SelectionChangedEvent event) {
                IAnnotationModel model = viewer.getAnnotationModel();
                Point selection = viewer.getSelectedRange();

                boolean enabled = selection.y != 0;

                fBoldButton.setEnabled(enabled && isBoldEnabled(model, selection));
                fItalicButton.setEnabled(enabled && isItalicEnabled(model, selection));
                fUnderlineButton.setEnabled(enabled && isUnderlineEnabled(model, selection));
                fCodeButton.setEnabled(enabled && isMarkEnabled(model, selection));

                fBoldButton.setSelection(isBoldChecked());
                fItalicButton.setSelection(isItalicChecked());
                fUnderlineButton.setSelection(isUnderlineChecked());
            }

        });
        return viewer;
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    @Override
    public void createPartControl(Composite parent) {
        //This controls displaying of the top button bar.
        parent.setLayout(new GridLayout(1, true));

        fTopBar = new Composite(parent, SWT.BORDER);
        fTopBar.setLayoutData(new GridData(SWT.FILL, SWT.NULL, true, false));
        fTopBar.setLayout(new GridLayout(NUM_COLS, false));

        createFormatButtonBar(fTopBar);
        createMusicBar(fTopBar);

        super.createPartControl(parent);

        parent.getChildren()[1].setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        if (((Transcript) getDocument()).getAudioFile() == null) {
            setEnable(fTopBar, false);
            fAudioPlayer = null;
        } else {
            Transcript trans = (Transcript) getDocument();
            IProject project = ResourcesPlugin.getWorkspace().getRoot()
                    .getProject(trans.getProject().getFolderName());
            String audioFile = project.getLocation() + File.separator + trans.getAudioFile().getRelativePath();

            try {
                fAudioPlayer = new AudioPlayer(audioFile, this);
            } catch (QualyzerException e) {
                MessageDialog.openError(getSite().getShell(),
                        Messages.getString("editors.TranscriptEditor.audioPlayerError"), e.getMessage()); //$NON-NLS-1$
                setEnable(fTopBar, false);
            }
        }

        hookupButtonActions();

        Facade.getInstance().getListenerManager().registerTranscriptListener(getDocument().getProject(), this);
    }

    /**
     * Sets the 2nd half of the passed container to the enablement according to state.
     * Used to enable or disable the music bar.
     * @param container
     * @param state
     */
    private void setEnable(Composite container, boolean state) {
        Control[] children = container.getChildren();
        for (int i = 0; i <= children.length / 2; i++) {
            children[children.length - (i + 1)].setEnabled(state);
        }
    }

    /**
     * Attaches the button actions to the buttons.
     */
    private void hookupButtonActions() {
        fBoldButton.addSelectionListener(createButtonSelectionListener(getBoldAction()));
        fUnderlineButton.addSelectionListener(createButtonSelectionListener(getUnderlineAction()));
        fItalicButton.addSelectionListener(createButtonSelectionListener(getItalicAction()));
        fCodeButton.addSelectionListener(createButtonSelectionListener(getMarkTextAction()));

        final PlayPushedAdapter playAdapter = new PlayPushedAdapter();

        fPlayButton.addSelectionListener(playAdapter);
        fStopButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                try {
                    fPlayButton.setImage(getImage(PLAY_IMG, QualyzerActivator.PLUGIN_ID));
                    playAdapter.stop();
                    fAudioPlayer.stop();
                    setSeconds(0);
                } catch (QualyzerException ex) {
                    openErrorDialog(ex.getMessage());
                }
            }
        });

        fBackButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                seekBack();
            }
        });

        fForwardButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                seekForward();
            }
        });
    }

    //these create the top button bar.
    /**
     * @param topBar
     * @return
     */
    private Composite createMusicBar(Composite parent) {
        fPlayButton = new Button(parent, SWT.PUSH);
        fPlayButton.setImage(getImage(PLAY_IMG, QualyzerActivator.PLUGIN_ID));

        fStopButton = new Button(parent, SWT.PUSH);
        fStopButton.setImage(getImage(STOP_IMG, QualyzerActivator.PLUGIN_ID));

        fBackButton = new Button(parent, SWT.PUSH);
        fBackButton.setImage(getImage(BACK_IMG, QualyzerActivator.PLUGIN_ID));

        fAudioSlider = new Scale(parent, SWT.HORIZONTAL);
        fAudioSlider.setMinimum(0);
        fAudioSlider.setSelection(0);
        fAudioSlider.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
        fAudioSlider.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseUp(MouseEvent e) {
                try {
                    fAudioPlayer.jumpToTime(fAudioSlider.getSelection());
                } catch (QualyzerException ex) {
                    openErrorDialog(ex.getMessage());
                }
            }

        });

        fAudioSlider.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                setSeconds(fAudioSlider.getSelection());
            }
        });

        fForwardButton = new Button(parent, SWT.PUSH);
        fForwardButton.setImage(getImage(FORWARD_IMG, QualyzerActivator.PLUGIN_ID));

        createTimeDisplay(parent);

        return parent;
    }

    /**
     * @param parent
     */
    private void createTimeDisplay(Composite parent) {
        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayout(new GridLayout(2, false));
        composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false));

        fTimeLabel = new Text(composite, SWT.NULL);
        fTimeLabel.setLayoutData(new GridData(SWT.NULL, SWT.FILL, false, false));
        fTimeLabel.setText("0:00"); //$NON-NLS-1$
        fTimeLabel.addKeyListener(createGotoListener());

        fLengthLabel = new Text(composite, SWT.READ_ONLY);
        fLengthLabel.setLayoutData(new GridData(SWT.NULL, SWT.NULL, false, false));
        fLengthLabel.setText("/ 0:00"); //$NON-NLS-1$
        fLengthLabel.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
    }

    /**
     * Creates the listener that seeks to the specified time when a time is entered into the 
     * current time text widget.
     * @return
     */
    private KeyListener createGotoListener() {
        return new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                if (e.keyCode == SWT.CR || e.keyCode == SWT.KEYPAD_CR) {
                    String[] time = fTimeLabel.getText().split(":"); //$NON-NLS-1$
                    if (time.length != 2) {
                        setSeconds(fAudioSlider.getSelection());
                        return;
                    }
                    try {
                        int seconds = Integer.parseInt(time[0]) * SECONDS_PER_MINUTE;
                        seconds += Integer.parseInt(time[1]);

                        if (seconds > fAudioLength) {
                            seconds = fAudioLength;
                        } else if (seconds < 0) {
                            seconds = 0;
                        }

                        seekToTime(seconds);
                    } catch (NumberFormatException ex) {
                        setSeconds(fAudioSlider.getSelection());
                    }
                }
            }
        };
    }

    private Control createFormatButtonBar(Composite parent) {
        fBoldButton = new Button(parent, SWT.TOGGLE);
        fBoldButton.setImage(getImage(BOLD_IMG, QualyzerActivator.PLUGIN_ID));
        fBoldButton.setEnabled(false);

        fUnderlineButton = new Button(parent, SWT.TOGGLE);
        fUnderlineButton.setImage(getImage(UNDERLINE_IMG, QualyzerActivator.PLUGIN_ID));
        fUnderlineButton.setEnabled(false);

        fItalicButton = new Button(parent, SWT.TOGGLE);
        fItalicButton.setImage(getImage(ITALIC_IMG, QualyzerActivator.PLUGIN_ID));
        fItalicButton.setEnabled(false);

        fCodeButton = new Button(parent, SWT.PUSH);
        fCodeButton.setImage(getImage(CODE_IMG, QualyzerActivator.PLUGIN_ID));
        fCodeButton.setEnabled(false);

        return parent;
    }

    /**
     * A Generic listener that handles all the button actions for the format bar.
     * @param action
     * @return
     */
    private SelectionAdapter createButtonSelectionListener(final Action action) {
        return new SelectionAdapter() {
            private Action fAction = action;

            /* (non-Javadoc)
             * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
             */
            @Override
            public void widgetSelected(SelectionEvent e) {
                if (fAction.isEnabled()) {
                    fAction.run();
                }
            }
        };
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.model.TranscriptListener#transcriptChanged(
     * ca.mcgill.cs.swevo.qualyzer.model.ListenerManager.ChangeType, ca.mcgill.cs.swevo.qualyzer.model.Transcript[],
     *  ca.mcgill.cs.swevo.qualyzer.model.Facade)
     */
    @Override
    public void transcriptChanged(ChangeType cType, Transcript[] transcripts, Facade facade) {
        if (cType == ChangeType.DELETE) {
            for (Transcript transcript : transcripts) {
                if (transcript.equals(getDocument())) {
                    getSite().getPage().closeEditor(this, false);
                    break;
                }
            }
        } else if (cType == ChangeType.MODIFY) {
            for (Transcript transcript : transcripts) {
                if (transcript.equals(getDocument())) {
                    if (fAudioPlayer == null && transcript.getAudioFile() != null) {
                        hookupNewAudioFile(transcript);
                    } else if (fAudioPlayer != null && ((Transcript) getDocument()).getAudioFile()
                            .getPersistenceId() != transcript.getAudioFile().getPersistenceId()) {
                        Transcript editorTranscript = (Transcript) getDocument();
                        editorTranscript.setAudioFile(transcript.getAudioFile());
                        if (transcript.getAudioFile() == null) {
                            setEnable(fTopBar, false);
                            fAudioPlayer = null;
                            setLength(0);
                        } else {
                            IProject project = ResourcesPlugin.getWorkspace().getRoot()
                                    .getProject(editorTranscript.getProject().getFolderName());
                            String audioFile = project.getLocation() + File.separator
                                    + editorTranscript.getAudioFile().getRelativePath();
                            fAudioPlayer.open(audioFile);
                        }

                    }
                }
            }
        }
    }

    /**
     * @param transcript
     */
    private void hookupNewAudioFile(Transcript transcript) {
        setEnable(fTopBar, true);
        Transcript editorTranscript = (Transcript) getDocument();
        editorTranscript.setAudioFile(transcript.getAudioFile());
        IProject project = ResourcesPlugin.getWorkspace().getRoot()
                .getProject(editorTranscript.getProject().getFolderName());
        String audioFile = project.getLocation() + File.separator
                + editorTranscript.getAudioFile().getRelativePath();
        fAudioPlayer = new AudioPlayer(audioFile, this);
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor#dispose()
     */
    @Override
    public void dispose() {
        Facade.getInstance().getListenerManager().unregisterTranscriptListener(getDocument().getProject(), this);
        if (fAudioPlayer != null) {
            fAudioPlayer.close();
        }

        super.dispose();
    }

    /**
     * Called by the AudioPlayer to set the length of the audio file.
     * @param length
     */
    protected void setLength(final double length) {
        fAudioLength = (int) length;
        fAudioSlider.setMaximum(fAudioLength);
        String label = "/ " + getTimeString(fAudioLength); //$NON-NLS-1$
        fLengthLabel.setText(label);
    }

    /**
     * Converts seconds into MM:SS.
     * @param seconds
     * @return
     */
    private String getTimeString(int seconds) {
        int minutes = seconds / SECONDS_PER_MINUTE;
        int secondsRemaining = seconds % SECONDS_PER_MINUTE;
        String secs = (secondsRemaining < TEN) ? "0" + secondsRemaining : "" + secondsRemaining; //$NON-NLS-1$ //$NON-NLS-2$
        return minutes + ":" + secs; //$NON-NLS-1$
    }

    /**
     * Called by the AudioPlayer to set the current time of the audio stream.
     * @param seconds
     */
    protected void setSeconds(final int seconds) {
        Runnable run = new Runnable() {

            @Override
            public void run() {
                String label = getTimeString(seconds);
                fTimeLabel.setText(label);
                fAudioSlider.setSelection(seconds);
            }
        };

        Display.getDefault().asyncExec(run);
    }

    private void openErrorDialog(String errorMessage) {
        MessageDialog.openError(getSite().getShell(), Messages.getString("editors.TranscriptEditor.audioError"), //$NON-NLS-1$
                errorMessage);
    }

    /**
     * Allows access to the seek back command so that the key binding works.
     */
    public void seekBack() {
        if (fAudioPlayer != null) {
            int selection = fAudioSlider.getSelection();
            selection -= fSeekTime + 2;
            fAudioPlayer.jumpToTime(selection >= 0 ? selection : 0);
        }
    }

    /**
     * Allows access to the seek forward command so that the key binding works.
     */
    public void seekForward() {
        if (fAudioPlayer != null) {
            int selection = fAudioSlider.getSelection();
            selection += fSeekTime;
            fAudioPlayer.jumpToTime(selection <= fAudioLength ? selection : fAudioLength);
        }
    }

    /**
     * The selection adapter for the play button.
     *
     */
    private class PlayPushedAdapter extends SelectionAdapter {
        private final Image fPLAY = getImage(PLAY_IMG, QualyzerActivator.PLUGIN_ID);
        private final Image fPAUSE = getImage(PAUSE_IMG, QualyzerActivator.PLUGIN_ID);
        private boolean fPlaying = false;

        @Override
        public void widgetSelected(SelectionEvent e) {
            try {
                if (fPlaying) {
                    fAudioPlayer.pause();
                    //Handle slight delay.
                    fAudioPlayer.jumpToTime(fAudioSlider.getSelection() - 1);
                    fPlayButton.setImage(fPLAY);
                    fPlaying = false;
                } else {
                    fAudioPlayer.play();
                    fPlayButton.setImage(fPAUSE);
                    fPlaying = true;
                }
            } catch (QualyzerException ex) {
                openErrorDialog(ex.getMessage());
            }
        }

        public void stop() {
            fPlaying = false;
        }
    }

    /* (non-Javadoc)
     * @see net.sf.colorer.eclipse.editors.ColorerEditor#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
     */
    @Override
    public void propertyChange(PropertyChangeEvent e) {
        if (e == null || e.getProperty().equals(IQualyzerPreferenceConstants.SEEK_TIME)) {
            fSeekTime = QualyzerActivator.getDefault().getPreferenceStore()
                    .getInt(IQualyzerPreferenceConstants.SEEK_TIME);
        } else {
            super.propertyChange(e);
        }
    }

    /**
     * Create a timestamp based on where the action was called from.
     */
    public void addTimeStamp() {
        if (fAudioPlayer != null) {
            IFile file = ((RTFEditorInput) getEditorInput()).getFile();

            int line;
            if (fInRuler) {
                line = getVerticalRuler().getLineOfLastMouseButtonActivity() + 1;
            } else {
                String pos = getCursorPosition();
                line = Integer.parseInt(pos.split(" : ")[0]); //$NON-NLS-1$
            }

            if (!markerExists(line)) {
                try {
                    Map<String, Object> map = new HashMap<String, Object>();
                    MarkerUtilities.setLineNumber(map, line);
                    MarkerUtilities.setMessage(map, getTimeString(fAudioSlider.getSelection()));
                    map.put("time", fAudioSlider.getSelection()); //$NON-NLS-1$
                    MarkerUtilities.createMarker(file, map, "ca.mcgill.cs.swevo.qualyzer.marker.timestamp"); //$NON-NLS-1$

                    Facade.getInstance().createTimestamp((Transcript) getDocument(), line,
                            fAudioSlider.getSelection());
                    Facade.getInstance().saveDocument(getDocument());
                } catch (CoreException e) {
                    e.printStackTrace();
                }
            }

            fInRuler = false;
        }
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor#editorContextMenuAboutToShow(
     * org.eclipse.jface.action.IMenuManager)
     */
    @Override
    protected void editorContextMenuAboutToShow(IMenuManager menu) {
        fInRuler = false;

        super.editorContextMenuAboutToShow(menu);

        int line = Integer.parseInt(getCursorPosition().split(" : ")[0]); //$NON-NLS-1$

        if (fAudioPlayer != null && !markerExists(line)) {
            addAction(menu, RTFConstants.ADD_TIMESTAMP_ACTION_ID);
        }
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor#createActions()
     */
    @Override
    protected void createActions() {
        super.createActions();

        createTimeStampAction();
        createRemoveTimeStampAction();

        setAction(RTFConstants.ADD_TIMESTAMP_ACTION_ID, fAddTimeStampAction);
        setAction(RTFConstants.REMOVE_TIMESTAMP_ACTION_ID, fRemoveTimeStampAction);
    }

    /**
     * 
     */
    private void createRemoveTimeStampAction() {
        fRemoveTimeStampAction = new Action(Messages.getString("editors.TranscriptEditor.removeTimeStamp")) //$NON-NLS-1$
        {

            @Override
            public void run() {
                int line = getVerticalRuler().getLineOfLastMouseButtonActivity();
                try {
                    for (IMarker marker : ((RTFEditorInput) getEditorInput()).getFile()
                            .findMarkers(RTFConstants.TIMESTAMP_MARKER_ID, false, 0)) {
                        if (marker.exists() && line + 1 == marker.getAttribute(IMarker.LINE_NUMBER, 0)) {
                            marker.delete();
                            Timestamp timestamp = ((Transcript) getDocument()).getTimestamps().get(line + 1);
                            Facade.getInstance().deleteTimestamp(timestamp);
                            break;
                        } else if (!marker.exists()) {
                            marker.delete();
                        }
                    }
                } catch (CoreException e) {
                    gLogger.error(UNABLE_TO_ACCESS_MARKER, e); //$NON-NLS-1$
                }
            }
        };
    }

    /**
     * Seeks to the specified time. For use by the SelectTimestampAction.
     * @param seconds
     */
    public void seekToTime(int seconds) {
        if (fAudioPlayer != null) {
            if (seconds < 0) {
                fAudioPlayer.jumpToTime(0);
            } else if (seconds > fAudioLength) {
                fAudioPlayer.jumpToTime(fAudioLength);
            } else {
                fAudioPlayer.jumpToTime(seconds);
            }
        }
    }

    /**
     * Moves the cursor to the beginning of the line.
     * @param line 0 relative line number.
     */
    public void moveCursorToLine(int line) {
        int offset = getSourceViewer().getTextWidget().getOffsetAtLine(line);
        getSourceViewer().setSelectedRange(offset, 0);
    }

    /* (non-Javadoc)
     * @see ca.mcgill.cs.swevo.qualyzer.editors.RTFEditor#rulerContextMenuAboutToShow(
     * org.eclipse.jface.action.IMenuManager)
     */
    @Override
    protected void rulerContextMenuAboutToShow(IMenuManager menu) {
        fInRuler = true;

        if (fAudioPlayer != null) {
            int line = getVerticalRuler().getLineOfLastMouseButtonActivity() + 1;

            if (markerExists(line)) {
                addAction(menu, RTFConstants.REMOVE_TIMESTAMP_ACTION_ID);
            } else {
                addAction(menu, RTFConstants.ADD_TIMESTAMP_ACTION_ID);
            }
        }
    }

    private boolean markerExists(int line) {

        Transcript transcript = (Transcript) getDocument();

        return transcript.getTimestamps().get(line) != null;
    }

    /**
     * Notify the editor that you aren't in the ruler.
     */
    public void notInRuler() {
        fInRuler = false;
    }
}