/*
 *  Copyright (c) 1995-2012, The University of Sheffield. See the file
 *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
 *
 *  This file is part of GATE (see http://gate.ac.uk/), and is free
 *  software, licenced under the GNU Library General Public License,
 *  Version 2, June 1991 (in the distribution as file licence.html,
 *  and also available at http://gate.ac.uk/gate/licence.html).
 *
 *  Valentin Tablan 12/07/2001
 *
 *  $Id: CorpusEditor.java 19641 2016-10-06 07:24:25Z markagreenwood $
 *
 */
package gate.gui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*;
import java.util.*;
import java.io.IOException;

import javax.swing.*;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.table.*;

import gate.*;
import gate.creole.AbstractVisualResource;
import gate.event.CorpusEvent;
import gate.creole.metadata.CreoleResource;
import gate.creole.metadata.GuiType;
import gate.event.CorpusListener;
import gate.event.CreoleListener;
import gate.event.CreoleEvent;
import gate.swing.XJTable;
import gate.swing.XJPopupMenu;
import gate.util.GateException;
import gate.util.GateRuntimeException;

/**
 * A simple viewer/editor for corpora. It will allow the visualisation of the
 * list of documents inside a corpus along with their features.
 * It will also allow addition and removal of documents.
 */
@SuppressWarnings("serial")
@CreoleResource(name = "Corpus editor", guiType = GuiType.LARGE,
    resourceDisplayed = "gate.Corpus", mainViewer = true)
