package edu.cmu.minorthird.text.gui; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.text.JTextComponent; import javax.swing.text.Keymap; import javax.swing.text.SimpleAttributeSet; import edu.cmu.minorthird.text.MutableTextLabels; import edu.cmu.minorthird.text.Span; import edu.cmu.minorthird.text.TextLabels; import edu.cmu.minorthird.util.gui.ComponentViewer; import edu.cmu.minorthird.util.gui.SmartVanillaViewer; import edu.cmu.minorthird.util.gui.Viewer; import edu.cmu.minorthird.util.gui.ViewerFrame; /** Interactivly edit the subspans associated with a particular * document span. * * @author William Cohen */ public class SpanEditor extends ViewerTracker{ static final long serialVersionUID=200803014L; public static final String EDITOR_PROP="_edited"; // internal state private String importType,exportType; private JLabel ioTypeLabel; private SortedSet<Span> editedSpans; private int editSpanCursor=-1; // indicates nothing selected private boolean readOnly=false; // buttons JButton readOnlyButton= new JButton(new ReadOnlyButton(readOnly?"Edit":"Read")); JButton importButton=new JButton(new ImportGuessSpans("Import")); JButton exportButton=new JButton(new ExportGuessSpans("Export")); JButton addButton=new JButton(new AddSelection("Add")); JButton deleteButton=new JButton(new DeleteCursoredSpan("Delete")); JButton propButton=new JButton(new EditSpanProperties("Props")); JButton prevButton=new JButton(new MoveSpanCursor("Prev",-1)); JButton nextButton=new JButton(new MoveSpanCursor("Next",+1)); private List<JButton> buttonsThatChangeStuff=new ArrayList<JButton>(); /** * @param viewLabels a superset of editLabels which may include some additional read-only information * @param editLabels the labels being modified * @param documentList the document Span being edited is associated with * the selected entry of the documentList. * @param spanPainter used to repaint documentList elements * @param statusMsg a JLabel used for status messages. */ public SpanEditor(TextLabels viewLabels,MutableTextLabels editLabels, JList documentList,SpanPainter spanPainter,StatusMessage statusMsg){ super(viewLabels,editLabels,documentList,spanPainter,statusMsg); init(); } private void init(){ this.importType=this.exportType=null; this.ioTypeLabel=new JLabel("Types: [None/None]"); initLayout(); loadSpan(nullSpan()); } private void initLayout(){ // // layout stuff // setLayout(new GridBagLayout()); GridBagConstraints gbc; int col=0; gbc=new GridBagConstraints(); //------------ ioType button ------------------// gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(ioTypeLabel,gbc); //------------ read only button ------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(readOnlyButton,gbc); //------------ import button ------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; buttonsThatChangeStuff.add(importButton); add(importButton,gbc); //------------ export button ------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; buttonsThatChangeStuff.add(exportButton); add(exportButton,gbc); //------------ add button -------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; buttonsThatChangeStuff.add(addButton); add(addButton,gbc); //-------------- delete button --------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; buttonsThatChangeStuff.add(deleteButton); add(deleteButton,gbc); //-------------- delete button --------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; buttonsThatChangeStuff.add(propButton); add(propButton,gbc); //------------ prev button ---------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(prevButton,gbc); //-------------- next button --------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(nextButton,gbc); //------------- up button -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(upButton,gbc); //------------- down button ------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(downButton,gbc); //-------------- context slider -----------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(contextWidthSlider,gbc); //----------- save button -------------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.weightx=0.5; gbc.weighty=0.0; gbc.gridx=++col; gbc.gridy=2; add(saveButton,gbc); buttonsThatChangeStuff.add(saveButton); //System.out.println("create saveButton: saveAsFile=" + saveAsFile + " enabled: " + (saveAsFile != null)); saveButton.setEnabled(saveAsFile!=null); //------------- editorHolder ---------------// gbc=new GridBagConstraints(); gbc.fill=GridBagConstraints.BOTH; gbc.weightx=1.0; gbc.weighty=1.0; gbc.gridx=1; gbc.gridy=1; gbc.gridwidth=col; add(editorHolder,gbc); } /** Set mode to read-only or not. In read-only mode, the * document viewed has the same highlighting as in the * documentList. In write mode, the "truth" spans * are shown, and the "guess" spans are imported. */ public void setReadOnly(boolean readOnly){ for(Iterator<JButton> i=buttonsThatChangeStuff.iterator();i.hasNext();){ JButton button=i.next(); button.setEnabled(readOnly?false:true); } this.readOnly=readOnly; } /** Declare which types are being edited. */ public void setTypesBeingEdited(String inType,String outType){ this.importType=inType; this.exportType=outType; ioTypeLabel.setText("Edit: "+importType+"/"+exportType); } @Override protected void loadSpanHook(){ if(readOnly&&!DUMMY_ID.equals(documentSpan.getDocumentId())){ importDocumentListMarkup(documentSpan.getDocumentId()); } Keymap keymap=JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control I"), importButton.getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control E"), exportButton.getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control S"), exportButton.getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control A"),addButton .getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("DELETE"),deleteButton .getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control D"), deleteButton.getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("LEFT"),prevButton .getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control B"),prevButton .getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("RIGHT"),nextButton .getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("control F"),nextButton .getAction()); keymap.addActionForKeyStroke(KeyStroke.getKeyStroke("TAB"),nextButton .getAction()); editedSpans=new TreeSet<Span>(); } /** Toggles readOnly status */ private class ReadOnlyButton extends AbstractAction{ static final long serialVersionUID=200803014L; public ReadOnlyButton(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ setReadOnly(!readOnly); if(documentSpan!=null) loadSpan(documentSpan); readOnlyButton.setText(readOnly?"Edit":"Read"); } } /** Imports the spans associated with the documentSpan into * the set currently being edited */ private class ImportGuessSpans extends AbstractAction{ static final long serialVersionUID=200803014L; public ImportGuessSpans(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ if(importType==null){ statusMsg.display("what type?"); return; } editedDoc.resetHighlights(); editedSpans=new TreeSet<Span>(); for(Iterator<Span> i= viewLabels.instanceIterator(importType,documentSpan.getDocumentId());i .hasNext();){ Span guessSpan=i.next(); editedDoc.highlight(guessSpan,HiliteColors.yellow); editedSpans.add(guessSpan); } editSpanCursor=-1; statusMsg.display("imported "+editedSpans.size()+" "+importType+ " spans to "+documentSpan.getDocumentId()); } } /** Exports the spans associated with the documentSpan into * the set currently being edited */ private class ExportGuessSpans extends AbstractAction{ static final long serialVersionUID=200803014L; public ExportGuessSpans(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ if(exportType==null){ statusMsg.display("what type?"); return; } Iterator<Span> newSpans=editedSpans.iterator(); editLabels.defineTypeInside(exportType,documentSpan,newSpans); //editLabels.setProperty(documentSpan.documentSpan(), EDITOR_PROP, "t"); spanPainter.paintDocument(documentSpan.getDocumentId()); editSpanCursor=-1; statusMsg.display("exported "+editedSpans.size()+" "+exportType+" spans"); } } /** Add span associated with selected text. */ private class AddSelection extends AbstractAction{ static final long serialVersionUID=200803014L; public AddSelection(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ int lo=editorPane.getSelectionStart(); int hi=editorPane.getSelectionEnd(); // compensate for the fact that the document being viewed // doesn't start at char position zero lo=editedDoc.toLogicalCharIndex(lo); hi=editedDoc.toLogicalCharIndex(hi); Span span=documentSpan.charIndexSubSpan(lo,hi); // int spanLo=-1,spanHi=-1; // if(span.size()>0){ // spanLo=span.getTextToken(0).getLo(); // spanHi=span.getTextToken(span.size()-1).getHi(); // } //System.out.println("spanSize="+span.size()+" lo="+lo+" hi="+hi+" spanLo="+spanLo+" spanHi="+spanHi); // figure out if we need to move the selected span int correction=0; if(editSpanCursor>=0&&(span.compareTo(getEditSpan(editSpanCursor))<0)){ correction=1; } editedDoc.highlight(span,HiliteColors.yellow); editedSpans.add(span); editSpanCursor+=correction; statusMsg.display("adding "+span); } } /** Delete the span under the cursor. */ private class DeleteCursoredSpan extends AbstractAction{ static final long serialVersionUID=200803014L; public DeleteCursoredSpan(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ Span span=null; if(editSpanCursor>=0){ span=getEditSpan(editSpanCursor); }else{ int lo=editorPane.getSelectionStart(); int hi=editorPane.getSelectionEnd(); span=documentSpan.charIndexSubSpan(lo,hi); } editedDoc.highlight(span,SimpleAttributeSet.EMPTY); editedSpans.remove(span); if(editSpanCursor>=editedSpans.size()){ editSpanCursor=-1; }else if(editSpanCursor>=0){ // highlight next span after the deleted one editedDoc.highlight(getEditSpan(editSpanCursor), HiliteColors.cursorColor); } } } /** Change the properties of the span. */ private class EditSpanProperties extends AbstractAction{ static final long serialVersionUID=200803014L; public EditSpanProperties(String msg){ super(msg); } @Override public void actionPerformed(ActionEvent event){ Span span=null; if(editSpanCursor>=0){ span=getEditSpan(editSpanCursor); }else{ int lo=editorPane.getSelectionStart(); int hi=editorPane.getSelectionEnd(); span=documentSpan.charIndexSubSpan(lo,hi); } Viewer viewer=new SpanPropertyViewer(); viewer.setContent(span); new ViewerFrame("Span to edit",viewer); } } private class SpanPropertyViewer extends ComponentViewer{ static final long serialVersionUID=200803014L; @Override public JComponent componentFor(Object o){ final Span span=(Span)o; final JTabbedPane pane=new JTabbedPane(); final JTextField propField=new JTextField(10); final JTextField valField=new JTextField(10); final JTable table=makePropertyTable(span); final JScrollPane tableScroller=new JScrollPane(table); final JButton addButton= new JButton(new AbstractAction("Insert Property"){ static final long serialVersionUID=200803014L; @Override public void actionPerformed(ActionEvent event){ editLabels.setProperty(span,propField.getText(),valField .getText()); tableScroller.getViewport().setView(makePropertyTable(span)); tableScroller.revalidate(); pane.revalidate(); } }); GridBagConstraints gbc=fillerGBC(); //gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridwidth=3; final JPanel subpanel=new JPanel(); subpanel.setLayout(new GridBagLayout()); subpanel.add(tableScroller,gbc); subpanel.add(addButton,myGBC(0)); subpanel.add(propField,myGBC(1)); subpanel.add(valField,myGBC(2)); pane.add("Properties",subpanel); pane.add("Span",new SmartVanillaViewer(span)); return pane; } private GridBagConstraints myGBC(int col){ GridBagConstraints gbc=fillerGBC(); gbc.fill=GridBagConstraints.HORIZONTAL; gbc.gridx=col; gbc.gridy=1; return gbc; } private JTable makePropertyTable(final Span span){ //System.out.println("editLabels="+editLabels); //System.out.println("spanProps="+editLabels.getSpanProperties()); Object[] spanProps=editLabels.getSpanProperties().toArray(); Object[][] table=new Object[spanProps.length][2]; for(int i=0;i<spanProps.length;i++){ table[i][0]=spanProps[i]; table[i][1]=editLabels.getProperty(span,(String)spanProps[i]); } String[] colNames=new String[]{"Property","Property's Value"}; return new JTable(table,colNames); } } /** Move through list of spans */ private class MoveSpanCursor extends AbstractAction{ static final long serialVersionUID=200803014L; private int delta; public MoveSpanCursor(String msg,int delta){ super(msg); this.delta=delta; } @Override public void actionPerformed(ActionEvent event){ if(editedSpans==null||editedSpans.isEmpty()) return; if(editSpanCursor>=0){ editedDoc.highlight(getEditSpan(editSpanCursor),HiliteColors.yellow); editSpanCursor=editSpanCursor+delta; // wrap around if(editSpanCursor<0) editSpanCursor+=editedSpans.size(); else if(editSpanCursor>=editedSpans.size()) editSpanCursor-=editedSpans.size(); }else{ // move to first legit span editSpanCursor=0; } editedDoc.highlight(getEditSpan(editSpanCursor),HiliteColors.cursorColor); statusMsg.display("to span#"+editSpanCursor+": "+ getEditSpan(editSpanCursor)); } } private Span getEditSpan(int k){ for(Iterator<Span> i=editedSpans.iterator();i.hasNext();){ Span s=i.next(); if(k--==0) return s; } throw new IllegalStateException("bad editedSpan index "+k); } }