// This file is part of Penn TotalRecall <http://memory.psych.upenn.edu/TotalRecall>. // // TotalRecall is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 3 only. // // TotalRecall is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with TotalRecall. If not, see <http://www.gnu.org/licenses/>. package behaviors.multiact; import info.Constants; import java.awt.event.ActionEvent; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import util.GiveMessage; import util.OSPath; import behaviors.UpdatingAction; import behaviors.singleact.DeleteSelectedAnnotationAction; import components.MyFrame; import components.MyMenu; import components.annotations.Annotation; import components.annotations.AnnotationDisplay; import components.annotations.AnnotationFileParser; import components.waveform.WaveformDisplay; import components.wordpool.WordpoolDisplay; import components.wordpool.WordpoolWord; import control.CurAudio; import edu.upenn.psych.memory.precisionplayer.PrecisionPlayer; /** * Commits a user's annotation, updating the annotation file and program window as appropriate. * * @author Yuvi Masory */ public class AnnotateAction extends IdentifiedMultiAction { public static enum Mode {INTRUSION, REGULAR}; private Mode mode; /** * Create an <code>Action</code> corresponding to an intrusion or a normal annotation. * * @param isIntrusion Whether the annotations committed by this <code>Action</code> are intrusions */ public AnnotateAction(Mode mode) { super(mode); this.mode = mode; } private static String obfuscate(String in) { byte[] inb = in.getBytes(); StringBuffer buff = new StringBuffer(); for(byte b: inb) { buff.append(b + " "); } return buff.toString(); } public static File getOutputFile() { String curFileName = CurAudio.getCurrentAudioFileAbsolutePath(); File oFile = new File(OSPath.basename(curFileName) + "." + Constants.temporaryAnnotationFileExtension); return oFile; } /** * Performs the <code>AnnotationAction</code> by appending the word in the text field to the temporary annotations file. * * @param e The <code>ActionEvent</code> provided by the trigger. */ @Override public void actionPerformed(ActionEvent e) { super.actionPerformed(e); //do nothing if no audio file is open if(CurAudio.audioOpen() == false) { WordpoolDisplay.clearText(); return; } //retrieve time associated with annotation double time = CurAudio.getMaster().framesToMillis(CurAudio.getAudioProgress()); //retrieve text associated with annotation, possibly the intrusion string String text = WordpoolDisplay.getFieldText(); if(text.length() == 0) { if(mode == Mode.INTRUSION) { text = Constants.intrusionSoundString; } else { return; } } //find whether the text matches a wordpool entry, so we can find the wordpool number of the annotation text WordpoolWord match = WordpoolDisplay.findMatchingWordpooWord(text); if(match == null) { if(mode == Mode.REGULAR) { //words not from the wordpool must be marked as intrusions return; } match = new WordpoolWord(text, -1); } //append the new annotation to the end of the temporary annotation file File oFile = getOutputFile(); if(oFile.exists() == false) { try { oFile.createNewFile(); } catch (IOException e1) { e1.printStackTrace(); GiveMessage.errorMessage("Could not create " + Constants.temporaryAnnotationFileExtension + " file."); } } if(oFile.exists()) { //check for header try { if(AnnotationFileParser.headerExists(oFile) == false) { String annotatorName = MyMenu.getAnnotator(); if(annotatorName == null) { annotatorName = GiveMessage.inputMessage("Please enter your name:"); if(annotatorName == null || annotatorName.equals("")) { GiveMessage.errorMessage("Cannot commit annotation without name."); return; } } MyMenu.setAnnotator(annotatorName); AnnotationFileParser.prependHeader(oFile, annotatorName); } Annotation ann = new Annotation(time, match.getNum(), match.getText()); //check if we are annotating the same position as an existing annotation, if so delete new DeleteSelectedAnnotationAction().actionPerformed( new ActionEvent(WordpoolDisplay.getInstance(), ActionEvent.ACTION_PERFORMED, null, System.currentTimeMillis(), 0)); WaveformDisplay.getInstance().repaint(); //file may no longer exist after deletion if(oFile.exists() == false) { if(oFile.createNewFile()) { String annotatorName = GiveMessage.inputMessage("Please enter your name:"); if(annotatorName == null || annotatorName.equals("")) { GiveMessage.errorMessage("Cannot commit annotation without name."); return; } if(AnnotationFileParser.headerExists(oFile) == false) { AnnotationFileParser.prependHeader(oFile, annotatorName); } } else { throw new IOException("Could not re-create file."); } } //add a new annotation object, and clear the field AnnotationFileParser.appendAnnotation(ann, oFile); AnnotationDisplay.addAnnotation(ann); WordpoolDisplay.clearText(); } catch (IOException e1) { e1.printStackTrace(); GiveMessage.errorMessage("Error comitting annotation! Check files for damage."); } } //return focus to the frame after annotation, for the sake of action key bindings MyFrame.getInstance().requestFocusInWindow(); MyMenu.updateActions(); } public static void writeSpans() { if(UpdatingAction.getStamps().size() > 0) { ArrayList<ArrayList<Long>> spans = new ArrayList<ArrayList<Long>>(); Long[] stamps = UpdatingAction.getStamps().toArray(new Long[] {}); Arrays.sort(stamps); long start = 0L; long end = 0L; for(long stamp: stamps) { if(stamp - end > Constants.timeout) { if(start > 0 && end > start) { ArrayList<Long> nSpan = new ArrayList<Long>(); nSpan.add(start); nSpan.add(end); spans.add(nSpan); } start = stamp; end = stamp; } else { end = stamp; } } if(start > 0 && end > start) { ArrayList<Long> nSpan = new ArrayList<Long>(); nSpan.add(start); nSpan.add(end); spans.add(nSpan); } UpdatingAction.getStamps().clear(); for(ArrayList<Long> span: spans) { String toWrite = "Span: " + span.get(0) + "-" + span.get(1); try { AnnotationFileParser.addField(getOutputFile(), obfuscate(toWrite)); } catch (IOException e) { e.printStackTrace(); } } } } /** * <code>AnnotateActions</code> are enabled anytime audio is open and not playing. */ @Override public void update() { if(CurAudio.audioOpen()) { if(CurAudio.getPlayer().getStatus() == PrecisionPlayer.Status.PLAYING) { setEnabled(false); } else { setEnabled(true); } } else { setEnabled(false); WordpoolDisplay.clearText(); } } }