public class CorpusEditor extends AbstractVisualResource
  implements CorpusListener {

  @Override
  public Resource init(){
    initLocalData();
    initGuiComponents();
    initListeners();
    return this;
  }


  protected void initLocalData(){
    docTableModel = new DocumentTableModel();
    try {
      documentsLoadedCount = Gate.getCreoleRegister()
        .getAllInstances("gate.Document").size();
    } catch (GateException exception) {
      exception.printStackTrace();
    }
  }

  protected void initGuiComponents(){
    setLayout(new BorderLayout());
    renderer = new DocumentNameRenderer();
    
    docTable = new XJTable(docTableModel);
    docTable.setSortable(true);
    docTable.setSortedColumn(DocumentTableModel.COL_INDEX);
    docTable.setAutoResizeMode(XJTable.AUTO_RESIZE_LAST_COLUMN);
    docTable.getColumnModel().getColumn(DocumentTableModel.COL_NAME).
        setCellRenderer(renderer);
    docTable.setDragEnabled(true);
    docTable.setTransferHandler(new TransferHandler() {
      // drag and drop to move up and down the table rows
      // import selected documents from the resources tree
      String source = "";
      @Override
      public int getSourceActions(JComponent c) {
        return MOVE;
      }
      @Override
      protected Transferable createTransferable(JComponent c) {
        int selectedRows[] = docTable.getSelectedRows();
        Arrays.sort(selectedRows);
        return new StringSelection("CorpusEditor"
          + Arrays.toString(selectedRows));
      }
      @Override
      protected void exportDone(JComponent c, Transferable data, int action) {
      }
      @Override
      public boolean canImport(JComponent c, DataFlavor[] flavors) {
        for(DataFlavor flavor : flavors) {
          if(DataFlavor.stringFlavor.equals(flavor)) {
            return true;
          }
        }
        return false;
      }
      @Override
      public boolean importData(JComponent c, Transferable t) {
        if (!canImport(c, t.getTransferDataFlavors())) {
          return false;
        }
        try {
          source = (String)t.getTransferData(DataFlavor.stringFlavor);
          if (source.startsWith("ResourcesTree")) {
            int insertion = docTable.getSelectedRow();
            List<Document> documents = new ArrayList<Document>();
            source = source.replaceFirst("^ResourcesTree\\[", "");
            source = source.replaceFirst("\\]$", "");
            final String documentsNames[] = source.split(", ");
            List<Resource> loadedDocuments;
            try {
              loadedDocuments =
                Gate.getCreoleRegister().getAllInstances("gate.Document");
            } catch(GateException e) {
              e.printStackTrace();
              return false;
            }
            // get the list of documents selected when dragging started
            for(String documentName : documentsNames) {
              for (Resource loadedDocument : loadedDocuments) {
                if (loadedDocument.getName().equals(documentName)
                 && !corpus.contains(loadedDocument)) {
                  documents.add((Document) loadedDocument);
                }
              }
            }
            // add the documents at the insertion point
            for (Document document : documents) {
              if (insertion != -1) {
                corpus.add(docTable.rowViewToModel(insertion), document);
                if (insertion == docTable.getRowCount()) { insertion++; }
              } else {
                corpus.add(document);
              }
            }
            // select the moved/already existing documents
            SwingUtilities.invokeLater(new Runnable() {
              @Override
              public void run() {
                docTable.clearSelection();
                for (String documentName : documentsNames) {
                  for (int row = 0; row < docTable.getRowCount(); row++) {
                    if (docTable.getValueAt(
                          row, docTable.convertColumnIndexToView(1))
                        .equals(documentName)) {
                      docTable.addRowSelectionInterval(row, row);
                    }
                  }
                }
              }
            });
            changeMessage();
            return true;

          } else if (source.startsWith("CorpusEditor")) {
            int insertion = docTable.getSelectedRow();
            int initialInsertion = insertion;
            List<Document> documents = new ArrayList<Document>();
            source = source.replaceFirst("^CorpusEditor\\[", "");
            source = source.replaceFirst("\\]$", "");
            String selectedRows[] = source.split(", ");
            if (Integer.parseInt(selectedRows[0]) < insertion) { insertion++; }
            // get the list of documents selected when dragging started
            for(String row : selectedRows) {
              if (Integer.parseInt(row) == initialInsertion) {
                // the user dragged the selected rows on themselves, do nothing
                return false;
              }
              documents.add(corpus.get(
                docTable.rowViewToModel(Integer.parseInt(row))));
              if (Integer.parseInt(row) < initialInsertion) { insertion--; }
            }
            // remove the documents selected when dragging started
            for(Document document : documents) {
              corpus.remove(document);
            }
            // add the documents at the insertion point
            for (Document document : documents) {
              corpus.add(docTable.rowViewToModel(insertion), document);
              insertion++;
            }
            // select the moved documents
            docTable.addRowSelectionInterval(
              insertion - selectedRows.length, insertion - 1);
            return true;

          } else {
            return false;
          }

        } catch (UnsupportedFlavorException ufe) {
          return false;
        } catch (IOException ioe) {
          return false;
        }
      }
    });

    JScrollPane scroller = new JScrollPane(docTable);
    scroller.setHorizontalScrollBarPolicy(
            JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    scroller.getViewport().setBackground(docTable.getBackground());
    add(scroller, BorderLayout.CENTER);

    toolbar = new JToolBar();
    toolbar.setFloatable(false);
    toolbar.add(newDocumentAction = new NewDocumentAction());
    toolbar.add(removeDocumentsAction = new RemoveDocumentsAction());
    toolbar.addSeparator();
    toolbar.add(moveUpAction = new MoveUpAction());
    toolbar.add(moveDownAction = new MoveDownAction());
    toolbar.addSeparator();
    toolbar.add(openDocumentsAction = new OpenDocumentsAction());

    removeDocumentsAction.setEnabled(false);
    moveUpAction.setEnabled(false);
    moveDownAction.setEnabled(false);
    openDocumentsAction.setEnabled(false);

    JPanel topPanel = new JPanel(new BorderLayout());
    topPanel.add(toolbar, BorderLayout.NORTH);

    messageLabel = new JLabel();
    changeMessage();
    topPanel.add(messageLabel, BorderLayout.SOUTH);

    add(topPanel, BorderLayout.NORTH);
  }

  protected void initListeners(){

    // mouse double-click to open the document
    // context menu to get the actions for the selection
    docTable.addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(MouseEvent e) {
        processMouseEvent(e);
      }
      @Override
      public void mousePressed(MouseEvent e) {
        if(e.isPopupTrigger()) { processMouseEvent(e); }
      }
      @Override
      public void mouseReleased(MouseEvent e) {
        if(e.isPopupTrigger()) { processMouseEvent(e); }
      }
      private void processMouseEvent(MouseEvent e) {
        int row = docTable.rowAtPoint(e.getPoint());
        if(row == -1) { return; }

        if(e.isPopupTrigger()) {
          // context menu
          if(!docTable.isRowSelected(row)) {
            // if right click outside the selection then reset selection
            docTable.getSelectionModel().setSelectionInterval(row, row);
          }
          JPopupMenu popup = new XJPopupMenu();
          popup.add(openDocumentsAction);
          popup.add(removeDocumentsAction);
          popup.show(docTable, e.getPoint().x, e.getPoint().y);

        } else if(e.getID() == MouseEvent.MOUSE_CLICKED
               && e.getClickCount() == 2) {
          // open document on double-click
          openDocumentsAction.actionPerformed(null);
        }
      }
    });

    // Enter key opens the selected documents
    docTable.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_ENTER) {
          openDocumentsAction.actionPerformed(null);
        }
      }
    });

    docTable.getSelectionModel().addListSelectionListener(
      new ListSelectionListener() {
        @Override
        public void valueChanged(ListSelectionEvent e) {
          // enable/disable buttons according to the selection
          removeDocumentsAction.setEnabled(docTable.getSelectedRowCount() > 0);
          openDocumentsAction.setEnabled(docTable.getSelectedRowCount() > 0);
          moveUpAction.setEnabled(docTable.getSelectedRowCount() > 0
            && !docTable.isRowSelected(0));
          moveDownAction.setEnabled(docTable.getSelectedRowCount() > 0
            && !docTable.isRowSelected(docTable.getRowCount() - 1));
        }
      });

    Gate.getCreoleRegister().addCreoleListener(new CreoleListener() {
      @Override
      public void resourceLoaded(CreoleEvent e) {
        if (e.getResource() instanceof Document) {
          documentsLoadedCount++;
          changeMessage();
        }
      }
      @Override
      public void resourceUnloaded(CreoleEvent e) {
        if (e.getResource() instanceof Document) {
          documentsLoadedCount--;
          changeMessage();
        }
      }
      @Override
      public void datastoreOpened(CreoleEvent e) { /* do nothing */ }
      @Override
      public void datastoreCreated(CreoleEvent e) { /* do nothing */ }
      @Override
      public void datastoreClosed(CreoleEvent e) { /* do nothing */ }
      @Override
      public void resourceRenamed(Resource resource, String oldName,
                                  String newName) { /* do nothing */ }
    });
  }

  @Override
  public void cleanup(){
    super.cleanup();
    corpus = null;
  }

  @Override
  public void setTarget(Object target){
    if(corpus != null && corpus != target){
      //we already had a different corpus
      corpus.removeCorpusListener(this);
    }
    if(!(target instanceof Corpus)){
      throw new IllegalArgumentException(
        "The GATE corpus editor can only be used with a GATE corpus!\n" +
        target.getClass().toString() + " is not a GATE corpus!");
    }
    this.corpus = (Corpus)target;
    corpus.addCorpusListener(this);
    docTableModel.dataChanged();
    SwingUtilities.invokeLater(new Runnable(){
      @Override
      public void run(){
        docTableModel.fireTableDataChanged();
      }
    });
  }

  @Override
  public void documentAdded(final CorpusEvent e) {
    docTableModel.dataChanged();
    SwingUtilities.invokeLater(new Runnable(){
      @Override
      public void run(){
        changeMessage();
        docTableModel.fireTableRowsInserted(e.getDocumentIndex(),
                e.getDocumentIndex());
      }
    });
  }

  @Override
  public void documentRemoved(final CorpusEvent e) {
    docTableModel.dataChanged();
    SwingUtilities.invokeLater(new Runnable(){
      @Override
      public void run(){
        changeMessage();
        docTableModel.fireTableRowsDeleted(e.getDocumentIndex(), 
                e.getDocumentIndex());
      }
    });
  }

  class DocumentTableModel extends AbstractTableModel{
    public DocumentTableModel(){
      documentNames = new ArrayList<String>();
    }
    
    /**
     * Called externally when the underlying corpus has changed.
     */
    private void dataChanged(){
      List<String> newDocs = new ArrayList<String>();
      if(corpus != null){
        newDocs.addAll(corpus.getDocumentNames());
     }
      List<String> oldDocs = documentNames;
      documentNames = newDocs;
      oldDocs.clear();
    }
    
    @Override
    public int getColumnCount() {
      return COLUMN_COUNT;
    }

    @Override
    public int getRowCount() {
      return documentNames.size();
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
      //invalid indexes might appear when update events are slow to act 
      if(rowIndex < 0 || rowIndex >= documentNames.size() || 
         columnIndex < 0 || columnIndex > COLUMN_COUNT) return null;
      switch(columnIndex) {
        case COL_INDEX:
          return rowIndex;
        case COL_NAME:
          return documentNames.get(rowIndex);
        default:
          return null;
      }
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
      switch(columnIndex) {
        case COL_INDEX:
          return Integer.class;
        case COL_NAME:
          return String.class;
        default:
          return String.class;
      }
    }

    @Override
    public String getColumnName(int column) {
      return COLUMN_NAMES[column];
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
      return false;
    }
    
    private List<String> documentNames;
    private final String[] COLUMN_NAMES = {"Index", "Document name"}; 
    private static final int COL_INDEX = 0;
    private static final int COL_NAME = 1;
    private static final int COLUMN_COUNT = 2;
  }

  class DocumentNameRenderer extends DefaultTableCellRenderer implements 
      ListCellRenderer<String>{
    public DocumentNameRenderer(){
      super();
      setIcon(MainFrame.getIcon("document"));
    }
    
    @Override
    public Component getListCellRendererComponent(JList<? extends String> list, String value,
            int index, boolean isSelected, boolean cellHasFocus) {
      // prepare the renderer

      return getTableCellRendererComponent(docTable, value, isSelected, 
              cellHasFocus, index, DocumentTableModel.COL_NAME);
    }

    @Override
    public Dimension getMaximumSize() {
      //we don't mind being extended horizontally
      Dimension dim = super.getMaximumSize();
      if(dim != null){
        dim.width = Integer.MAX_VALUE;
        setMaximumSize(dim);
      }
      return dim;
    }

    @Override
    public Dimension getMinimumSize() {
      //we don't like being squashed!
      return getPreferredSize();
    }
    
    
  }
  
  class MoveUpAction extends AbstractAction{
    public MoveUpAction(){
      super("Move up", MainFrame.getIcon("up"));
      putValue(SHORT_DESCRIPTION, "Moves selected document(s) up");
      putValue(MNEMONIC_KEY, KeyEvent.VK_UP);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      int[] rowsTable = docTable.getSelectedRows();
      int[] rowsCorpus = new int[rowsTable.length];
      for(int i = 0; i < rowsTable.length; i++)
        rowsCorpus[i] = docTable.rowViewToModel(rowsTable[i]);
      Arrays.sort(rowsCorpus);
      //starting from the smallest one, move each element up
      for(int i = 0; i < rowsCorpus.length; i++){
        if(rowsCorpus[i] > 0){
          //swap the doc with the one before
          //serial corpus does not load the document on remove, so we need
          //to load the document explicitly
          boolean wasLoaded = corpus.isDocumentLoaded(rowsCorpus[i]);
          Document doc = corpus.get(rowsCorpus[i]);
          corpus.remove(rowsCorpus[i]);
          rowsCorpus[i] = rowsCorpus[i] - 1;
          corpus.add(rowsCorpus[i], doc);
          if(!wasLoaded){
            corpus.unloadDocument(doc);
            Factory.deleteResource(doc);
          }
        }
      }
      //restore selection
      //the remove / add events will cause the table to be updated
      //we need to only restore the selection after that happened
      final int[] selectedRowsCorpus = new int[rowsCorpus.length];
      System.arraycopy(rowsCorpus, 0, selectedRowsCorpus, 0, rowsCorpus.length);
      SwingUtilities.invokeLater(new Runnable(){
        @Override
        public void run(){
          docTable.clearSelection();
          for(int i = 0; i < selectedRowsCorpus.length; i++){
            int rowTable = docTable.rowModelToView(selectedRowsCorpus[i]);
            docTable.getSelectionModel().addSelectionInterval(rowTable, 
                    rowTable);
          }                
        }
      });
    }
  }

  class MoveDownAction extends AbstractAction{
    public MoveDownAction(){
      super("Move down", MainFrame.getIcon("down"));
      putValue(SHORT_DESCRIPTION, "Moves selected document(s) down");
      putValue(MNEMONIC_KEY, KeyEvent.VK_DOWN);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      int[] rowsTable = docTable.getSelectedRows();
      int[] rowsCorpus = new int[rowsTable.length];
      for(int i = 0; i < rowsTable.length; i++)
        rowsCorpus[i] = docTable.rowViewToModel(rowsTable[i]);
      Arrays.sort(rowsCorpus);
      //starting from the largest one, move each element down
      for(int i = rowsCorpus.length -1; i >=0; i--){
        if(rowsCorpus[i] < corpus.size() -1){
          //swap the doc with the one before
          //serial corpus does not load the document on remove, so we need
          //to load the document explicitly
          boolean wasLoaded = corpus.isDocumentLoaded(rowsCorpus[i]);
          Document doc = corpus.get(rowsCorpus[i]);
          corpus.remove(rowsCorpus[i]);
          rowsCorpus[i]++;
          corpus.add(rowsCorpus[i], doc);
          if(!wasLoaded){
            corpus.unloadDocument(doc);
            Factory.deleteResource(doc);
          }
        }
      }
      //restore selection
      //the remove / add events will cause the table to be updated
      //we need to only restore the selection after that happened
      final int[] selectedRowsCorpus = new int[rowsCorpus.length];
      System.arraycopy(rowsCorpus, 0, selectedRowsCorpus, 0, rowsCorpus.length);
      SwingUtilities.invokeLater(new Runnable(){
        @Override
        public void run(){
          docTable.clearSelection();
          for(int i = 0; i < selectedRowsCorpus.length; i++){
            int rowTable = docTable.rowModelToView(selectedRowsCorpus[i]);
            docTable.getSelectionModel().addSelectionInterval(rowTable, 
                    rowTable);
          }                
        }
      });
    }
  }

  class NewDocumentAction extends AbstractAction{
    public NewDocumentAction(){
      super("Add document", MainFrame.getIcon("add-document"));
      putValue(SHORT_DESCRIPTION, "Add new document(s) to this corpus");
      putValue(MNEMONIC_KEY, KeyEvent.VK_ENTER);
    }

    @Override
    public void actionPerformed(ActionEvent e){
      List<Resource> loadedDocuments;
      try {
        // get all the documents loaded in the system
        loadedDocuments = Gate.getCreoleRegister()
          .getAllInstances("gate.Document");
      } catch(GateException ge) {
        //gate.Document is not registered in creole.xml....what is!?
        throw new GateRuntimeException(
          "gate.Document is not registered in the creole register!\n" +
          "Something must be terribly wrong...take a vacation!");
      }
      Vector<String> docNames = new Vector<String>();
      for (Resource loadedDocument : new ArrayList<Resource>(loadedDocuments)) {
        if (corpus.contains(loadedDocument)) {
          loadedDocuments.remove(loadedDocument);
        } else {
          docNames.add(loadedDocument.getName());
        }
      }
      JList<String> docList = new JList<String>(docNames);
      docList.getSelectionModel().setSelectionInterval(0, docNames.size()-1);
      docList.setCellRenderer(renderer);
      final JOptionPane optionPane = new JOptionPane(new JScrollPane(docList),
        JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION);
      final JDialog dialog = optionPane.createDialog(CorpusEditor.this,
        "Add document(s) to this corpus");
      docList.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
          if (e.getClickCount() == 2) {
            optionPane.setValue(JOptionPane.OK_OPTION);
            dialog.dispose();
          }
        }
      });
      dialog.setVisible(true);
      if(optionPane.getValue().equals(JOptionPane.OK_OPTION)){
        int[] selectedIndices = docList.getSelectedIndices();
        for (int selectedIndice : selectedIndices) {
          corpus.add((Document)loadedDocuments.get(selectedIndice));
        }
      }
      changeMessage();
    }
  }

  class RemoveDocumentsAction extends AbstractAction{
    public RemoveDocumentsAction(){
      super("Remove documents", MainFrame.getIcon("remove-document"));
      putValue(SHORT_DESCRIPTION,
        "Removes selected document(s) from this corpus");
      putValue(MNEMONIC_KEY, KeyEvent.VK_DELETE);
    }

    @Override
    public void actionPerformed(ActionEvent e){
      int[] selectedIndexes = docTable.getSelectedRows();
      int[] corpusIndexes = new int[selectedIndexes.length];
      for(int i = 0; i < selectedIndexes.length; i++)
        corpusIndexes[i] = docTable.rowViewToModel(selectedIndexes[i]);
      Arrays.sort(corpusIndexes);
      //remove the document starting with the one with the highest index
      for(int i = corpusIndexes.length-1; i >= 0; i--){
        corpus.remove(corpusIndexes[i]);
      }
      docTable.clearSelection();
      changeMessage();
    }
  }

  class OpenDocumentsAction extends AbstractAction{
    public OpenDocumentsAction(){
      super("Open documents", MainFrame.getIcon("document"));
      putValue(SHORT_DESCRIPTION,
        "Opens selected document(s) in a document editor");
    }

    @Override
    public void actionPerformed(ActionEvent e){
      Component root = SwingUtilities.getRoot(CorpusEditor.this);
      if (!(root instanceof MainFrame)) { return; }
      final MainFrame mainFrame = (MainFrame) root;
      final int[] selectedRows = docTable.getSelectedRows();
      if (selectedRows.length > 10) {
        Object[] possibleValues =
          { "Open the "+selectedRows.length+" documents", "Don't open" };
        int selectedValue =
          JOptionPane.showOptionDialog(docTable, "Do you want to open "
          +selectedRows.length+" documents in the central tabbed pane ?",
          "Warning", JOptionPane.DEFAULT_OPTION,
          JOptionPane.QUESTION_MESSAGE, null,
          possibleValues, possibleValues[1]);
        if (selectedValue == 1
         || selectedValue == JOptionPane.CLOSED_OPTION) {
          return;
        }
      }
      for (int row : selectedRows) {
        // load the document if inside a datastore
        corpus.get(docTable.rowViewToModel(row));
      }
      SwingUtilities.invokeLater(new Runnable() { @Override
      public void run() {
        for (int row : selectedRows) {
          Document doc = corpus.get(docTable.rowViewToModel(row));
          // show the document in the central view
          mainFrame.select(doc);
        }
      }});
    }
  }

  protected void changeMessage() {
    SwingUtilities.invokeLater(new Runnable(){ @Override
    public void run() {
    if (corpus == null || corpus.size() == 0) {
      newDocumentAction.setEnabled(true);
      messageLabel.setText(
        "<html>To add or remove documents to this corpus:<ul>" +
        "<li>use the toolbar buttons at the top of this view" +
        "<li>drag documents from the left resources tree and drop them below" +
        "<li>right click on the corpus in the resources tree and choose 'Populate'" +
        "</ul></html>");
      messageLabel.setVisible(true);
    } 
    // This is a really stupid way of checking if all the open documents are in the
    //corpus and it seems to be causing more problems than it might possibly be trying
    //to solve
    /*else if (documentsLoadedCount > 0
            && documentsLoadedCount == corpus.size()) {
      newDocumentAction.setEnabled(false);
      messageLabel.setText("All the documents loaded in the " +
        "system are in this corpus.");
      messageLabel.setVisible(true);
    } */
    else if (documentsLoadedCount == 0) {
      newDocumentAction.setEnabled(false);
      if (corpus.getDataStore() == null) {
        messageLabel.setText(
          "There are no documents loaded in the system. " +
          "Press F1 for help.");
      } else {
        messageLabel.setText("Open a document to load it from the datastore.");
      }
      messageLabel.setVisible(true);
    } else {
      newDocumentAction.setEnabled(true);
      messageLabel.setVisible(false);
    }
    }});
  }

  protected XJTable docTable;
  protected DocumentTableModel docTableModel;
  protected DocumentNameRenderer renderer;
  protected JToolBar toolbar;
  protected Corpus corpus;
  protected NewDocumentAction newDocumentAction;
  protected RemoveDocumentsAction removeDocumentsAction;
  protected MoveUpAction moveUpAction;
  protected MoveDownAction moveDownAction;
  protected OpenDocumentsAction openDocumentsAction;
  protected JLabel messageLabel;
  protected int documentsLoadedCount;
}