LuceneDataStoreSearchGUI.java
0001 /*
0002  *  Copyright (c) 1998-2009, The University of Sheffield and Ontotext.
0003  *
0004  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0005  *  software, licenced under the GNU Library General Public License,
0006  *  Version 2, June 1991 (in the distribution as file licence.html,
0007  *  and also available at http://gate.ac.uk/gate/licence.html).
0008  *
0009  *  Thomas Heitz, Dec 11, 2007
0010  *  based on Niraj Aswani GUI
0011  *
0012  *  $Id$
0013  */
0014 
0015 package gate.gui;
0016 
0017 import gate.Corpus;
0018 import gate.DataStore;
0019 import gate.Document;
0020 import gate.Factory;
0021 import gate.FeatureMap;
0022 import gate.Gate;
0023 import gate.Resource;
0024 import gate.corpora.SerialCorpusImpl;
0025 import gate.creole.AbstractVisualResource;
0026 import gate.creole.annic.Constants;
0027 import gate.creole.annic.Hit;
0028 import gate.creole.annic.Pattern;
0029 import gate.creole.annic.PatternAnnotation;
0030 import gate.creole.annic.SearchException;
0031 import gate.creole.annic.Searcher;
0032 import gate.creole.annic.lucene.QueryParser;
0033 import gate.creole.metadata.CreoleResource;
0034 import gate.creole.metadata.GuiType;
0035 import gate.event.DatastoreEvent;
0036 import gate.event.DatastoreListener;
0037 import gate.gui.docview.AnnotationSetsView;
0038 import gate.gui.docview.AnnotationStack;
0039 import gate.gui.docview.AnnotationStack.StackMouseListener;
0040 import gate.gui.docview.TextualDocumentView;
0041 import gate.persist.LuceneDataStoreImpl;
0042 import gate.persist.PersistenceException;
0043 import gate.persist.SerialDataStore;
0044 import gate.swing.BlockingGlassPane;
0045 import gate.swing.XJFileChooser;
0046 import gate.swing.XJTable;
0047 import gate.util.ExtensionFileFilter;
0048 import gate.util.GateRuntimeException;
0049 import gate.util.Strings;
0050 
0051 import java.awt.BorderLayout;
0052 import java.awt.Color;
0053 import java.awt.Component;
0054 import java.awt.Dimension;
0055 import java.awt.Font;
0056 import java.awt.GridBagConstraints;
0057 import java.awt.GridBagLayout;
0058 import java.awt.Insets;
0059 import java.awt.Rectangle;
0060 import java.awt.event.ActionEvent;
0061 import java.awt.event.ActionListener;
0062 import java.awt.event.FocusAdapter;
0063 import java.awt.event.FocusEvent;
0064 import java.awt.event.KeyAdapter;
0065 import java.awt.event.KeyEvent;
0066 import java.awt.event.MouseAdapter;
0067 import java.awt.event.MouseEvent;
0068 import java.awt.event.MouseListener;
0069 import java.awt.event.WindowAdapter;
0070 import java.awt.event.WindowEvent;
0071 import java.io.BufferedWriter;
0072 import java.io.File;
0073 import java.io.FileWriter;
0074 import java.io.IOException;
0075 import java.util.ArrayList;
0076 import java.util.Arrays;
0077 import java.util.Collections;
0078 import java.util.Comparator;
0079 import java.util.Date;
0080 import java.util.EventObject;
0081 import java.util.HashMap;
0082 import java.util.HashSet;
0083 import java.util.List;
0084 import java.util.Map;
0085 import java.util.Set;
0086 import java.util.Timer;
0087 import java.util.TimerTask;
0088 import java.util.TreeSet;
0089 import java.util.Vector;
0090 import java.util.regex.Matcher;
0091 
0092 import javax.swing.AbstractAction;
0093 import javax.swing.AbstractCellEditor;
0094 import javax.swing.Action;
0095 import javax.swing.ActionMap;
0096 import javax.swing.BorderFactory;
0097 import javax.swing.Box;
0098 import javax.swing.DefaultCellEditor;
0099 import javax.swing.DefaultComboBoxModel;
0100 import javax.swing.DefaultListModel;
0101 import javax.swing.ImageIcon;
0102 import javax.swing.InputMap;
0103 import javax.swing.JButton;
0104 import javax.swing.JCheckBox;
0105 import javax.swing.JComboBox;
0106 import javax.swing.JComponent;
0107 import javax.swing.JFileChooser;
0108 import javax.swing.JFrame;
0109 import javax.swing.JLabel;
0110 import javax.swing.JList;
0111 import javax.swing.JMenuItem;
0112 import javax.swing.JOptionPane;
0113 import javax.swing.JPanel;
0114 import javax.swing.JPopupMenu;
0115 import javax.swing.JScrollPane;
0116 import javax.swing.JSlider;
0117 import javax.swing.JSplitPane;
0118 import javax.swing.JTabbedPane;
0119 import javax.swing.JTable;
0120 import javax.swing.JTextArea;
0121 import javax.swing.JTextField;
0122 import javax.swing.JToolTip;
0123 import javax.swing.JWindow;
0124 import javax.swing.KeyStroke;
0125 import javax.swing.ListSelectionModel;
0126 import javax.swing.Popup;
0127 import javax.swing.PopupFactory;
0128 import javax.swing.SwingConstants;
0129 import javax.swing.SwingUtilities;
0130 import javax.swing.ToolTipManager;
0131 import javax.swing.UIManager;
0132 import javax.swing.border.CompoundBorder;
0133 import javax.swing.border.EmptyBorder;
0134 import javax.swing.border.EtchedBorder;
0135 import javax.swing.event.AncestorEvent;
0136 import javax.swing.event.AncestorListener;
0137 import javax.swing.event.CaretEvent;
0138 import javax.swing.event.CaretListener;
0139 import javax.swing.event.ChangeEvent;
0140 import javax.swing.event.ChangeListener;
0141 import javax.swing.event.DocumentEvent;
0142 import javax.swing.event.DocumentListener;
0143 import javax.swing.event.MouseInputAdapter;
0144 import javax.swing.table.AbstractTableModel;
0145 import javax.swing.table.DefaultTableCellRenderer;
0146 import javax.swing.table.DefaultTableModel;
0147 import javax.swing.table.TableCellEditor;
0148 import javax.swing.table.TableCellRenderer;
0149 import javax.swing.text.BadLocationException;
0150 
0151 /**
0152  * GUI allowing to write a query with a JAPE derived syntax for querying
0153  * a Lucene Datastore and display the results with a stacked view of the
0154  * annotations and their values. <br>
0155  * This VR is associated to {@link gate.creole.annic.SearchableDataStore}.
0156  * You have to set the target with setTarget(). <br>
0157  * Features: query auto-completion, syntactic error checker, display of
0158  * very big values, export of results in a file, 16 types of statistics,
0159  * store display settings in gate config.
0160  */
0161 @SuppressWarnings("serial")
0162 @CreoleResource(name = "Lucene Datastore Searcher", guiType = GuiType.LARGE, resourceDisplayed = "gate.creole.annic.SearchableDataStore", comment = "GUI allowing to write a query with a JAPE derived syntax for querying\n"
0163         " a Lucene Datastore and display the results with a stacked view of the\n"
0164         " annotations and their values.", helpURL = "http://gate.ac.uk/userguide/chap:annic")
0165 public class LuceneDataStoreSearchGUI extends AbstractVisualResource implements
0166                                                                     DatastoreListener {
0167 
0168   /** The GUI is associated with the AnnicSearchPR */
0169   private Object target;
0170 
0171   /** instances of results associated found in the document */
0172   private List<Hit> results;
0173 
0174   /** Annotation types as keys and list of features as values */
0175   private Map<String, List<String>> allAnnotTypesAndFeaturesFromDatastore;
0176 
0177   private Map<String, Set<String>> populatedAnnotationTypesAndFeatures;
0178 
0179   /** Lists the results found by the query */
0180   private XJTable resultTable;
0181 
0182   private ResultTableModel resultTableModel;
0183 
0184   /** Display the stack view configuration window. */
0185   private JButton configureStackViewButton;
0186 
0187   /**
0188    * Contains statistics for the corpus and the annotation set selected.
0189    */
0190   private XJTable globalStatisticsTable;
0191 
0192   /** Contains statistics of one row each. */
0193   private XJTable oneRowStatisticsTable;
0194 
0195   /** Comparator for Integer in statistics tables. */
0196   private Comparator<Integer> integerComparator;
0197 
0198   /** Collator for String with insensitive case. */
0199   private java.text.Collator stringCollator;
0200 
0201   /** Horizontal split between the results pane and statistics pane. */
0202   private JSplitPane bottomSplitPane;
0203 
0204   /** Display statistics on the datastore. */
0205   private JTabbedPane statisticsTabbedPane;
0206 
0207   /** Text Area that contains the query */
0208   private QueryTextArea queryTextArea;
0209 
0210   private JComboBox<String> corpusToSearchIn;
0211 
0212   private JComboBox<String> annotationSetsToSearchIn;
0213 
0214   /** list of IDs available in datastore */
0215   private List<Object> corpusIds;
0216 
0217   /*** AnnotationSet IDS the structure is: CorpusID;annotationSetName */
0218   private String[] annotationSetIDsFromDataStore;
0219 
0220   private JSlider numberOfResultsSlider;
0221 
0222   /** Number of tokens to be shown as context in the results */
0223   private JSlider contextSizeSlider;
0224 
0225   /** Gives the page number displayed in the results. */
0226   private JLabel titleResults;
0227 
0228   /** Show the next page of results. */
0229   private JButton nextResults;
0230 
0231   /** Number of the page of results. */
0232   private int pageOfResults;
0233 
0234   /** Number of row to show in the results. */
0235   int noOfResults;
0236 
0237   /** JPanel that contains the central panel of stack rows. */
0238   private AnnotationStack centerPanel;
0239 
0240   private ExecuteQueryAction executeQueryAction;
0241 
0242   private NextResultsAction nextResultsAction;
0243 
0244   private ExportResultsAction exportResultsAction;
0245 
0246   /** Current instance of the stack view frame. */
0247   private ConfigureStackViewFrame configureStackViewFrame;
0248 
0249   /** Names of the columns for stackRows data. */
0250   String[] columnNames = {"Display""Shortcut""Annotation type""Feature",
0251       "Crop"};
0252 
0253   /** Column (second dimension) of stackRows double array. */
0254   static private final int DISPLAY = 0;
0255 
0256   /** Column (second dimension) of stackRows double array. */
0257   static private final int SHORTCUT = 1;
0258 
0259   /** Column (second dimension) of stackRows double array. */
0260   static private final int ANNOTATION_TYPE = 2;
0261 
0262   /** Column (second dimension) of stackRows double array. */
0263   static private final int FEATURE = 3;
0264 
0265   /** Column (second dimension) of stackRows double array. */
0266   static private final int CROP = 4;
0267 
0268   /** Maximum number of stackRow */
0269   static private final int maxStackRows = 100;
0270 
0271   /** Number of stackRows. */
0272   private int numStackRows = 0;
0273 
0274   /** Double array that contains [row, column] of the stackRows data. */
0275   private String[][] stackRows =
0276           new String[maxStackRows + 1][columnNames.length];
0277 
0278   private ConfigureStackViewTableModel configureStackViewTableModel;
0279 
0280   private DefaultTableModel oneRowStatisticsTableModel;
0281 
0282   private DefaultTableModel globalStatisticsTableModel;
0283 
0284   /** Contains the tooltips of the first column. */
0285   private Vector<String> oneRowStatisticsTableToolTips;
0286 
0287   /** Searcher object obtained from the datastore */
0288   private Searcher searcher;
0289 
0290   /** true if there was an error on the last query. */
0291   private boolean errorOnLastQuery;
0292 
0293   /**
0294    * Called when a View is loaded in GATE.
0295    */
0296   @Override
0297   public Resource init() {
0298 
0299     results = new ArrayList<Hit>();
0300     allAnnotTypesAndFeaturesFromDatastore = new HashMap<String, List<String>>();
0301     corpusIds = new ArrayList<Object>();
0302     populatedAnnotationTypesAndFeatures = new HashMap<String, Set<String>>();
0303     noOfResults = 0;
0304     for(int row = 0; row <= maxStackRows; row++) {
0305       stackRows[row][DISPLAY"true";
0306       stackRows[row][SHORTCUT"";
0307       stackRows[row][ANNOTATION_TYPE"";
0308       stackRows[row][FEATURE"";
0309       stackRows[row][CROP"Crop end";
0310     }
0311 
0312     // read the user config data for annotation stack rows
0313     String prefix = LuceneDataStoreSearchGUI.class.getName() ".";
0314     if(Gate.getUserConfig().containsKey(prefix + "rows")) {
0315       Map<String, String> map = Gate.getUserConfig().getMap(prefix + "rows");
0316       for(int row = 0; row < maxStackRows; row++) {
0317         if(!map.containsKey(columnNames[0'_' + row)) {
0318           break;
0319         }
0320         for(int col = 0; col < columnNames.length; col++) {
0321           stackRows[row][col= map.get(columnNames[col'_' + row);
0322         }
0323         numStackRows++;
0324       }
0325     }
0326 
0327     // initialize GUI
0328     initGui();
0329     updateViews();
0330     validate();
0331     SwingUtilities.invokeLater(new Runnable() {
0332       @Override
0333       public void run() {
0334         queryTextArea.requestFocusInWindow();
0335       }
0336     });
0337 
0338     return this;
0339   }
0340 
0341   /**
0342    * Called when the user close the datastore.
0343    */
0344   @Override
0345   public void cleanup() {
0346     // no parent so need to be disposed explicitly
0347     configureStackViewFrame.dispose();
0348   }
0349 
0350   /**
0351    * Initialize the GUI.
0352    */
0353   protected void initGui() {
0354 
0355     // see the global layout schema at the end
0356     setLayout(new BorderLayout());
0357 
0358     stringCollator = java.text.Collator.getInstance();
0359     stringCollator.setStrength(java.text.Collator.TERTIARY);
0360 
0361     Comparator<String> lastWordComparator = new Comparator<String>() {
0362       @Override
0363       public int compare(String o1, String o2) {
0364         if(o1 == null || o2 == null) {
0365           return 0;
0366         }
0367         return stringCollator.compare(
0368                 o1.substring(o1.trim().lastIndexOf(' '1),
0369                 o2.substring(o2.trim().lastIndexOf(' '1));
0370       }
0371     };
0372 
0373     integerComparator = new Comparator<Integer>() {
0374       @Override
0375       public int compare(Integer o1, Integer o2) {
0376         if(o1 == null || o2 == null) {
0377           return 0;
0378         }
0379         return o1.compareTo(o2);
0380       }
0381     };
0382 
0383     /**********************************
0384      * Stack view configuration frame *
0385      **********************************/
0386 
0387     configureStackViewFrame =
0388             new ConfigureStackViewFrame("Stack view configuration");
0389     configureStackViewFrame.setIconImage(((ImageIcon)MainFrame
0390             .getIcon("crystal-clear-action-window-new")).getImage());
0391     configureStackViewFrame
0392             .setLocationRelativeTo(LuceneDataStoreSearchGUI.this);
0393     configureStackViewFrame.getRootPane()
0394             .getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
0395             .put(KeyStroke.getKeyStroke("ESCAPE")"close row manager");
0396     configureStackViewFrame.getRootPane().getActionMap()
0397             .put("close row manager"new AbstractAction() {
0398               @Override
0399               public void actionPerformed(ActionEvent e) {
0400                 configureStackViewFrame.setVisible(false);
0401               }
0402             });
0403     configureStackViewFrame.validate();
0404     configureStackViewFrame.setSize(200300);
0405     configureStackViewFrame.pack();
0406 
0407     // called when Gate is exited, in case the user doesn't close the
0408     // datastore
0409     MainFrame.getInstance().addWindowListener(new WindowAdapter() {
0410       @Override
0411       public void windowClosed(WindowEvent e) {
0412         // no parent so need to be disposed explicitly
0413         configureStackViewFrame.dispose();
0414       }
0415     });
0416 
0417     /*************
0418      * Top panel *
0419      *************/
0420 
0421     JPanel topPanel = new JPanel(new GridBagLayout());
0422     topPanel.setOpaque(false);
0423     topPanel.setBorder(BorderFactory.createEmptyBorder(3303));
0424     GridBagConstraints gbc = new GridBagConstraints();
0425 
0426     // first column, three rows span
0427     queryTextArea = new QueryTextArea();
0428     queryTextArea.setToolTipText("<html>Enter a query to search the datastore."
0429             "<br><small>'{' or '.' activate auto-completion."
0430             "<br>Ctrl+Enter add a new line.</small></html>");
0431     queryTextArea.setLineWrap(true);
0432     gbc.gridheight = 3;
0433     gbc.weightx = 1;
0434     gbc.weighty = 1;
0435     gbc.fill = GridBagConstraints.BOTH;
0436     gbc.insets = new Insets(0004);
0437     topPanel.add(new JScrollPane(queryTextArea), gbc);
0438     gbc.gridheight = 1;
0439     gbc.weightx = 0;
0440     gbc.weighty = 0;
0441     gbc.insets = new Insets(0000);
0442 
0443     // second column, first row
0444     gbc.gridx = GridBagConstraints.RELATIVE;
0445     topPanel.add(new JLabel("Corpus: "), gbc);
0446     corpusToSearchIn = new JComboBox<String>();
0447     corpusToSearchIn.addItem(Constants.ENTIRE_DATASTORE);
0448     corpusToSearchIn.setPrototypeDisplayValue(Constants.ENTIRE_DATASTORE);
0449     corpusToSearchIn.setToolTipText("Select the corpus to search in.");
0450     if(target == null || target instanceof Searcher) {
0451       corpusToSearchIn.setEnabled(false);
0452     }
0453     corpusToSearchIn.addActionListener(new ActionListener() {
0454       @Override
0455       public void actionPerformed(ActionEvent ie) {
0456         SwingUtilities.invokeLater(new Runnable() {
0457           @Override
0458           public void run() {
0459             updateAnnotationSetsList();
0460           }
0461         });
0462       }
0463     });
0464     topPanel.add(corpusToSearchIn, gbc);
0465     topPanel.add(Box.createHorizontalStrut(4), gbc);
0466     topPanel.add(new JLabel("Annotation set: "), gbc);
0467     annotationSetsToSearchIn = new JComboBox<String>();
0468     annotationSetsToSearchIn.setPrototypeDisplayValue(Constants.COMBINED_SET);
0469     annotationSetsToSearchIn
0470             .setToolTipText("Select the annotation set to search in.");
0471     annotationSetsToSearchIn.addActionListener(new ActionListener() {
0472       @Override
0473       public void actionPerformed(ActionEvent ie) {
0474         updateAnnotationTypesList();
0475       }
0476     });
0477     topPanel.add(annotationSetsToSearchIn, gbc);
0478     
0479     // refresh button
0480     topPanel.add(Box.createHorizontalStrut(4), gbc);
0481     RefreshAnnotationSetsAndFeaturesAction refreshAction = new RefreshAnnotationSetsAndFeaturesAction();
0482     JButton refreshTF =
0483             new ButtonBorder(new Color(240240240)new Insets(4243),
0484                     false);
0485     refreshTF.setAction(refreshAction);
0486     topPanel.add(refreshTF, gbc);
0487     
0488     // second column, second row
0489     gbc.gridy = 1;
0490     JLabel noOfResultsLabel = new JLabel("Results: ");
0491     topPanel.add(noOfResultsLabel, gbc);
0492     numberOfResultsSlider = new JSlider(1110050);
0493     numberOfResultsSlider.setToolTipText("50 results per page");
0494     numberOfResultsSlider.addChangeListener(new ChangeListener() {
0495       @Override
0496       public void stateChanged(ChangeEvent e) {
0497         if(numberOfResultsSlider.getValue() (numberOfResultsSlider
0498                 .getMaximum() 100)) {
0499           numberOfResultsSlider.setToolTipText("Retrieve all results.");
0500           nextResults.setText("Retrieve all results.");
0501           nextResultsAction.setEnabled(false);
0502         else {
0503           numberOfResultsSlider.setToolTipText("Retrieve "
0504                   + numberOfResultsSlider.getValue() " results per page.");
0505           nextResults.setText("Next page of "
0506                   + numberOfResultsSlider.getValue() " results");
0507           if(searcher.getHits().length == noOfResults) {
0508             nextResultsAction.setEnabled(true);
0509           }
0510         }
0511         // show the tooltip each time the value change
0512         ToolTipManager.sharedInstance().mouseMoved(
0513                 new MouseEvent(numberOfResultsSlider, MouseEvent.MOUSE_MOVED,
0514                         00000false));
0515       }
0516     });
0517     // always show the tooltip for this component
0518     numberOfResultsSlider.addMouseListener(new MouseAdapter() {
0519       ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
0520 
0521       int initialDelay, reshowDelay, dismissDelay;
0522 
0523       boolean enabled;
0524 
0525       @Override
0526       public void mouseEntered(MouseEvent e) {
0527         initialDelay = toolTipManager.getInitialDelay();
0528         reshowDelay = toolTipManager.getReshowDelay();
0529         dismissDelay = toolTipManager.getDismissDelay();
0530         enabled = toolTipManager.isEnabled();
0531         toolTipManager.setInitialDelay(0);
0532         toolTipManager.setReshowDelay(0);
0533         toolTipManager.setDismissDelay(Integer.MAX_VALUE);
0534         toolTipManager.setEnabled(true);
0535       }
0536 
0537       @Override
0538       public void mouseExited(MouseEvent e) {
0539         toolTipManager.setInitialDelay(initialDelay);
0540         toolTipManager.setReshowDelay(reshowDelay);
0541         toolTipManager.setDismissDelay(dismissDelay);
0542         toolTipManager.setEnabled(enabled);
0543       }
0544     });
0545     gbc.insets = new Insets(5000);
0546     topPanel.add(numberOfResultsSlider, gbc);
0547     gbc.insets = new Insets(0000);
0548     topPanel.add(Box.createHorizontalStrut(4), gbc);
0549     JLabel contextWindowLabel = new JLabel("Context size: ");
0550     topPanel.add(contextWindowLabel, gbc);
0551     contextSizeSlider = new JSlider(1505);
0552     contextSizeSlider
0553             .setToolTipText("Display 5 tokens of context in the results.");
0554     contextSizeSlider.addChangeListener(new ChangeListener() {
0555       @Override
0556       public void stateChanged(ChangeEvent e) {
0557         contextSizeSlider.setToolTipText("Display "
0558                 + contextSizeSlider.getValue()
0559                 " tokens of context in the results.");
0560         ToolTipManager.sharedInstance().mouseMoved(
0561                 new MouseEvent(contextSizeSlider, MouseEvent.MOUSE_MOVED, 00,
0562                         000false));
0563       }
0564     });
0565     // always show the tooltip for this component
0566     contextSizeSlider.addMouseListener(new MouseAdapter() {
0567       ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
0568 
0569       int initialDelay, reshowDelay, dismissDelay;
0570 
0571       boolean enabled;
0572 
0573       @Override
0574       public void mouseEntered(MouseEvent e) {
0575         initialDelay = toolTipManager.getInitialDelay();
0576         reshowDelay = toolTipManager.getReshowDelay();
0577         dismissDelay = toolTipManager.getDismissDelay();
0578         enabled = toolTipManager.isEnabled();
0579         toolTipManager.setInitialDelay(0);
0580         toolTipManager.setReshowDelay(0);
0581         toolTipManager.setDismissDelay(Integer.MAX_VALUE);
0582         toolTipManager.setEnabled(true);
0583       }
0584 
0585       @Override
0586       public void mouseExited(MouseEvent e) {
0587         toolTipManager.setInitialDelay(initialDelay);
0588         toolTipManager.setReshowDelay(reshowDelay);
0589         toolTipManager.setDismissDelay(dismissDelay);
0590         toolTipManager.setEnabled(enabled);
0591       }
0592     });
0593     gbc.insets = new Insets(5000);
0594     topPanel.add(contextSizeSlider, gbc);
0595     gbc.insets = new Insets(0000);
0596 
0597     // second column, third row
0598     gbc.gridy = 2;
0599     JPanel panel = new JPanel();
0600     panel.setBorder(new EmptyBorder(new Insets(0000)));
0601     executeQueryAction = new ExecuteQueryAction();
0602     JButton executeQuery =
0603             new ButtonBorder(new Color(240240240)new Insets(0203),
0604                     false);
0605     executeQuery.setAction(executeQueryAction);
0606     panel.add(executeQuery);
0607     ClearQueryAction clearQueryAction = new ClearQueryAction();
0608     JButton clearQueryTF =
0609             new ButtonBorder(new Color(240240240)new Insets(4243),
0610                     false);
0611     clearQueryTF.setAction(clearQueryAction);
0612     panel.add(Box.createHorizontalStrut(5));
0613     panel.add(clearQueryTF);
0614     nextResultsAction = new NextResultsAction();
0615     nextResultsAction.setEnabled(false);
0616     nextResults =
0617             new ButtonBorder(new Color(240240240)new Insets(0003),
0618                     false);
0619     nextResults.setAction(nextResultsAction);
0620     panel.add(Box.createHorizontalStrut(5));
0621     panel.add(nextResults);
0622     gbc.fill = GridBagConstraints.NONE;
0623     gbc.anchor = GridBagConstraints.WEST;
0624     gbc.gridwidth = GridBagConstraints.REMAINDER;
0625     topPanel.add(panel, gbc);
0626 
0627     // will be added to the GUI via a split panel
0628 
0629     /****************
0630      * Center panel *
0631      ****************/
0632 
0633     // these components will be used in updateStackView()
0634     centerPanel = new AnnotationStack(15030);
0635 
0636     configureStackViewButton =
0637             new ButtonBorder(new Color(250250250)new Insets(0003),
0638                     true);
0639     configureStackViewButton.setHorizontalAlignment(SwingConstants.LEFT);
0640     configureStackViewButton.setAction(new ConfigureStackViewAction());
0641 
0642     // will be added to the GUI via a split panel
0643 
0644     /*********************
0645      * Bottom left panel *
0646      *********************/
0647 
0648     JPanel bottomLeftPanel = new JPanel(new GridBagLayout());
0649     bottomLeftPanel.setOpaque(false);
0650     gbc = new GridBagConstraints();
0651 
0652     // title of the table, results options, export and next results
0653     // button
0654     gbc.gridy = 0;
0655     panel = new JPanel();
0656     panel.setBorder(new EmptyBorder(new Insets(0000)));
0657     titleResults = new JLabel("Results");
0658     titleResults.setBorder(new EmptyBorder(new Insets(0000)));
0659     panel.add(titleResults);
0660     panel.add(Box.createHorizontalStrut(5), gbc);
0661     exportResultsAction = new ExportResultsAction();
0662     exportResultsAction.setEnabled(false);
0663     JButton exportToHTML =
0664             new ButtonBorder(new Color(240240240)new Insets(0003),
0665                     false);
0666     exportToHTML.setAction(exportResultsAction);
0667     panel.add(exportToHTML, gbc);
0668     bottomLeftPanel.add(panel, gbc);
0669 
0670     // table of results
0671     resultTableModel = new ResultTableModel();
0672     resultTable = new XJTable(resultTableModel);
0673     resultTable.setDefaultRenderer(String.class, new ResultTableCellRenderer());
0674     resultTable.setEnableHidingColumns(true);
0675 
0676     resultTable.addMouseListener(new MouseAdapter() {
0677       private JPopupMenu mousePopup;
0678 
0679       JMenuItem menuItem;
0680 
0681       @Override
0682       public void mousePressed(MouseEvent e) {
0683         int row = resultTable.rowAtPoint(e.getPoint());
0684         if(e.isPopupTrigger() && !resultTable.isRowSelected(row)) {
0685           // if right click outside the selection then reset selection
0686           resultTable.getSelectionModel().setSelectionInterval(row, row);
0687         }
0688         if(e.isPopupTrigger()) {
0689           createPopup();
0690           mousePopup.show(e.getComponent(), e.getX(), e.getY());
0691         }
0692       }
0693 
0694       @Override
0695       public void mouseReleased(MouseEvent e) {
0696         if(e.isPopupTrigger()) {
0697           createPopup();
0698           mousePopup.show(e.getComponent(), e.getX(), e.getY());
0699         }
0700       }
0701 
0702       private void createPopup() {
0703         mousePopup = new JPopupMenu();
0704         menuItem =
0705                 new JMenuItem("Remove the selected result"
0706                         (resultTable.getSelectedRowCount() "s" ""));
0707         mousePopup.add(menuItem);
0708         menuItem.addActionListener(new ActionListener() {
0709           @Override
0710           public void actionPerformed(ActionEvent ae) {
0711             int[] rows = resultTable.getSelectedRows();
0712             for(int i = 0; i < rows.length; i++) {
0713               rows[i= resultTable.rowViewToModel(rows[i]);
0714             }
0715             Arrays.sort(rows);
0716             for(int i = rows.length - 1; i >= 0; i--) {
0717               results.remove(rows[i]);
0718             }
0719             resultTable.clearSelection();
0720             resultTableModel.fireTableDataChanged();
0721             mousePopup.setVisible(false);
0722           }
0723         });
0724 
0725         if(target instanceof LuceneDataStoreImpl
0726                 && SwingUtilities.getRoot(LuceneDataStoreSearchGUI.thisinstanceof MainFrame) {
0727           menuItem =
0728                   new JMenuItem("Open the selected document"
0729                           (resultTable.getSelectedRowCount() "s" ""));
0730           menuItem.addActionListener(new ActionListener() {
0731             @Override
0732             public void actionPerformed(ActionEvent ae) {
0733               Set<Pattern> patterns = new HashSet<Pattern>();
0734               Set<String> documentIds = new HashSet<String>();
0735               for(int rowView : resultTable.getSelectedRows()) {
0736                 // create and display the document for this result
0737                 int rowModel = resultTable.rowViewToModel(rowView);
0738                 Pattern pattern = (Pattern)results.get(rowModel);
0739                 if(!documentIds.contains(pattern.getDocumentID())) {
0740                   patterns.add(pattern);
0741                   documentIds.add(pattern.getDocumentID());
0742                 }
0743               }
0744               if(patterns.size() 10) {
0745                 Object[] possibleValues =
0746                         {"Open the " + patterns.size() " documents",
0747                             "Don't open"};
0748                 int selectedValue =
0749                         JOptionPane
0750                                 .showOptionDialog(
0751                                         LuceneDataStoreSearchGUI.this,
0752                                         "Do you want to open "
0753                                                 + patterns.size()
0754                                                 " documents in the central tabbed pane ?",
0755                                         "Warning", JOptionPane.DEFAULT_OPTION,
0756                                         JOptionPane.QUESTION_MESSAGE, null,
0757                                         possibleValues, possibleValues[1]);
0758                 if(selectedValue == 1
0759                         || selectedValue == JOptionPane.CLOSED_OPTION) {
0760                   return;
0761                 }
0762               }
0763               for(final Pattern pattern : patterns) {
0764                 // create and display the document for this result
0765                 FeatureMap features = Factory.newFeatureMap();
0766                 features.put(DataStore.DATASTORE_FEATURE_NAME, target);
0767                 features.put(DataStore.LR_ID_FEATURE_NAME,
0768                         pattern.getDocumentID());
0769                 final Document doc;
0770                 try {
0771                   doc =
0772                           (Document)Factory.createResource(
0773                                   "gate.corpora.DocumentImpl", features);
0774                 catch(gate.util.GateException e) {
0775                   e.printStackTrace();
0776                   return;
0777                 }
0778                 // show the expression in the document
0779                 SwingUtilities.invokeLater(new Runnable() {
0780                   @Override
0781                   public void run() {
0782                     MainFrame.getInstance().select(doc);
0783                   }
0784                 });
0785                 if(patterns.size() == 1) {
0786                   // wait some time for the document to be displayed
0787                   Date timeToRun = new Date(System.currentTimeMillis() 2000);
0788                   Timer timer = new Timer("Annic show document timer"true);
0789                   timer.schedule(new TimerTask() {
0790                     @Override
0791                     public void run() {
0792                       showResultInDocument(doc, pattern);
0793                     }
0794                   }, timeToRun);
0795                 }
0796               }
0797             }
0798           });
0799           mousePopup.add(menuItem);
0800         }
0801       }
0802     })// resultTable.addMouseListener
0803 
0804     // when selection change in the result table
0805     // update the stack view and export button
0806     resultTable.getSelectionModel().addListSelectionListener(
0807             new javax.swing.event.ListSelectionListener() {
0808               @Override
0809               public void valueChanged(javax.swing.event.ListSelectionEvent e) {
0810                 if(!e.getValueIsAdjusting()) {
0811                   updateStackView();
0812                 }
0813               }
0814             });
0815     resultTable.setColumnSelectionAllowed(false);
0816     resultTable.setRowSelectionAllowed(true);
0817     resultTable.setSortable(true);
0818     resultTable.setComparator(ResultTableModel.LEFT_CONTEXT_COLUMN,
0819             lastWordComparator);
0820     resultTable.setComparator(ResultTableModel.RESULT_COLUMN, stringCollator);
0821     resultTable.setComparator(ResultTableModel.RIGHT_CONTEXT_COLUMN,
0822             stringCollator);
0823 
0824     JScrollPane tableScrollPane =
0825             new JScrollPane(resultTable,
0826                     JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
0827                     JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
0828 
0829     gbc.fill = GridBagConstraints.BOTH;
0830     gbc.anchor = GridBagConstraints.NORTH;
0831     gbc.gridy = 1;
0832     gbc.gridx = 0;
0833     gbc.insets = new Insets(0000);
0834     gbc.gridwidth = GridBagConstraints.REMAINDER;
0835     gbc.gridheight = GridBagConstraints.REMAINDER;
0836     gbc.weightx = 1;
0837     gbc.weighty = 1;
0838     bottomLeftPanel.add(tableScrollPane, gbc);
0839 
0840     /**************************
0841      * Statistics tabbed pane *
0842      **************************/
0843 
0844     statisticsTabbedPane = new JTabbedPane();
0845 
0846     globalStatisticsTable = new XJTable() {
0847       @Override
0848       public boolean isCellEditable(int rowIndex, int vColIndex) {
0849         return false;
0850       }
0851     };
0852     globalStatisticsTableModel =
0853             new DefaultTableModel(new Object[] {"Annotation Type""Count"}0);
0854     globalStatisticsTable.setModel(globalStatisticsTableModel);
0855     globalStatisticsTable.setComparator(0, stringCollator);
0856     globalStatisticsTable.setComparator(1, integerComparator);
0857     globalStatisticsTable.setSortedColumn(1);
0858     globalStatisticsTable.setAscending(false);
0859     globalStatisticsTable.addMouseListener(new MouseInputAdapter() {
0860       @Override
0861       public void mousePressed(MouseEvent e) {
0862         if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
0863           updateQuery();
0864         }
0865       }
0866 
0867       private void updateQuery() {
0868         int caretPosition = queryTextArea.getCaretPosition();
0869         String query = queryTextArea.getText();
0870         String type =
0871                 (String)globalStatisticsTable.getValueAt(
0872                         globalStatisticsTable.getSelectedRow(),
0873                         globalStatisticsTable.convertColumnIndexToView(0));
0874         String queryMiddle = "{" + type + "}";
0875         String queryLeft =
0876                 (queryTextArea.getSelectionStart() == queryTextArea
0877                         .getSelectionEnd())
0878                         ? query.substring(0, caretPosition)
0879                         : query.substring(0, queryTextArea.getSelectionStart());
0880         String queryRight =
0881                 (queryTextArea.getSelectionStart() == queryTextArea
0882                         .getSelectionEnd()) ? query.substring(caretPosition,
0883                         query.length()) : query.substring(
0884                         queryTextArea.getSelectionEnd(), query.length());
0885         queryTextArea.setText(queryLeft + queryMiddle + queryRight);
0886       }
0887     });
0888 
0889     statisticsTabbedPane.addTab("Global", null, new JScrollPane(
0890             globalStatisticsTable),
0891             "Global statistics on the Corpus and Annotation Set selected.");
0892 
0893     statisticsTabbedPane.addMouseListener(new MouseAdapter() {
0894       private JPopupMenu mousePopup;
0895 
0896       JMenuItem menuItem;
0897 
0898       @Override
0899       public void mousePressed(MouseEvent e) {
0900         int tabIndex = statisticsTabbedPane.indexAtLocation(e.getX(), e.getY());
0901         if(e.isPopupTrigger() && tabIndex > 0) {
0902           createPopup(tabIndex);
0903           mousePopup.show(e.getComponent(), e.getX(), e.getY());
0904         }
0905       }
0906 
0907       @Override
0908       public void mouseReleased(MouseEvent e) {
0909         int tabIndex = statisticsTabbedPane.indexAtLocation(e.getX(), e.getY());
0910         if(e.isPopupTrigger() && tabIndex > 0) {
0911           createPopup(tabIndex);
0912           mousePopup.show(e.getComponent(), e.getX(), e.getY());
0913         }
0914       }
0915 
0916       private void createPopup(final int tabIndex) {
0917         mousePopup = new JPopupMenu();
0918         if(tabIndex == 1) {
0919           menuItem = new JMenuItem("Clear table");
0920           menuItem.addActionListener(new ActionListener() {
0921             @Override
0922             public void actionPerformed(ActionEvent ie) {
0923               oneRowStatisticsTableModel.setRowCount(0);
0924             }
0925           });
0926         else {
0927           menuItem = new JMenuItem("Close tab");
0928           menuItem.addActionListener(new ActionListener() {
0929             @Override
0930             public void actionPerformed(ActionEvent ie) {
0931               statisticsTabbedPane.remove(tabIndex);
0932             }
0933           });
0934         }
0935         mousePopup.add(menuItem);
0936       }
0937     });
0938 
0939     class RemoveCellEditorRenderer extends AbstractCellEditor implements
0940                                                              TableCellRenderer,
0941                                                              TableCellEditor,
0942                                                              ActionListener {
0943       private JButton button;
0944 
0945       public RemoveCellEditorRenderer() {
0946         button = new JButton();
0947         button.setHorizontalAlignment(SwingConstants.CENTER);
0948         button.setIcon(MainFrame.getIcon("crystal-clear-action-button-cancel"));
0949         button.setToolTipText("Remove this row.");
0950         button.setContentAreaFilled(false);
0951         button.setBorderPainted(false);
0952         button.setMargin(new Insets(0000));
0953         button.addActionListener(this);
0954       }
0955 
0956       @Override
0957       public Component getTableCellRendererComponent(JTable table,
0958               Object color, boolean isSelected, boolean hasFocus, int row,
0959               int col) {
0960         button.setSelected(isSelected);
0961         return button;
0962       }
0963 
0964       @Override
0965       public boolean shouldSelectCell(EventObject anEvent) {
0966         return false;
0967       }
0968 
0969       @Override
0970       public void actionPerformed(ActionEvent e) {
0971         int editingRow = oneRowStatisticsTable.getEditingRow();
0972         fireEditingStopped();
0973         oneRowStatisticsTableModel.removeRow(oneRowStatisticsTable
0974                 .rowViewToModel(editingRow));
0975       }
0976 
0977       @Override
0978       public Object getCellEditorValue() {
0979         return null;
0980       }
0981 
0982       @Override
0983       public Component getTableCellEditorComponent(JTable table, Object value,
0984               boolean isSelected, int row, int col) {
0985         button.setSelected(isSelected);
0986         return button;
0987       }
0988     }
0989 
0990     oneRowStatisticsTable = new XJTable() {
0991       @Override
0992       public boolean isCellEditable(int rowIndex, int vColIndex) {
0993         return vColIndex == 2;
0994       }
0995 
0996       @Override
0997       public Component prepareRenderer(TableCellRenderer renderer, int row,
0998               int col) {
0999         Component c = super.prepareRenderer(renderer, row, col);
1000         if(instanceof JComponent && col != 2) {
1001           // display a custom tooltip saved when adding statistics
1002           ((JComponent)c).setToolTipText("<html>"
1003                   + oneRowStatisticsTableToolTips.get(rowViewToModel(row))
1004                   "</html>");
1005         }
1006         return c;
1007       }
1008     };
1009 
1010     oneRowStatisticsTableModel =
1011             new DefaultTableModel(new Object[] {"Annotation Type/Feature",
1012                 "Count"""}0);
1013     oneRowStatisticsTable.setModel(oneRowStatisticsTableModel);
1014     oneRowStatisticsTable.getColumnModel().getColumn(2)
1015             .setCellEditor(new RemoveCellEditorRenderer());
1016     oneRowStatisticsTable.getColumnModel().getColumn(2)
1017             .setCellRenderer(new RemoveCellEditorRenderer());
1018     oneRowStatisticsTable.setComparator(0, stringCollator);
1019     oneRowStatisticsTable.setComparator(1, integerComparator);
1020 
1021     statisticsTabbedPane.addTab("One item", null, new JScrollPane(
1022             oneRowStatisticsTable)"<html>One item statistics.<br>"
1023             "Right-click on an annotation<br>" "to add statistics here.");
1024     oneRowStatisticsTableToolTips = new Vector<String>();
1025 
1026     // will be added to the GUI via a split panel
1027 
1028     /**************************************************************
1029      * Vertical splits between top, center panel and bottom panel *
1030      **************************************************************/
1031 
1032     /** ________________________________________
1033      * |               topPanel                 |
1034      * |__________________3_____________________|
1035      * |                                        |
1036      * |             centerPanel                |
1037      * |________2________ __________2___________|
1038      * |                 |                      |
1039      * | bottomLeftPanel 1 statisticsTabbedPane |
1040      * |_________________|______________________|
1041      
1042      * 1 bottomSplitPane 2 centerBottomSplitPane 3 topBottomSplitPane
1043      */
1044 
1045     bottomSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
1046     Dimension minimumSize = new Dimension(00);
1047     bottomLeftPanel.setMinimumSize(minimumSize);
1048     statisticsTabbedPane.setMinimumSize(minimumSize);
1049     bottomSplitPane.add(bottomLeftPanel);
1050     bottomSplitPane.add(statisticsTabbedPane);
1051     bottomSplitPane.setOneTouchExpandable(true);
1052     bottomSplitPane.setResizeWeight(0.75);
1053     bottomSplitPane.setContinuousLayout(true);
1054 
1055     JSplitPane centerBottomSplitPane =
1056             new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1057     centerBottomSplitPane.add(new JScrollPane(centerPanel,
1058             JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
1059             JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED));
1060     centerBottomSplitPane.add(bottomSplitPane);
1061     centerBottomSplitPane.setResizeWeight(0.5);
1062     centerBottomSplitPane.setContinuousLayout(true);
1063 
1064     JSplitPane topBottomSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1065     topBottomSplitPane.add(topPanel);
1066     topBottomSplitPane.add(centerBottomSplitPane);
1067     topBottomSplitPane.setContinuousLayout(true);
1068 
1069     add(topBottomSplitPane, BorderLayout.CENTER);
1070 
1071   }
1072 
1073   private void showResultInDocument(final Document doc, final Pattern result) {
1074     SwingUtilities.invokeLater(new Runnable() {
1075 
1076       @Override
1077       public void run() {
1078         try {
1079           // find the document view associated with the document
1080           TextualDocumentView t = null;
1081           for(Resource r : Gate.getCreoleRegister().getAllInstances(
1082                   "gate.gui.docview.TextualDocumentView")) {
1083             if(((TextualDocumentView)r).getDocument().getName()
1084                     .equals(doc.getName())) {
1085               t = (TextualDocumentView)r;
1086               break;
1087             }
1088           }
1089 
1090           if(t != null && t.getOwner() != null) {
1091             // display the annotation sets view
1092             t.getOwner().setRightView(0);
1093             try {
1094               // scroll to the expression that matches the query result
1095               t.getTextView().scrollRectToVisible(
1096                       t.getTextView()
1097                               .modelToView(result.getRightContextEndOffset()));
1098             catch(BadLocationException e) {
1099               e.printStackTrace();
1100               return;
1101             }
1102             // select the expression that matches the query result
1103             t.getTextView().select(result.getLeftContextStartOffset(),
1104                     result.getRightContextEndOffset());
1105             t.getTextView().requestFocus();
1106           }
1107 
1108           // find the annotation sets view associated with the document
1109           for(Resource r : Gate.getCreoleRegister().getAllInstances(
1110                   "gate.gui.docview.AnnotationSetsView")) {
1111             AnnotationSetsView asv = (AnnotationSetsView)r;
1112             if(asv == null) {
1113               continue;
1114             }
1115             if(asv.isActive() && asv.getDocument().getName().equals(doc.getName())) {
1116               // display the same annotation types as in Annic
1117               for(int row = 0; row < numStackRows; row++) {
1118                 if(stackRows[row][DISPLAY].equals("false")) {
1119                   continue;
1120                 }
1121                 String type = stackRows[row][ANNOTATION_TYPE];
1122                 if(type.equals(Constants.ANNIC_TOKEN)) {
1123                   // not interesting to display them
1124                   continue;
1125                 }
1126                 // look if there is the type displayed in Annic
1127                 String asn = result.getAnnotationSetName();
1128                 if(asn.equals(Constants.DEFAULT_ANNOTATION_SET_NAME)
1129                         && doc.getAnnotations().getAllTypes().contains(type)) {
1130                   asv.setTypeSelected(null, type, true);
1131                 else if(doc.getAnnotationSetNames().contains(asn)
1132                         && doc.getAnnotations(asn).getAllTypes().contains(type)) {
1133                   asv.setTypeSelected(asn, type, true);
1134                 }
1135               }
1136               break;
1137             }
1138           }
1139 
1140         catch(gate.util.GateException e) {
1141           e.printStackTrace();
1142         }
1143         
1144       }
1145       
1146     });
1147     
1148     
1149   // private void showExpressionInDocument
1150 
1151   /**
1152    * Update the result table and center view according to the result of
1153    * the search contained in <code>searcher</code>.
1154    */
1155   protected void updateViews() {
1156 
1157     if(searcher != null) {
1158       Collections.addAll(results, searcher.getHits());
1159       // update the table of results
1160       resultTableModel.fireTableDataChanged();
1161     }
1162 
1163     if(results.size() 0) {
1164       String query = queryTextArea.getText().trim();
1165       if(query.length() && !results.isEmpty()) {
1166         int row;
1167         do // delete previous temporary stack rows
1168           row = findStackRow(DISPLAY, "one time");
1169           deleteStackRow(row);
1170         while(row >= 0);
1171         // from the query display all the existing stackRows
1172         // that are not already displayed
1173         Matcher matcher = java.util.regex.Pattern.compile("\\{" // first
1174                                                                 // condition
1175                 "([^\\{\\}=,.]+)" // annotation type or shortcut (1)
1176                 "(?:(?:\\.([^=]+)==\"([^\\}\"]+)\")" // feature (2),
1177                                                        // value (3)
1178                 "|(?:==([^\\}]+)))?" // value of a shortcut (4)
1179                 "(?:, ?" // second condition
1180                 "([^\\{\\}=,.]+)" // annotation type or shortcut (5)
1181                 "(?:(?:\\.([^=]+)==\"([^\\}\"]+)\")" // feature (6),
1182                                                        // value (7)
1183                 "|(?:==([^\\}]+)))?)?" // value of a shortcut (8)
1184                 "\\}").matcher(query);
1185         while(matcher.find()) {
1186           for(int i = 0; i <= 4; i += 4) { // first then second
1187                                            // condition
1188             String type = null, feature = null, shortcut;
1189             row = -1;
1190             if(matcher.group(+ i!= null && matcher.group(+ i== null
1191                     && matcher.group(+ i== null
1192                     && matcher.group(+ i== null) {
1193               type = matcher.group(+ i);
1194               feature = "";
1195               row = findStackRow(ANNOTATION_TYPE, type, FEATURE, feature);
1196             else if(matcher.group(+ i!= null
1197                     && matcher.group(+ i== null
1198                     && matcher.group(+ i== null) {
1199               shortcut = matcher.group(+ i);
1200               row = findStackRow(SHORTCUT, shortcut);
1201             else if(matcher.group(+ i!= null
1202                     && matcher.group(+ i!= null
1203                     && matcher.group(+ i== null) {
1204               type = matcher.group(+ i);
1205               feature = matcher.group(+ i);
1206               row = findStackRow(ANNOTATION_TYPE, type, FEATURE, feature);
1207             }
1208             if(row >= 0) {
1209               stackRows[row][DISPLAY"true";
1210             else if(type != null && feature != null
1211                     && numStackRows < maxStackRows) {
1212               stackRows[numStackRows][DISPLAY"one time";
1213               stackRows[numStackRows][SHORTCUT"";
1214               stackRows[numStackRows][ANNOTATION_TYPE= type;
1215               stackRows[numStackRows][FEATURE= feature;
1216               stackRows[numStackRows][CROP"Crop end";
1217               numStackRows++;
1218             }
1219           }
1220         }
1221         configureStackViewTableModel.fireTableDataChanged();
1222       }
1223       exportResultsAction.setEnabled(true);
1224       if(numberOfResultsSlider.getValue() <= (numberOfResultsSlider
1225               .getMaximum() 100)) {
1226         nextResultsAction.setEnabled(true);
1227       }
1228       if(searcher.getHits().length < noOfResults) {
1229         nextResultsAction.setEnabled(false);
1230       }
1231       resultTable.setRowSelectionInterval(00);
1232       resultTable.scrollRectToVisible(resultTable.getCellRect(00true));
1233 
1234     else if(queryTextArea.getText().trim().length() 1) {
1235       centerPanel.removeAll();
1236       centerPanel
1237               .add(new JTextArea(
1238                       "Have a look at the statistics table at the bottom right\n"
1239                               "for the most frequent annotations.\n\n"
1240                               "Enter a query in the text area at the top and press Enter.\n\n"
1241                               "For example: {Person} to retrieve Person annotations."),
1242                       new GridBagConstraints());
1243       centerPanel.updateUI();
1244       nextResultsAction.setEnabled(false);
1245       exportResultsAction.setEnabled(false);
1246 
1247     else {
1248       GridBagConstraints gbc = new GridBagConstraints();
1249       gbc.gridwidth = GridBagConstraints.REMAINDER;
1250       gbc.gridy = GridBagConstraints.RELATIVE;
1251       if(errorOnLastQuery) {
1252         errorOnLastQuery = false;
1253       else {
1254         centerPanel.removeAll();
1255         centerPanel.add(new JTextArea("No result found for your query."), gbc);
1256         if(!corpusToSearchIn.getSelectedItem().equals(
1257                 Constants.ENTIRE_DATASTORE)
1258                 || !annotationSetsToSearchIn.getSelectedItem().equals(
1259                         Constants.ALL_SETS)) {
1260           gbc.insets = new Insets(20000);
1261           centerPanel.add(
1262                   new JTextArea(
1263                           "Consider increasing the number of documents to search "
1264                                   "in selecting \""
1265                                   + Constants.ENTIRE_DATASTORE
1266                                   "\" as corpus\n" " and \""
1267                                   + Constants.ALL_SETS
1268                                   "\" as annotation set "
1269                                   "in the drop-down lists."), gbc);
1270         }
1271       }
1272       gbc.insets = new Insets(20000);
1273       centerPanel.add(new JTextArea("Try one of these types of query:\n"
1274               "- word (each word must match a Token)\n"
1275               "- {AnnotationType}\n" "- {AnnotationType==\"text\"}\n"
1276               "- {AnnotationType.feature==\"value\"}\n"
1277               "- {AnnotationType, AnnotationType}\n"
1278               "- ({A}∣{B}) (means A or B)\n"
1279               "- ({A})+n (means one and up to n occurrences)\n"
1280               "- ({A})*n (means zero or up to n occurrences)\n"), gbc);
1281       centerPanel.updateUI();
1282       exportResultsAction.setEnabled(false);
1283       nextResultsAction.setEnabled(false);
1284     }
1285   }
1286 
1287   /**
1288    * Updates the annotation stack in the central view.
1289    */
1290   protected void updateStackView() {
1291 
1292     GridBagConstraints gbc = new GridBagConstraints();
1293     gbc.gridx = 0;
1294     gbc.gridy = 0;
1295     gbc.fill = GridBagConstraints.BOTH;
1296 
1297     if(resultTable.getSelectedRow() == -1) {
1298       // no result is selected in the result table
1299       centerPanel.removeAll();
1300       if(resultTable.getRowCount() 0) {
1301         centerPanel.add(new JLabel("Select a row in the results table below."),
1302                 gbc);
1303       else {
1304         if(numberOfResultsSlider.getValue() (numberOfResultsSlider
1305                 .getMaximum() 100)) {
1306           centerPanel.add(new JLabel("Retrieving all results..."), gbc);
1307         else {
1308           centerPanel.add(
1309                   new JLabel("Retrieving " + numberOfResultsSlider.getValue()
1310                           " results..."), gbc);
1311         }
1312       }
1313       centerPanel.validate();
1314       centerPanel.repaint();
1315       return;
1316     }
1317 
1318     // get information for the selected row in the results table
1319     Pattern result =
1320             (Pattern)results.get(resultTable.rowViewToModel(resultTable
1321                     .getSelectionModel().getLeadSelectionIndex()));
1322 
1323     // initialize the annotation stack
1324     centerPanel.setText(result.getPatternText());
1325     centerPanel.setExpressionStartOffset(result.getStartOffset());
1326     centerPanel.setExpressionEndOffset(result.getEndOffset());
1327     centerPanel.setContextBeforeSize(result.getStartOffset()
1328             - result.getLeftContextStartOffset());
1329     centerPanel.setContextAfterSize(result.getRightContextEndOffset()
1330             - result.getEndOffset());
1331     centerPanel.setLastRowButton(configureStackViewButton);
1332     centerPanel.setTextMouseListener(new TextMouseListener());
1333     centerPanel.setHeaderMouseListener(new HeaderMouseListener());
1334     centerPanel.setAnnotationMouseListener(new AnnotationMouseListener());
1335     centerPanel.clearAllRows();
1336 
1337     // add each row to the annotation stack
1338     for(int row = 0; row < numStackRows; row++) {
1339       if(stackRows[row][DISPLAY].equals("false")) {
1340         continue;
1341       }
1342 
1343       String type = stackRows[row][ANNOTATION_TYPE];
1344       String feature = stackRows[row][FEATURE];
1345       String shortcut = stackRows[row][SHORTCUT];
1346 
1347       // remove button displayed at the end of each row
1348       JButton removeRowButton =
1349               new ButtonBorder(new Color(250250250),
1350                       new Insets(0303)true);
1351       removeRowButton.setIcon(MainFrame
1352               .getIcon("crystal-clear-action-edit-remove"));
1353       removeRowButton.setToolTipText("Hide this row.");
1354       final String typeFinal = type;
1355       final String featureFinal = feature;
1356       removeRowButton.addActionListener(new ActionListener() {
1357         @Override
1358         public void actionPerformed(ActionEvent ie) {
1359           int row =
1360                   findStackRow(ANNOTATION_TYPE, typeFinal, FEATURE,
1361                           featureFinal);
1362           if(row >= 0) {
1363             stackRows[row][DISPLAY"false";
1364             saveStackViewConfiguration();
1365           }
1366           updateStackView();
1367         }
1368       });
1369 
1370       int crop;
1371       if(stackRows[row][CROP].equals("Crop start")) {
1372         crop = AnnotationStack.CROP_START;
1373       else if(stackRows[row][CROP].equals("Crop end")) {
1374         crop = AnnotationStack.CROP_END;
1375       else {
1376         crop = AnnotationStack.CROP_MIDDLE;
1377       }
1378 
1379       centerPanel.addRow(null, type, feature, removeRowButton, shortcut, crop);
1380 
1381       // annotations for this row
1382       PatternAnnotation[] annotations = result.getPatternAnnotations(type);
1383       if(annotations != null && annotations.length > 0) {
1384         for(PatternAnnotation annotation : annotations) {
1385           FeatureMap features = Factory.newFeatureMap();
1386           features.putAll(annotation.getFeatures());
1387           centerPanel.addAnnotation(annotation.getStartOffset(),
1388                   annotation.getEndOffset(), annotation.getType(), features);
1389         }
1390       }
1391     }
1392 
1393     // draw the annotation stack
1394     centerPanel.drawStack();
1395   }
1396 
1397   protected void updateAnnotationSetsList() {
1398     String corpusName =
1399             (corpusToSearchIn.getSelectedItem()
1400                     .equals(Constants.ENTIRE_DATASTORE))
1401                     null
1402                     (String)corpusIds
1403                             .get(corpusToSearchIn.getSelectedIndex() 1);
1404     TreeSet<String> ts = new TreeSet<String>(stringCollator);
1405     ts.addAll(getAnnotationSetNames(corpusName));
1406     DefaultComboBoxModel<String> dcbm = new DefaultComboBoxModel<String>(ts.toArray(new String[ts.size()]));
1407     dcbm.insertElementAt(Constants.ALL_SETS, 0);
1408     annotationSetsToSearchIn.setModel(dcbm);
1409     annotationSetsToSearchIn.setSelectedItem(Constants.ALL_SETS);
1410 
1411     // used in the ConfigureStackViewFrame as Annotation type column
1412     // cell editor
1413     TreeSet<String> types = new TreeSet<String>(stringCollator);
1414     types.addAll(getTypesAndFeatures(null, null).keySet());
1415     // put all annotation types from the datastore
1416     // combobox used as cell editor
1417     JComboBox<String> annotTypesBox = new JComboBox<String>();
1418     annotTypesBox.setMaximumRowCount(10);
1419     annotTypesBox.setModel(new DefaultComboBoxModel<String>(types.toArray(new String[types.size()])));
1420     DefaultCellEditor cellEditor = new DefaultCellEditor(annotTypesBox);
1421     cellEditor.setClickCountToStart(0);
1422     configureStackViewFrame.getTable().getColumnModel()
1423             .getColumn(ANNOTATION_TYPE).setCellEditor(cellEditor);
1424   }
1425 
1426   protected void updateAnnotationTypesList() {
1427     String corpusName =
1428             (corpusToSearchIn.getSelectedItem()
1429                     .equals(Constants.ENTIRE_DATASTORE))
1430                     null
1431                     (String)corpusIds
1432                             .get(corpusToSearchIn.getSelectedIndex() 1);
1433     String annotationSetName =
1434             (annotationSetsToSearchIn.getSelectedItem()
1435                     .equals(Constants.ALL_SETS))
1436                     null
1437                     (String)annotationSetsToSearchIn.getSelectedItem();
1438     populatedAnnotationTypesAndFeatures =
1439             getTypesAndFeatures(corpusName, annotationSetName);
1440 
1441     int countTotal = 0;
1442     try {
1443       int count;
1444       TreeSet<String> ts = new TreeSet<String>(stringCollator);
1445       ts.addAll(populatedAnnotationTypesAndFeatures.keySet());
1446       globalStatisticsTableModel.setRowCount(0);
1447       for(String annotationType : ts) {
1448         // retrieves the number of occurrences for each Annotation Type
1449         // of the choosen Annotation Set
1450         count = searcher.freq(corpusName, annotationSetName, annotationType);
1451         globalStatisticsTableModel.addRow(new Object[] {annotationType, count});
1452         countTotal += count;
1453       }
1454     catch(SearchException se) {
1455       se.printStackTrace();
1456       return;
1457     }
1458     if(countTotal == 0) {
1459       centerPanel.removeAll();
1460       centerPanel.add(new JLabel("<html>There is no annotation for the moment "
1461               "for the selected corpus and annotation set.<br><br>"
1462               "Select another corpus or annotation set or wait for the "
1463               "end of the automatic indexation.")new GridBagConstraints());
1464     }
1465   }
1466 
1467   protected Set<String> getAnnotationSetNames(String corpusName) {
1468     Set<String> toReturn = new HashSet<String>();
1469     if(corpusName == null) {
1470       for(String aSet : annotationSetIDsFromDataStore) {
1471         aSet = aSet.substring(aSet.indexOf(';'1);
1472         toReturn.add(aSet);
1473       }
1474     else {
1475       for(String aSet : annotationSetIDsFromDataStore) {
1476         if(aSet.startsWith(corpusName + ";")) {
1477           aSet = aSet.substring(aSet.indexOf(';'1);
1478           toReturn.add(aSet);
1479         }
1480       }
1481     }
1482     return toReturn;
1483   }
1484 
1485   protected Map<String, Set<String>> getTypesAndFeatures(String corpusName,
1486           String annotationSetName) {
1487     HashMap<String, Set<String>> toReturn = new HashMap<String, Set<String>>();
1488     if(corpusName == null && annotationSetName == null) {
1489       // we simply go through all the annotTyes
1490       // remove corpusID;annotationSetID; from it
1491       for(String type : allAnnotTypesAndFeaturesFromDatastore.keySet()) {
1492         String annotation = type.substring(type.lastIndexOf(';'1);
1493         Set<String> features = toReturn.get(annotation);
1494         if(features == null) {
1495           features = new HashSet<String>();
1496           toReturn.put(annotation, features);
1497         }
1498         features.addAll(allAnnotTypesAndFeaturesFromDatastore.get(type));
1499       }
1500     else if(corpusName == null) {
1501       // we simply go through all the annotTyes
1502       // remove corpusID;annotationSetID; from it
1503       for(String type : allAnnotTypesAndFeaturesFromDatastore.keySet()) {
1504         String annotation = type.substring(type.indexOf(';'1);
1505         if(annotation.startsWith(annotationSetName + ";")) {
1506           annotation = annotation.substring(annotation.indexOf(';'1);
1507           Set<String> features = toReturn.get(annotation);
1508           if(features == null) {
1509             features = new HashSet<String>();
1510             toReturn.put(annotation, features);
1511           }
1512           features.addAll(allAnnotTypesAndFeaturesFromDatastore.get(type));
1513         }
1514       }
1515     else if(annotationSetName == null) {
1516       // we simply go through all the annotTyes
1517       // remove corpusID;annotationSetID; from it
1518       for(String type : allAnnotTypesAndFeaturesFromDatastore.keySet()) {
1519         if(type.startsWith(corpusName + ";")) {
1520           String annotation = type.substring(type.lastIndexOf(';'1);
1521           Set<String> features = toReturn.get(annotation);
1522           if(features == null) {
1523             features = new HashSet<String>();
1524             toReturn.put(annotation, features);
1525           }
1526           features.addAll(allAnnotTypesAndFeaturesFromDatastore.get(type));
1527         }
1528       }
1529     else {
1530       // we simply go through all the annotTyes
1531       // remove corpusID;annotationSetID; from it
1532       for(String type : allAnnotTypesAndFeaturesFromDatastore.keySet()) {
1533         if(type.startsWith(corpusName + ";" + annotationSetName + ";")) {
1534           String annotation = type.substring(type.lastIndexOf(';'1);
1535           Set<String> features = toReturn.get(annotation);
1536           if(features == null) {
1537             features = new HashSet<String>();
1538             toReturn.put(annotation, features);
1539           }
1540           features.addAll(allAnnotTypesAndFeaturesFromDatastore.get(type));
1541         }
1542       }
1543     }
1544     return toReturn;
1545   }
1546 
1547   /**
1548    * Find the first stack row satisfying all the parameters.
1549    
1550    @param parameters couples of int*String that stands for
1551    *          column*value
1552    @return -2 if there is an error in parameters, -1 if not found, row
1553    *         satisfying the parameters otherwise
1554    @see #DISPLAY DISPLAY column parameter
1555    @see #SHORTCUT SHORTCUT column parameter
1556    @see #ANNOTATION_TYPE ANNOTATION_TYPE column parameter
1557    @see #FEATURE FEATURE column parameter
1558    @see #CROP CROP column parameter
1559    */
1560   protected int findStackRow(Object... parameters) {
1561     // test the number of parameters
1562     if((parameters.length % 2!= 0) {
1563       return -2;
1564     }
1565     // test the type and value of the parameters
1566     for(int num = 0; num < parameters.length; num += 2) {
1567       if(parameters[num== null || parameters[num + 1== null) {
1568         return -2;
1569       }
1570       try {
1571         if(Integer.parseInt(parameters[num].toString()) 0
1572                 || Integer.parseInt(parameters[num].toString()) (columnNames.length - 1)) {
1573           return -2;
1574         }
1575       catch(NumberFormatException nfe) {
1576         return -2;
1577       }
1578       if(!(parameters[num + 1instanceof String)) {
1579         return -2;
1580       }
1581     }
1582 
1583     // look for the first row satisfying all the parameters
1584     for(int row = 0; row < numStackRows; row++) {
1585       int numParametersSatisfied = 0;
1586       for(int num = 0; num < parameters.length; num += 2) {
1587         if(stackRows[row][Integer.parseInt(parameters[num].toString())]
1588                 .equals(parameters[num + 1])) {
1589           numParametersSatisfied++;
1590         }
1591       }
1592       if(numParametersSatisfied == (parameters.length / 2)) {
1593         return row;
1594       }
1595     }
1596     return -1;
1597   }
1598 
1599   /**
1600    * Delete a row in the stackRows array by shifting the following rows
1601    * to avoid empty row.
1602    
1603    @param row row to delete in the stackRows array
1604    @return true if deleted, false otherwise
1605    */
1606   protected boolean deleteStackRow(int row) {
1607     if(row < || row > numStackRows) {
1608       return false;
1609     }
1610     // shift the rows in the array
1611     for(int row2 = row; row2 < numStackRows; row2++) {
1612       System.arraycopy(stackRows[row2 + 1]0, stackRows[row2]0,
1613               columnNames.length);
1614     }
1615     stackRows[numStackRows][DISPLAY"true";
1616     stackRows[numStackRows][SHORTCUT"";
1617     stackRows[numStackRows][ANNOTATION_TYPE"";
1618     stackRows[numStackRows][FEATURE"";
1619     stackRows[numStackRows][CROP"Crop end";
1620     numStackRows--;
1621     return true;
1622   }
1623 
1624   /**
1625    * Save the user config data.
1626    */
1627   protected void saveStackViewConfiguration() {
1628     Map<String, String> map = new HashMap<String, String>();
1629     for(int row = 0; row < numStackRows; row++) {
1630       for(int col = 0; col < columnNames.length; col++) {
1631         map.put(columnNames[col'_' + row, stackRows[row][col]);
1632       }
1633     }
1634     Gate.getUserConfig().put(
1635             LuceneDataStoreSearchGUI.class.getName() ".rows",
1636             Strings.toString(map));
1637   }
1638 
1639   /**
1640    * Exports results and statistics to a HTML File.
1641    */
1642   protected class ExportResultsAction extends AbstractAction {
1643 
1644     public ExportResultsAction() {
1645       super("Export", MainFrame.getIcon("crystal-clear-app-download-manager"));
1646       super.putValue(SHORT_DESCRIPTION,
1647               "Export results and statistics to a HTML file.");
1648       super.putValue(MNEMONIC_KEY, KeyEvent.VK_E);
1649     }
1650 
1651     @Override
1652     public void actionPerformed(ActionEvent ae) {
1653       XJFileChooser fileChooser =
1654               (MainFrame.getFileChooser() != null)
1655                       ? MainFrame.getFileChooser()
1656                       new XJFileChooser();
1657       fileChooser.setAcceptAllFileFilterUsed(true);
1658       fileChooser.setDialogTitle("Choose a file to export the results");
1659       fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
1660       ExtensionFileFilter filter =
1661               new ExtensionFileFilter("HTML files""html");
1662       fileChooser.addChoosableFileFilter(filter);
1663       String location =
1664               (target instanceof SerialDataStore)
1665                       '-' ((SerialDataStore)target).getStorageDir()
1666                               .getName() "";
1667       String fileName = "Datastore" + location + ".html";
1668       fileChooser.setFileName(fileName);
1669       fileChooser.setResource(LuceneDataStoreSearchGUI.class.getName());
1670       int res = fileChooser.showSaveDialog(LuceneDataStoreSearchGUI.this);
1671       if(res != JFileChooser.APPROVE_OPTION) {
1672         return;
1673       }
1674 
1675       File saveFile = fileChooser.getSelectedFile();
1676       BufferedWriter bw = null;
1677       try {
1678         String nl = Strings.getNl();
1679         bw = new BufferedWriter(new FileWriter(saveFile));
1680         bw.write("<!DOCTYPE html PUBLIC "
1681                 "\"-//W3C//DTD HTML 4.01 Transitional//EN\"" + nl);
1682         bw.write("\"http://www.w3.org/TR/html4/loose.dtd\">" + nl);
1683         bw.write("<HTML><HEAD><TITLE>Annic Results and Statistics</TITLE>" + nl);
1684         bw.write("<meta http-equiv=\"Content-Type\""
1685                 " content=\"text/html; charset=utf-8\">" + nl);
1686         bw.write("</HEAD><BODY>" + nl + nl);
1687 
1688         bw.write("<H1 align=\"center\">Annic Results and Statistics</H1>" + nl);
1689         bw.write("<H2>Parameters</H2>" + nl);
1690         bw.write("<UL><LI>Corpus: <B>" + corpusToSearchIn.getSelectedItem()
1691                 "</B></LI>" + nl);
1692         bw.write("<LI>Annotation set: <B>"
1693                 + annotationSetsToSearchIn.getSelectedItem() "</B></LI>" + nl);
1694         bw.write("<LI>Query Issued: <B>" + searcher.getQuery() "</B></LI>");
1695         bw.write("<LI>Context Window: <B>"
1696                 + searcher.getParameters().get(Constants.CONTEXT_WINDOW)
1697                 "</B></LI>" + nl);
1698         bw.write("</UL>" + nl + nl);
1699 
1700         bw.write("<H2>Results</H2>" + nl);
1701         bw.write("<TABLE border=\"1\"><TBODY>" + nl);
1702         bw.write("<TR>");
1703         for(int col = 0; col < resultTable.getColumnCount(); col++) {
1704           bw.write("<TH>" + resultTable.getColumnName(col"</TH>" + nl);
1705         }
1706         bw.write("</TR>" + nl);
1707         for(int row = 0; row < resultTable.getRowCount(); row++) {
1708           bw.write("<TR>");
1709           for(int col = 0; col < resultTable.getColumnCount(); col++) {
1710             bw.write("<TD>"
1711                     ((String)resultTable.getValueAt(row, col))
1712                             .replaceAll("&""&amp;").replaceAll("<""&lt;")
1713                             .replaceAll(">""&gt;").replaceAll("\"""&quot;")
1714                     "</TD>" + nl);
1715           }
1716           bw.write("</TR>" + nl);
1717         }
1718         bw.write("</TBODY></TABLE>" + nl + nl);
1719 
1720         bw.write("<H2>Global Statistics</H2>");
1721         bw.write("<TABLE border=\"1\"><TBODY>" + nl);
1722         bw.write("<TR>");
1723         for(int col = 0; col < globalStatisticsTable.getColumnCount(); col++) {
1724           bw.write("<TH>" + globalStatisticsTable.getColumnName(col"</TH>"
1725                   + nl);
1726         }
1727         bw.write("</TR>" + nl);
1728         for(int row = 0; row < globalStatisticsTable.getRowCount(); row++) {
1729           bw.write("<TR>");
1730           for(int col = 0; col < globalStatisticsTable.getColumnCount(); col++) {
1731             bw.write("<TD>" + globalStatisticsTable.getValueAt(row, col)
1732                     "</TD>" + nl);
1733           }
1734           bw.write("</TR>" + nl);
1735         }
1736         bw.write("</TBODY></TABLE>" + nl + nl);
1737 
1738         bw.write("<H2>One item Statistics</H2>" + nl);
1739         bw.write("<TABLE border=\"1\"><TBODY>" + nl);
1740         bw.write("<TR>");
1741         for(int col = 0; col < (oneRowStatisticsTable.getColumnCount() 1); col++) {
1742           bw.write("<TH>" + oneRowStatisticsTable.getColumnName(col"</TH>"
1743                   + nl);
1744         }
1745         bw.write("</TR>" + nl);
1746         for(int row = 0; row < oneRowStatisticsTable.getRowCount(); row++) {
1747           bw.write("<TR>");
1748           for(int col = 0; col < (oneRowStatisticsTable.getColumnCount() 1); col++) {
1749             bw.write("<TD>" + oneRowStatisticsTable.getValueAt(row, col)
1750                     "</TD>" + nl);
1751           }
1752           bw.write("</TR>" + nl);
1753         }
1754         bw.write("</TBODY></TABLE>" + nl + nl);
1755 
1756         bw.write("<P><BR></P><HR>" + nl);
1757         bw.write("</BODY>" + nl);
1758         bw.write("</HTML>");
1759         bw.flush();
1760 
1761       catch(IOException e) {
1762         e.printStackTrace();
1763 
1764       finally {
1765         try {
1766           if(bw != null) {
1767             bw.close();
1768           }
1769         catch(IOException e) {
1770           e.printStackTrace();
1771         }
1772       }
1773     }
1774   }
1775 
1776   /**
1777    * Clear the queryTextArea text box.
1778    */
1779   protected class ClearQueryAction extends AbstractAction {
1780 
1781     public ClearQueryAction() {
1782       super("Clear", MainFrame.getIcon("crystal-clear-action-button-cancel"));
1783       super.putValue(SHORT_DESCRIPTION, "Clear the query text box.");
1784       super.putValue(MNEMONIC_KEY, KeyEvent.VK_BACK_SPACE);
1785     }
1786 
1787     @Override
1788     public void actionPerformed(ActionEvent ae) {
1789       queryTextArea.setText("");
1790       queryTextArea.requestFocusInWindow();
1791     }
1792   }
1793 
1794   /**
1795    * Finds out the newly created query and execute it.
1796    */
1797   protected class ExecuteQueryAction extends AbstractAction {
1798 
1799     public ExecuteQueryAction() {
1800       super("Search", MainFrame.getIcon("crystal-clear-app-xmag"));
1801       super.putValue(SHORT_DESCRIPTION, "Execute the query.");
1802       super.putValue(MNEMONIC_KEY, KeyEvent.VK_ENTER);
1803     }
1804 
1805     @Override
1806     public void actionPerformed(ActionEvent ae) {
1807 
1808       // disable all mouse and key events and display the wait cursor
1809       final BlockingGlassPane blockingGlassPane = new BlockingGlassPane();
1810       LuceneDataStoreSearchGUI.this.getRootPane().setGlassPane(
1811               blockingGlassPane);
1812       blockingGlassPane.block(true);
1813 
1814       // clear the result table and center view
1815       if(results.size() 0) {
1816         results.clear();
1817         resultTableModel.fireTableDataChanged();
1818       else {
1819         updateStackView();
1820       }
1821 
1822       // set the search parameters
1823       Map<String, Object> parameters = searcher.getParameters();
1824       if(parameters == nullparameters = new HashMap<String, Object>();
1825 
1826       if(target instanceof LuceneDataStoreImpl) {
1827         String corpus2SearchIn =
1828                 corpusToSearchIn.getSelectedItem().equals(
1829                         Constants.ENTIRE_DATASTOREnull (String)corpusIds
1830                         .get(corpusToSearchIn.getSelectedIndex() 1);
1831         parameters.put(Constants.CORPUS_ID, corpus2SearchIn);
1832       }
1833 
1834       noOfResults =
1835               (numberOfResultsSlider.getValue() (numberOfResultsSlider
1836                       .getMaximum() 100))
1837                       ? -1
1838                       ((Number)numberOfResultsSlider.getValue()).intValue();
1839       int contextWindow = ((Number)contextSizeSlider.getValue()).intValue();
1840       String query = queryTextArea.getText().trim();
1841       java.util.regex.Pattern pattern =
1842               java.util.regex.Pattern.compile("[\\{, ]([^\\{=]+)==");
1843       Matcher matcher = pattern.matcher(query);
1844       int start = 0;
1845       while(matcher.find(start)) {
1846         start = matcher.end(1)// avoid infinite loop
1847         int row = findStackRow(SHORTCUT, matcher.group(1));
1848         if(row >= 0) {
1849           // rewrite the query to put the long form of the
1850           // shortcut found
1851           query =
1852                   query.substring(0, matcher.start(1))
1853                           + stackRows[row][ANNOTATION_TYPE"."
1854                           + stackRows[row][FEATURE]
1855                           + query.substring(matcher.end(1));
1856           matcher = pattern.matcher(query);
1857         }
1858       }
1859 
1860       parameters.put(Constants.CONTEXT_WINDOW, contextWindow);
1861       if(annotationSetsToSearchIn.getSelectedItem().equals(Constants.ALL_SETS)) {
1862         parameters.remove(Constants.ANNOTATION_SET_ID);
1863       else {
1864         String annotationSet =
1865                 (String)annotationSetsToSearchIn.getSelectedItem();
1866         parameters.put(Constants.ANNOTATION_SET_ID, annotationSet);
1867       }
1868 
1869       // execute the search
1870       final String queryF = query;
1871       final Map<String, Object> parametersF = parameters;
1872       SwingUtilities.invokeLater(new Runnable() {
1873         @Override
1874         public void run() {
1875           try {
1876             if(searcher.search(queryF, parametersF)) {
1877               searcher.next(noOfResults);
1878             }
1879 
1880           catch(SearchException se) {
1881             errorOnLastQuery = true;
1882             GridBagConstraints gbc = new GridBagConstraints();
1883             gbc.gridwidth = GridBagConstraints.REMAINDER;
1884             centerPanel.removeAll();
1885             String[] message = se.getMessage().split("\\n");
1886             if(message.length == 1) {
1887               // some errors to fix into QueryParser
1888               se.printStackTrace();
1889               return;
1890             }
1891             // message[0] contains the Java error
1892             JTextArea jta = new JTextArea(message[1]);
1893             jta.setForeground(Color.RED);
1894             centerPanel.add(jta, gbc);
1895             jta = new JTextArea(message[2]);
1896             if(message.length > 3) {
1897               jta.setText(message[2"\n" + message[3]);
1898             }
1899             jta.setFont(new Font("Monospaced", Font.PLAIN, 12));
1900             centerPanel.add(jta, gbc);
1901 
1902           catch(Exception e) {
1903             e.printStackTrace();
1904 
1905           finally {
1906             updateViews();
1907             pageOfResults = 1;
1908             titleResults.setText("Page " + pageOfResults + " ("
1909                     + searcher.getHits().length + " results)");
1910             queryTextArea.requestFocusInWindow();
1911             SwingUtilities.invokeLater(new Runnable() {
1912               @Override
1913               public void run() {
1914                 blockingGlassPane.block(false);
1915               }
1916             });
1917           }
1918         }
1919       });
1920     }
1921   }
1922 
1923   /**
1924    * Refresh annotations sets and features.
1925    */
1926   protected class RefreshAnnotationSetsAndFeaturesAction extends AbstractAction {
1927 
1928     public RefreshAnnotationSetsAndFeaturesAction() {
1929       super("",MainFrame.getIcon("crystal-clear-action-reload"));
1930       super.putValue(SHORT_DESCRIPTION, "Reloads annotations types.");
1931 
1932       // assigning F5 as the short cut key for the refresh button
1933       super.putValue(MNEMONIC_KEY, KeyEvent.VK_F5);
1934     }
1935 
1936     @Override
1937     public void actionPerformed(ActionEvent ae) {
1938 
1939       // disable all mouse and key events and display the wait cursor
1940       final BlockingGlassPane blockingGlassPane = new BlockingGlassPane();
1941       LuceneDataStoreSearchGUI.this.getRootPane().setGlassPane(
1942               blockingGlassPane);
1943       blockingGlassPane.block(true);
1944 
1945       SwingUtilities.invokeLater(new Runnable() {
1946         @Override
1947         public void run() {
1948           try {
1949             updateSetsTypesAndFeatures();
1950           catch(Exception e) {
1951             e.printStackTrace();
1952           finally {
1953             SwingUtilities.invokeLater(new Runnable() {
1954               @Override
1955               public void run() {
1956                 blockingGlassPane.block(false);
1957               }
1958             });
1959           }
1960         }
1961       });
1962     }
1963   }
1964 
1965   /**
1966    * Finds out the next few results.
1967    */
1968   protected class NextResultsAction extends AbstractAction {
1969 
1970     public NextResultsAction() {
1971       super("Next page of " + numberOfResultsSlider.getValue() " results",
1972               MainFrame.getIcon("crystal-clear-action-loopnone"));
1973       super.putValue(SHORT_DESCRIPTION, "Show next page of results.");
1974       super.putValue(MNEMONIC_KEY, KeyEvent.VK_RIGHT);
1975     }
1976 
1977     @Override
1978     public void actionPerformed(ActionEvent ae) {
1979 
1980       // disable all mouse and key events and display the wait cursor
1981       final BlockingGlassPane blockingGlassPane = new BlockingGlassPane();
1982       LuceneDataStoreSearchGUI.this.getRootPane().setGlassPane(
1983               blockingGlassPane);
1984       blockingGlassPane.block(true);
1985 
1986       // clear the results table and center view
1987       if(results.size() 0) {
1988         results.clear();
1989         resultTableModel.fireTableDataChanged();
1990       else {
1991         updateStackView();
1992       }
1993 
1994       SwingUtilities.invokeLater(new Runnable() {
1995         @Override
1996         public void run() {
1997           noOfResults = ((Number)numberOfResultsSlider.getValue()).intValue();
1998           try {
1999             searcher.next(noOfResults);
2000 
2001           catch(Exception e) {
2002             e.printStackTrace();
2003 
2004           finally {
2005             updateViews();
2006             pageOfResults++;
2007             titleResults.setText("Page " + pageOfResults + " ("
2008                     + searcher.getHits().length + " results)");
2009             if(searcher.getHits().length < noOfResults) {
2010               nextResultsAction.setEnabled(false);
2011             }
2012             queryTextArea.requestFocusInWindow();
2013             SwingUtilities.invokeLater(new Runnable() {
2014               @Override
2015               public void run() {
2016                 blockingGlassPane.block(false);
2017               }
2018             });
2019           }
2020         }
2021       });
2022     }
2023   }
2024 
2025   /**
2026    * Show the configuration window for the annotation stack view.
2027    */
2028   protected class ConfigureStackViewAction extends AbstractAction {
2029 
2030     public ConfigureStackViewAction() {
2031       super("Configure", MainFrame.getIcon("crystal-clear-action-edit-add"));
2032       super.putValue(SHORT_DESCRIPTION, "Configure the view");
2033       super.putValue(MNEMONIC_KEY, KeyEvent.VK_LEFT);
2034     }
2035 
2036     @Override
2037     public void actionPerformed(ActionEvent e) {
2038       // to avoid having the frame behind a window
2039       configureStackViewFrame.setVisible(false);
2040       configureStackViewFrame.setVisible(true);
2041     }
2042   }
2043 
2044   /**
2045    * Add at the caret position or replace the selection in the query
2046    * according to the text row value left clicked.
2047    */
2048   public class TextMouseListener extends StackMouseListener {
2049 
2050     public TextMouseListener() {
2051     }
2052 
2053     public TextMouseListener(String text) {
2054       this.text = text;
2055     }
2056 
2057     @Override
2058     public MouseInputAdapter createListener(String... parameters) {
2059       return new TextMouseListener(parameters[0]);
2060     }
2061 
2062     @Override
2063     public void mouseClicked(MouseEvent me) {
2064       if(!me.isPopupTrigger() && me.getButton() == MouseEvent.BUTTON1
2065               && me.getClickCount() == 2) {
2066         int caretPosition = queryTextArea.getCaretPosition();
2067         String query = queryTextArea.getText();
2068         String queryMiddle = text;
2069         String queryLeft =
2070                 (queryTextArea.getSelectionStart() == queryTextArea
2071                         .getSelectionEnd())
2072                         ? query.substring(0, caretPosition)
2073                         : query.substring(0, queryTextArea.getSelectionStart());
2074         String queryRight =
2075                 (queryTextArea.getSelectionStart() == queryTextArea
2076                         .getSelectionEnd()) ? query.substring(caretPosition,
2077                         query.length()) : query.substring(
2078                         queryTextArea.getSelectionEnd(), query.length());
2079         queryTextArea.setText(queryLeft + queryMiddle + queryRight);
2080       }
2081     }
2082 
2083     @Override
2084     public void mouseEntered(MouseEvent e) {
2085       Component component = e.getComponent();
2086       if(!isTooltipSet && component instanceof JLabel) {
2087         isTooltipSet = true;
2088         JLabel label = (JLabel)component;
2089         Pattern result =
2090                 (Pattern)results.get(resultTable.rowViewToModel(resultTable
2091                         .getSelectionModel().getLeadSelectionIndex()));
2092         label.setToolTipText("The query that matched this "
2093                 "expression was: " + result.getQueryString() ".");
2094       }
2095     }
2096 
2097     String text;
2098 
2099     boolean isTooltipSet = false;
2100   }
2101 
2102   /**
2103    * Modifies the query or displays statistics according to the
2104    * annotation rectangle clicked.
2105    */
2106   protected class AnnotationMouseListener extends StackMouseListener {
2107 
2108     String type;
2109 
2110     String feature;
2111 
2112     String text;
2113 
2114     String description;
2115 
2116     String toolTip;
2117 
2118     String descriptionTemplate;
2119 
2120     String toolTipTemplate;
2121 
2122     JPopupMenu mousePopup;
2123 
2124     JMenuItem menuItem;
2125 
2126     final String corpusID = (corpusToSearchIn.getSelectedItem()
2127             .equals(Constants.ENTIRE_DATASTORE)) null (String)corpusIds
2128             .get(corpusToSearchIn.getSelectedIndex() 1);
2129 
2130     final String annotationSetID = (annotationSetsToSearchIn.getSelectedItem()
2131             .equals(Constants.ALL_SETS))
2132             null
2133             (String)annotationSetsToSearchIn.getSelectedItem();
2134 
2135     final String corpusName = (String)corpusToSearchIn.getSelectedItem();
2136 
2137     final String annotationSetName = (String)annotationSetsToSearchIn
2138             .getSelectedItem();
2139 
2140     ToolTipManager toolTipManager = ToolTipManager.sharedInstance();
2141 
2142     int dismissDelay, initialDelay, reshowDelay;
2143 
2144     boolean enabled;
2145 
2146     boolean isTooltipSet = false;
2147 
2148     public AnnotationMouseListener() {
2149     }
2150 
2151     public AnnotationMouseListener(String type, String feature, String text) {
2152       this.type = type;
2153       this.feature = feature;
2154       this.text = text;
2155       String value;
2156       if(text.replace("\\s""").length() 20) {
2157         value = text.replace("\\s""").substring(020("...");
2158       else {
2159         value = text.replace("\\s""");
2160       }
2161       this.descriptionTemplate =
2162               type + "." + feature + "==\"" + value + "\" (kind)";
2163       this.toolTipTemplate =
2164               "Statistics in kind" "<br>on Corpus: " + corpusName
2165                       "<br>and Annotation Set: " + annotationSetName
2166                       "<br>for the query: " + results.get(0).getQueryString();
2167     }
2168 
2169     public AnnotationMouseListener(String type) {
2170       this.type = type;
2171     }
2172 
2173     @Override
2174     public MouseInputAdapter createListener(String... parameters) {
2175       switch(parameters.length) {
2176         case 3:
2177           return new AnnotationMouseListener(parameters[1]);
2178         case 5:
2179           return new AnnotationMouseListener(parameters[1], parameters[2],
2180                   parameters[3]);
2181         default:
2182           return null;
2183       }
2184     }
2185 
2186     @Override
2187     public void mouseEntered(MouseEvent e) {
2188       dismissDelay = toolTipManager.getDismissDelay();
2189       initialDelay = toolTipManager.getInitialDelay();
2190       reshowDelay = toolTipManager.getReshowDelay();
2191       enabled = toolTipManager.isEnabled();
2192       Component component = e.getComponent();
2193       if(feature != null && !isTooltipSet && component instanceof JLabel) {
2194         isTooltipSet = true;
2195         JLabel label = (JLabel)component;
2196         String toolTip = label.getToolTipText();
2197         toolTip =
2198                 (toolTip == null || toolTip.equals("")) "" : toolTip
2199                         .replaceAll("</?html>""""<br>";
2200         toolTip = "<html>" + toolTip + "Right click to get statistics.</html>";
2201         label.setToolTipText(toolTip);
2202       }
2203       // make the tooltip indefinitely shown when the mouse is over
2204       toolTipManager.setDismissDelay(Integer.MAX_VALUE);
2205       toolTipManager.setInitialDelay(0);
2206       toolTipManager.setReshowDelay(0);
2207       toolTipManager.setEnabled(true);
2208     }
2209 
2210     @Override
2211     public void mouseExited(MouseEvent e) {
2212       toolTipManager.setDismissDelay(dismissDelay);
2213       toolTipManager.setInitialDelay(initialDelay);
2214       toolTipManager.setReshowDelay(reshowDelay);
2215       toolTipManager.setEnabled(enabled);
2216     }
2217 
2218     @Override
2219     public void mousePressed(MouseEvent e) {
2220       if(e.isPopupTrigger() && type != null && feature != null) {
2221         createPopup(e);
2222         mousePopup.show(e.getComponent(), e.getX(), e.getY());
2223       else if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 2) {
2224         updateQuery();
2225       }
2226     }
2227 
2228     @Override
2229     public void mouseReleased(MouseEvent e) {
2230       if(e.isPopupTrigger() && type != null && feature != null) {
2231         createPopup(e);
2232         mousePopup.show(e.getComponent(), e.getX(), e.getY());
2233       }
2234     }
2235 
2236     private void updateQuery() {
2237       int caretPosition = queryTextArea.getCaretPosition();
2238       String query = queryTextArea.getText();
2239       String queryMiddle;
2240 
2241       if(type != null && feature != null) {
2242         int row = findStackRow(ANNOTATION_TYPE, type, FEATURE, feature);
2243         if(row >= && !stackRows[row][SHORTCUT].equals("")) {
2244           queryMiddle = "{" + stackRows[row][SHORTCUT"==\"" + text + "\"}";
2245         else {
2246           queryMiddle = "{" + type + "." + feature + "==\"" + text + "\"}";
2247         }
2248       else if(type != null) {
2249         queryMiddle = "{" + type + "}";
2250 
2251       else {
2252         queryMiddle = text;
2253       }
2254       String queryLeft =
2255               (queryTextArea.getSelectionStart() == queryTextArea
2256                       .getSelectionEnd())
2257                       ? query.substring(0, caretPosition)
2258                       : query.substring(0, queryTextArea.getSelectionStart());
2259       String queryRight =
2260               (queryTextArea.getSelectionStart() == queryTextArea
2261                       .getSelectionEnd()) ? query.substring(caretPosition,
2262                       query.length()) : query.substring(
2263                       queryTextArea.getSelectionEnd(), query.length());
2264       queryTextArea.setText(queryLeft + queryMiddle + queryRight);
2265     }
2266 
2267     private int checkStatistics() {
2268       boolean found = false;
2269       int numRow = 0;
2270       // check if this statistics doesn't already exist in the table
2271       for(int row = 0; row < oneRowStatisticsTable.getRowCount(); row++) {
2272         String oldDescription =
2273                 (String)oneRowStatisticsTable.getValueAt(row, 0);
2274         String oldToolTip =
2275                 oneRowStatisticsTableToolTips.get(oneRowStatisticsTable
2276                         .rowViewToModel(numRow));
2277         if(oldDescription.equals(description&& oldToolTip.equals(toolTip)) {
2278           found = true;
2279           break;
2280         }
2281         numRow++;
2282       }
2283       return found ? numRow : -1;
2284     }
2285 
2286     private void addStatistics(String kind, int count, int numRow,
2287             final MouseEvent e) {
2288       JLabel label = (JLabel)e.getComponent();
2289       if(!label.getToolTipText().contains(kind)) {
2290         // add the statistics to the tooltip
2291         String toolTip = label.getToolTipText();
2292         toolTip = toolTip.replaceAll("</?html>""");
2293         toolTip = kind + " = " + count + "<br>" + toolTip;
2294         toolTip = "<html>" + toolTip + "</html>";
2295         label.setToolTipText(toolTip);
2296       }
2297       if(bottomSplitPane.getDividerLocation()
2298               / bottomSplitPane.getSize().getWidth() 0.90) {
2299         // select the row in the statistics table
2300         statisticsTabbedPane.setSelectedIndex(1);
2301         oneRowStatisticsTable.setRowSelectionInterval(numRow, numRow);
2302         oneRowStatisticsTable.scrollRectToVisible(oneRowStatisticsTable
2303                 .getCellRect(numRow, 0true));
2304       else {
2305         // display a tooltip
2306         JToolTip tip = label.createToolTip();
2307         tip.setTipText(kind + " = " + count);
2308         PopupFactory popupFactory = PopupFactory.getSharedInstance();
2309         final Popup tipWindow =
2310                 popupFactory.getPopup(label, tip, e.getX()
2311                         + e.getComponent().getLocationOnScreen().x, e.getY()
2312                         + e.getComponent().getLocationOnScreen().y);
2313         tipWindow.show();
2314         Date timeToRun = new Date(System.currentTimeMillis() 2000);
2315         Timer timer = new Timer("Annic statistics hide tooltip timer"true);
2316         timer.schedule(new TimerTask() {
2317           @Override
2318           public void run() {
2319             // hide the tooltip after 2 seconds
2320             tipWindow.hide();
2321           }
2322         }, timeToRun);
2323       }
2324     }
2325 
2326     private void createPopup(final MouseEvent e) {
2327       mousePopup = new JPopupMenu();
2328 
2329       menuItem = new JMenuItem("Occurrences in datastore");
2330       menuItem.addActionListener(new ActionListener() {
2331         @Override
2332         public void actionPerformed(ActionEvent ie) {
2333           description = descriptionTemplate.replaceFirst("kind""datastore");
2334           toolTip = toolTipTemplate.replaceFirst("kind""datastore");
2335           int count;
2336           int numRow = checkStatistics();
2337           if(numRow == -1) {
2338             try // retrieves the number of occurrences
2339               count =
2340                       searcher.freq(corpusID, annotationSetID, type, feature,
2341                               text);
2342             catch(SearchException se) {
2343               se.printStackTrace();
2344               return;
2345             }
2346             oneRowStatisticsTableModel.addRow(new Object[] {description, count,
2347                 ""});
2348             oneRowStatisticsTableToolTips.add(toolTip);
2349             numRow =
2350                     oneRowStatisticsTable.rowModelToView(oneRowStatisticsTable
2351                             .getRowCount() 1);
2352           else {
2353             count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2354           }
2355           addStatistics("datastore", count, numRow, e);
2356         }
2357       });
2358       mousePopup.add(menuItem);
2359 
2360       menuItem = new JMenuItem("Occurrences in matches");
2361       menuItem.addActionListener(new ActionListener() {
2362         @Override
2363         public void actionPerformed(ActionEvent ie) {
2364           description = descriptionTemplate.replaceFirst("kind""matches");
2365           toolTip = toolTipTemplate.replaceFirst("kind""matches");
2366           int count;
2367           int numRow = checkStatistics();
2368           if(numRow == -1) {
2369             try // retrieves the number of occurrences
2370               count = searcher.freq(results, type, feature, text, true, false);
2371             catch(SearchException se) {
2372               se.printStackTrace();
2373               return;
2374             }
2375             oneRowStatisticsTableModel.addRow(new Object[] {description, count,
2376                 ""});
2377             oneRowStatisticsTableToolTips.add(toolTip);
2378             numRow =
2379                     oneRowStatisticsTable.rowModelToView(oneRowStatisticsTable
2380                             .getRowCount() 1);
2381           else {
2382             count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2383           }
2384           addStatistics("matches", count, numRow, e);
2385         }
2386       });
2387       mousePopup.add(menuItem);
2388 
2389       menuItem = new JMenuItem("Occurrences in contexts");
2390       menuItem.addActionListener(new ActionListener() {
2391         @Override
2392         public void actionPerformed(ActionEvent ie) {
2393           description = descriptionTemplate.replaceFirst("kind""contexts");
2394           toolTip = toolTipTemplate.replaceFirst("kind""contexts");
2395           int count;
2396           int numRow = checkStatistics();
2397           if(numRow == -1) {
2398             try // retrieves the number of occurrences
2399               count = searcher.freq(results, type, feature, text, false, true);
2400             catch(SearchException se) {
2401               se.printStackTrace();
2402               return;
2403             }
2404             oneRowStatisticsTableModel.addRow(new Object[] {description, count,
2405                 ""});
2406             oneRowStatisticsTableToolTips.add(toolTip);
2407             numRow =
2408                     oneRowStatisticsTable.rowModelToView(oneRowStatisticsTable
2409                             .getRowCount() 1);
2410           else {
2411             count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2412           }
2413           addStatistics("contexts", count, numRow, e);
2414         }
2415       });
2416       mousePopup.add(menuItem);
2417 
2418       menuItem = new JMenuItem("Occurrences in matches+contexts");
2419       menuItem.addActionListener(new ActionListener() {
2420         @Override
2421         public void actionPerformed(ActionEvent ie) {
2422           description = descriptionTemplate.replaceFirst("kind""mch+ctxt");
2423           toolTip = toolTipTemplate.replaceFirst("kind""matches+contexts");
2424           int count;
2425           int numRow = checkStatistics();
2426           if(numRow == -1) {
2427             try // retrieves the number of occurrences
2428               count = searcher.freq(results, type, feature, text, true, true);
2429             catch(SearchException se) {
2430               se.printStackTrace();
2431               return;
2432             }
2433             oneRowStatisticsTableModel.addRow(new Object[] {description, count,
2434                 ""});
2435             oneRowStatisticsTableToolTips.add(toolTip);
2436             numRow =
2437                     oneRowStatisticsTable.rowModelToView(oneRowStatisticsTable
2438                             .getRowCount() 1);
2439           else {
2440             count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2441           }
2442           addStatistics("matches+contexts", count, numRow, e);
2443         }
2444       });
2445       mousePopup.add(menuItem);
2446     }
2447   }
2448 
2449   /**
2450    * Displays statistics according to the stack row header
2451    * right-clicked.
2452    */
2453   protected class HeaderMouseListener extends StackMouseListener {
2454 
2455     String type;
2456 
2457     String feature;
2458 
2459     String description;
2460 
2461     String toolTip;
2462 
2463     String descriptionTemplate;
2464 
2465     String toolTipTemplate;
2466 
2467     JPopupMenu mousePopup;
2468 
2469     JMenuItem menuItem;
2470 
2471     XJTable table;
2472 
2473     JWindow popupWindow;
2474 
2475     int row;
2476 
2477     final String corpusID = (corpusToSearchIn.getSelectedItem()
2478             .equals(Constants.ENTIRE_DATASTORE)) null (String)corpusIds
2479             .get(corpusToSearchIn.getSelectedIndex() 1);
2480 
2481     final String annotationSetID = (annotationSetsToSearchIn.getSelectedItem()
2482             .equals(Constants.ALL_SETS))
2483             null
2484             (String)annotationSetsToSearchIn.getSelectedItem();
2485 
2486     final String corpusName = (String)corpusToSearchIn.getSelectedItem();
2487 
2488     final String annotationSetName = (String)annotationSetsToSearchIn
2489             .getSelectedItem();
2490 
2491     boolean isTooltipSet = false;
2492 
2493     public HeaderMouseListener() {
2494     }
2495 
2496     public HeaderMouseListener(String type, String feature) {
2497       this.type = type;
2498       this.feature = feature;
2499       this.descriptionTemplate = type + "." + feature + " (kind)";
2500       this.toolTipTemplate =
2501               "Statistics in kind" "<br>on Corpus: " + corpusName
2502                       "<br>and Annotation Set: " + annotationSetName
2503                       "<br>for the query: " + results.get(0).getQueryString();
2504       init();
2505     }
2506 
2507     public HeaderMouseListener(String type) {
2508       this.type = type;
2509       this.descriptionTemplate = type + " (kind)";
2510       this.toolTipTemplate =
2511               "Statistics in kind" "<br>on Corpus: " + corpusName
2512                       "<br>and Annotation Set: " + annotationSetName
2513                       "<br>for the query: " + results.get(0).getQueryString();
2514       init();
2515     }
2516 
2517     void init() {
2518       addAncestorListener(new AncestorListener() {
2519         @Override
2520         public void ancestorMoved(AncestorEvent event) {
2521         }
2522 
2523         @Override
2524         public void ancestorAdded(AncestorEvent event) {
2525         }
2526 
2527         @Override
2528         public void ancestorRemoved(AncestorEvent event) {
2529           // no parent so need to be disposed explicitly
2530           if(popupWindow != null) {
2531             popupWindow.dispose();
2532           }
2533         }
2534       });
2535       row =
2536               findStackRow(ANNOTATION_TYPE, type, FEATURE, (feature == null
2537                       ""
2538                       : feature));
2539     }
2540 
2541     @Override
2542     public MouseInputAdapter createListener(String... parameters) {
2543       switch(parameters.length) {
2544         case 1:
2545           return new HeaderMouseListener(parameters[0]);
2546         case 2:
2547           return new HeaderMouseListener(parameters[0], parameters[1]);
2548         default:
2549           return null;
2550       }
2551     }
2552 
2553     @Override
2554     public void mouseEntered(MouseEvent e) {
2555       Component component = e.getComponent();
2556       if(!isTooltipSet && component instanceof JLabel) {
2557         isTooltipSet = true;
2558         JLabel label = (JLabel)component;
2559         String shortcut = "";
2560         if(feature != null) {
2561           int row =
2562                   resultTable.rowViewToModel(resultTable.getSelectionModel()
2563                           .getLeadSelectionIndex());
2564           if(!stackRows[row][SHORTCUT].equals("")) {
2565             shortcut = "Shortcut for " + type + "." + feature + ".<br>";
2566           }
2567         }
2568         label.setToolTipText("<html>" + shortcut
2569                 "Double click to choose annotation feature.<br>"
2570                 "Right click to get statistics.</html>");
2571       }
2572     }
2573 
2574     // when double clicked shows a list of features for this annotation
2575     // type
2576     @Override
2577     public void mouseClicked(MouseEvent e) {
2578       if(popupWindow != null && popupWindow.isVisible()) {
2579         popupWindow.dispose();
2580         return;
2581       }
2582       if(e.getButton() != MouseEvent.BUTTON1 || e.getClickCount() != 2) {
2583         return;
2584       }
2585       // get a list of features for the current annotation type
2586       TreeSet<String> features = new TreeSet<String>();
2587       if(populatedAnnotationTypesAndFeatures.containsKey(type)) {
2588         // this annotation type still exists in the datastore
2589         features.addAll(populatedAnnotationTypesAndFeatures.get(type));
2590       }
2591       features.add(" ");
2592       // create the list component
2593       final JList<String> list = new JList<String>(features.toArray(new String[features.size()]));
2594       list.setVisibleRowCount(Math.min(8, features.size()));
2595       list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
2596       list.setBackground(Color.WHITE);
2597       list.addMouseListener(new MouseAdapter() {
2598         @Override
2599         public void mouseClicked(MouseEvent e) {
2600           if(e.getClickCount() == 1) {
2601             String newFeature = list.getSelectedValue();
2602             if(newFeature.equals(" ")) {
2603               newFeature = "";
2604             }
2605             stackRows[row][FEATURE= newFeature;
2606             saveStackViewConfiguration();
2607             popupWindow.setVisible(false);
2608             popupWindow.dispose();
2609             updateStackView();
2610           }
2611         }
2612       });
2613       // create the window that will contain the list
2614       popupWindow = new JWindow();
2615       popupWindow.addKeyListener(new KeyAdapter() {
2616         @Override
2617         public void keyPressed(KeyEvent e) {
2618           if(e.getKeyCode() == KeyEvent.VK_ESCAPE) {
2619             popupWindow.setVisible(false);
2620             popupWindow.dispose();
2621           }
2622         }
2623       });
2624       popupWindow.add(new JScrollPane(list));
2625       Component component = e.getComponent();
2626       popupWindow.setBounds(
2627               component.getLocationOnScreen().x,
2628               component.getLocationOnScreen().y + component.getHeight(),
2629               component.getWidth(),
2630               Math.min(* component.getHeight(),
2631                       features.size() * component.getHeight()));
2632       popupWindow.pack();
2633       popupWindow.setVisible(true);
2634       SwingUtilities.invokeLater(new Runnable() {
2635         @Override
2636         public void run() {
2637           String newFeature = stackRows[row][FEATURE];
2638           if(newFeature.equals("")) {
2639             newFeature = " ";
2640           }
2641           list.setSelectedValue(newFeature, true);
2642           popupWindow.requestFocusInWindow();
2643         }
2644       });
2645     }
2646 
2647     @Override
2648     public void mousePressed(MouseEvent e) {
2649       if(e.isPopupTrigger()) {
2650         createPopup(e);
2651         mousePopup.show(e.getComponent(), e.getX(), e.getY());
2652       }
2653     }
2654 
2655     @Override
2656     public void mouseReleased(MouseEvent e) {
2657       if(e.isPopupTrigger()) {
2658         createPopup(e);
2659         mousePopup.show(e.getComponent(), e.getX(), e.getY());
2660       }
2661     }
2662 
2663     private int checkStatistics() {
2664       boolean found = false;
2665       int numRow = 0;
2666       // check if this statistics doesn't already exist in the table
2667       for(int row = 0; row < oneRowStatisticsTable.getRowCount(); row++) {
2668         String oldDescription =
2669                 (String)oneRowStatisticsTable.getValueAt(row, 0);
2670         String oldToolTip =
2671                 oneRowStatisticsTableToolTips.get(oneRowStatisticsTable
2672                         .rowViewToModel(numRow));
2673         if(oldDescription.equals(description&& oldToolTip.equals(toolTip)) {
2674           found = true;
2675           break;
2676         }
2677         numRow++;
2678       }
2679       return found ? numRow : -1;
2680     }
2681 
2682     private void addStatistics(String kind, int count, int numRow,
2683             final MouseEvent e) {
2684       JLabel label = (JLabel)e.getComponent();
2685       if(!label.getToolTipText().contains(kind)) {
2686         // add the statistics to the tooltip
2687         String toolTip = label.getToolTipText();
2688         toolTip = toolTip.replaceAll("</?html>""");
2689         toolTip = kind + " = " + count + "<br>" + toolTip;
2690         toolTip = "<html>" + toolTip + "</html>";
2691         label.setToolTipText(toolTip);
2692       }
2693       if(bottomSplitPane.getDividerLocation()
2694               / bottomSplitPane.getSize().getWidth() 0.90) {
2695         // select the row in the statistics table
2696         statisticsTabbedPane.setSelectedIndex(1);
2697         oneRowStatisticsTable.setRowSelectionInterval(numRow, numRow);
2698         oneRowStatisticsTable.scrollRectToVisible(oneRowStatisticsTable
2699                 .getCellRect(numRow, 0true));
2700       else {
2701         // display a tooltip
2702         JToolTip tip = label.createToolTip();
2703         tip.setTipText(kind + " = " + count);
2704         PopupFactory popupFactory = PopupFactory.getSharedInstance();
2705         final Popup tipWindow =
2706                 popupFactory.getPopup(label, tip, e.getX()
2707                         + e.getComponent().getLocationOnScreen().x, e.getY()
2708                         + e.getComponent().getLocationOnScreen().y);
2709         tipWindow.show();
2710         Date timeToRun = new Date(System.currentTimeMillis() 2000);
2711         Timer timer = new Timer("Annic statistics hide tooltip timer"true);
2712         timer.schedule(new TimerTask() {
2713           @Override
2714           public void run() {
2715             // hide the tooltip after 2 seconds
2716             tipWindow.hide();
2717           }
2718         }, timeToRun);
2719       }
2720     }
2721 
2722     private void createPopup(final MouseEvent e) {
2723       mousePopup = new JPopupMenu();
2724 
2725       if(type != null && feature != null) {
2726 
2727         // count values for one Feature of an Annotation type
2728 
2729         menuItem = new JMenuItem("Occurrences in datastore");
2730         menuItem.addActionListener(new ActionListener() {
2731           @Override
2732           public void actionPerformed(ActionEvent ie) {
2733             description = descriptionTemplate.replaceFirst("kind""datastore");
2734             toolTip = toolTipTemplate.replaceFirst("kind""datastore");
2735             int count;
2736             int numRow = checkStatistics();
2737             if(numRow == -1) {
2738               try // retrieves the number of occurrences
2739                 count = searcher.freq(corpusID, annotationSetID, type, feature);
2740               catch(SearchException se) {
2741                 se.printStackTrace();
2742                 return;
2743               }
2744               oneRowStatisticsTableModel.addRow(new Object[] {description,
2745                   count, ""});
2746               oneRowStatisticsTableToolTips.add(toolTip);
2747               numRow =
2748                       oneRowStatisticsTable
2749                               .rowModelToView(oneRowStatisticsTable
2750                                       .getRowCount() 1);
2751             else {
2752               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2753             }
2754             addStatistics("datastore", count, numRow, e);
2755           }
2756         });
2757         mousePopup.add(menuItem);
2758 
2759         menuItem = new JMenuItem("Occurrences in matches");
2760         menuItem.addActionListener(new ActionListener() {
2761           @Override
2762           public void actionPerformed(ActionEvent ie) {
2763             description = descriptionTemplate.replaceFirst("kind""matches");
2764             toolTip = toolTipTemplate.replaceFirst("kind""matches");
2765             int count;
2766             int numRow = checkStatistics();
2767             if(numRow == -1) {
2768               try // retrieves the number of occurrences
2769                 count =
2770                         searcher.freq(results, type, feature, null, true, false);
2771               catch(SearchException se) {
2772                 se.printStackTrace();
2773                 return;
2774               }
2775               oneRowStatisticsTableModel.addRow(new Object[] {description,
2776                   count, ""});
2777               oneRowStatisticsTableToolTips.add(toolTip);
2778               numRow =
2779                       oneRowStatisticsTable
2780                               .rowModelToView(oneRowStatisticsTable
2781                                       .getRowCount() 1);
2782             else {
2783               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2784             }
2785             addStatistics("matches", count, numRow, e);
2786           }
2787         });
2788         mousePopup.add(menuItem);
2789 
2790         menuItem = new JMenuItem("Occurrences in contexts");
2791         menuItem.addActionListener(new ActionListener() {
2792           @Override
2793           public void actionPerformed(ActionEvent ie) {
2794             description = descriptionTemplate.replaceFirst("kind""contexts");
2795             toolTip = toolTipTemplate.replaceFirst("kind""contexts");
2796             int count;
2797             int numRow = checkStatistics();
2798             if(numRow == -1) {
2799               try // retrieves the number of occurrences
2800                 count =
2801                         searcher.freq(results, type, feature, null, false, true);
2802               catch(SearchException se) {
2803                 se.printStackTrace();
2804                 return;
2805               }
2806               oneRowStatisticsTableModel.addRow(new Object[] {description,
2807                   count, ""});
2808               oneRowStatisticsTableToolTips.add(toolTip);
2809               numRow =
2810                       oneRowStatisticsTable
2811                               .rowModelToView(oneRowStatisticsTable
2812                                       .getRowCount() 1);
2813             else {
2814               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2815             }
2816             addStatistics("contexts", count, numRow, e);
2817           }
2818         });
2819         mousePopup.add(menuItem);
2820 
2821         menuItem = new JMenuItem("Occurrences in matches+contexts");
2822         menuItem.addActionListener(new ActionListener() {
2823           @Override
2824           public void actionPerformed(ActionEvent ie) {
2825             description = descriptionTemplate.replaceFirst("kind""mch+ctxt");
2826             toolTip = toolTipTemplate.replaceFirst("kind""matches+contexts");
2827             int count;
2828             int numRow = checkStatistics();
2829             if(numRow == -1) {
2830               try // retrieves the number of occurrences
2831                 count = searcher.freq(results, type, feature, null, true, true);
2832               catch(SearchException se) {
2833                 se.printStackTrace();
2834                 return;
2835               }
2836               oneRowStatisticsTableModel.addRow(new Object[] {description,
2837                   count, ""});
2838               oneRowStatisticsTableToolTips.add(toolTip);
2839               numRow =
2840                       oneRowStatisticsTable
2841                               .rowModelToView(oneRowStatisticsTable
2842                                       .getRowCount() 1);
2843             else {
2844               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
2845             }
2846             addStatistics("matches+contexts", count, numRow, e);
2847           }
2848         });
2849         mousePopup.add(menuItem);
2850 
2851         // count values for all Features of an Annotation Type
2852 
2853         mousePopup.addSeparator();
2854 
2855         menuItem = new JMenuItem("All values from matches");
2856         menuItem.addActionListener(new ActionListener() {
2857           @Override
2858           public void actionPerformed(ActionEvent ie) {
2859             Map<String, Integer> freqs;
2860             try // retrieves the number of occurrences
2861               freqs =
2862                       searcher.freqForAllValues(results, type, feature, true,
2863                               false);
2864             catch(SearchException se) {
2865               se.printStackTrace();
2866               return;
2867             }
2868             DefaultTableModel model = new DefaultTableModel();
2869             model.addColumn(type + '.' + feature + " (matches)");
2870             model.addColumn("Count");
2871             for(Map.Entry<String, Integer> map : freqs.entrySet()) {
2872               model.addRow(new Object[] {map.getKey(), map.getValue()});
2873             }
2874             table = new XJTable() {
2875               @Override
2876               public boolean isCellEditable(int rowIndex, int vColIndex) {
2877                 return false;
2878               }
2879             };
2880             table.setModel(model);
2881             table.setComparator(0, stringCollator);
2882             table.setComparator(1, integerComparator);
2883             statisticsTabbedPane.addTab(
2884                     String.valueOf(statisticsTabbedPane.getTabCount() 1),
2885                     null, new JScrollPane(table)"<html>Statistics in matches"
2886                             "<br>on Corpus: " + corpusName
2887                             "<br>and Annotation Set: " + annotationSetName
2888                             "<br>for the query: "
2889                             + results.get(0).getQueryString() "</html>");
2890             if(bottomSplitPane.getDividerLocation()
2891                     / bottomSplitPane.getSize().getWidth() 0.75) {
2892               bottomSplitPane.setDividerLocation(0.66);
2893             }
2894             statisticsTabbedPane.setSelectedIndex(statisticsTabbedPane
2895                     .getTabCount() 1);
2896           }
2897         });
2898         mousePopup.add(menuItem);
2899 
2900         menuItem = new JMenuItem("All values from contexts");
2901         menuItem.addActionListener(new ActionListener() {
2902           @Override
2903           public void actionPerformed(ActionEvent ie) {
2904             Map<String, Integer> freqs;
2905             try // retrieves the number of occurrences
2906               freqs =
2907                       searcher.freqForAllValues(results, type, feature, false,
2908                               true);
2909             catch(SearchException se) {
2910               se.printStackTrace();
2911               return;
2912             }
2913             DefaultTableModel model = new DefaultTableModel();
2914             model.addColumn(type + '.' + feature + " (contexts)");
2915             model.addColumn("Count");
2916             for(Map.Entry<String, Integer> map : freqs.entrySet()) {
2917               model.addRow(new Object[] {map.getKey(), map.getValue()});
2918             }
2919             table = new XJTable() {
2920               @Override
2921               public boolean isCellEditable(int rowIndex, int vColIndex) {
2922                 return false;
2923               }
2924             };
2925             table.setModel(model);
2926             table.setComparator(0, stringCollator);
2927             table.setComparator(1, integerComparator);
2928             statisticsTabbedPane.addTab(
2929                     String.valueOf(statisticsTabbedPane.getTabCount() 1),
2930                     null, new JScrollPane(table),
2931                     "<html>Statistics in contexts" "<br>on Corpus: "
2932                             + corpusName + "<br>and Annotation Set: "
2933                             + annotationSetName + "<br>for the query: "
2934                             + results.get(0).getQueryString() "</html>");
2935             if(bottomSplitPane.getDividerLocation()
2936                     / bottomSplitPane.getSize().getWidth() 0.75) {
2937               bottomSplitPane.setDividerLocation(0.66);
2938             }
2939             statisticsTabbedPane.setSelectedIndex(statisticsTabbedPane
2940                     .getTabCount() 1);
2941           }
2942         });
2943         mousePopup.add(menuItem);
2944 
2945         menuItem = new JMenuItem("All values from matches+contexts");
2946         menuItem.addActionListener(new ActionListener() {
2947           @Override
2948           public void actionPerformed(ActionEvent ie) {
2949             Map<String, Integer> freqs;
2950             try // retrieves the number of occurrences
2951               freqs =
2952                       searcher.freqForAllValues(results, type, feature, true,
2953                               true);
2954             catch(SearchException se) {
2955               se.printStackTrace();
2956               return;
2957             }
2958             DefaultTableModel model = new DefaultTableModel();
2959             model.addColumn(type + '.' + feature + " (mch+ctxt)");
2960             model.addColumn("Count");
2961             for(Map.Entry<String, Integer> map : freqs.entrySet()) {
2962               model.addRow(new Object[] {map.getKey(), map.getValue()});
2963             }
2964             table = new XJTable() {
2965               @Override
2966               public boolean isCellEditable(int rowIndex, int vColIndex) {
2967                 return false;
2968               }
2969             };
2970             table.setModel(model);
2971             table.setComparator(0, stringCollator);
2972             table.setComparator(1, integerComparator);
2973             statisticsTabbedPane.addTab(
2974                     String.valueOf(statisticsTabbedPane.getTabCount() 1),
2975                     null, new JScrollPane(table),
2976                     "<html>Statistics in matches+contexts" "<br>on Corpus: "
2977                             + corpusName + "<br>and Annotation Set: "
2978                             + annotationSetName + "<br>for the query: "
2979                             + results.get(0).getQueryString() "</html>");
2980             if(bottomSplitPane.getDividerLocation()
2981                     / bottomSplitPane.getSize().getWidth() 0.75) {
2982               bottomSplitPane.setDividerLocation(0.66);
2983             }
2984             statisticsTabbedPane.setSelectedIndex(statisticsTabbedPane
2985                     .getTabCount() 1);
2986           }
2987         });
2988         mousePopup.add(menuItem);
2989 
2990       else {
2991         // count values of one Annotation type
2992 
2993         menuItem = new JMenuItem("Occurrences in datastore");
2994         menuItem.addActionListener(new ActionListener() {
2995           @Override
2996           public void actionPerformed(ActionEvent ie) {
2997             description = descriptionTemplate.replaceFirst("kind""datastore");
2998             toolTip = toolTipTemplate.replaceFirst("kind""datastore");
2999             int count;
3000             int numRow = checkStatistics();
3001             if(numRow == -1) {
3002               try // retrieves the number of occurrences
3003                 count = searcher.freq(corpusID, annotationSetID, type);
3004               catch(SearchException se) {
3005                 se.printStackTrace();
3006                 return;
3007               }
3008               oneRowStatisticsTableModel.addRow(new Object[] {description,
3009                   count, ""});
3010               oneRowStatisticsTableToolTips.add(toolTip);
3011               numRow =
3012                       oneRowStatisticsTable
3013                               .rowModelToView(oneRowStatisticsTable
3014                                       .getRowCount() 1);
3015             else {
3016               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
3017             }
3018             addStatistics("datastore", count, numRow, e);
3019           }
3020         });
3021         mousePopup.add(menuItem);
3022 
3023         menuItem = new JMenuItem("Occurrences in matches");
3024         menuItem.addActionListener(new ActionListener() {
3025           @Override
3026           public void actionPerformed(ActionEvent ie) {
3027             description = descriptionTemplate.replaceFirst("kind""matches");
3028             toolTip = toolTipTemplate.replaceFirst("kind""matches");
3029             int count;
3030             int numRow = checkStatistics();
3031             if(numRow == -1) {
3032               try // retrieves the number of occurrences
3033                 count = searcher.freq(results, type, true, false);
3034               catch(SearchException se) {
3035                 se.printStackTrace();
3036                 return;
3037               }
3038               oneRowStatisticsTableModel.addRow(new Object[] {description,
3039                   count, ""});
3040               oneRowStatisticsTableToolTips.add(toolTip);
3041               numRow =
3042                       oneRowStatisticsTable
3043                               .rowModelToView(oneRowStatisticsTable
3044                                       .getRowCount() 1);
3045             else {
3046               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
3047             }
3048             addStatistics("matches", count, numRow, e);
3049           }
3050         });
3051         mousePopup.add(menuItem);
3052 
3053         menuItem = new JMenuItem("Occurrences in contexts");
3054         menuItem.addActionListener(new ActionListener() {
3055           @Override
3056           public void actionPerformed(ActionEvent ie) {
3057             description = descriptionTemplate.replaceFirst("kind""contexts");
3058             toolTip = toolTipTemplate.replaceFirst("kind""contexts");
3059             int count;
3060             int numRow = checkStatistics();
3061             if(numRow == -1) {
3062               try // retrieves the number of occurrences
3063                 count = searcher.freq(results, type, false, true);
3064               catch(SearchException se) {
3065                 se.printStackTrace();
3066                 return;
3067               }
3068               oneRowStatisticsTableModel.addRow(new Object[] {description,
3069                   count, ""});
3070               oneRowStatisticsTableToolTips.add(toolTip);
3071               numRow =
3072                       oneRowStatisticsTable
3073                               .rowModelToView(oneRowStatisticsTable
3074                                       .getRowCount() 1);
3075             else {
3076               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
3077             }
3078             addStatistics("contexts", count, numRow, e);
3079           }
3080         });
3081         mousePopup.add(menuItem);
3082 
3083         menuItem = new JMenuItem("Occurrences in matches+contexts");
3084         menuItem.addActionListener(new ActionListener() {
3085           @Override
3086           public void actionPerformed(ActionEvent ie) {
3087             description = descriptionTemplate.replaceFirst("kind""mch+ctxt");
3088             toolTip = toolTipTemplate.replaceFirst("kind""matches+contexts");
3089             int count;
3090             int numRow = checkStatistics();
3091             if(numRow == -1) {
3092               try // retrieves the number of occurrences
3093                 count = searcher.freq(results, type, true, true);
3094               catch(SearchException se) {
3095                 se.printStackTrace();
3096                 return;
3097               }
3098               oneRowStatisticsTableModel.addRow(new Object[] {description,
3099                   count, ""});
3100               oneRowStatisticsTableToolTips.add(toolTip);
3101               numRow =
3102                       oneRowStatisticsTable
3103                               .rowModelToView(oneRowStatisticsTable
3104                                       .getRowCount() 1);
3105             else {
3106               count = (Integer)oneRowStatisticsTable.getValueAt(numRow, 1);
3107             }
3108             addStatistics("matches+contexts", count, numRow, e);
3109           }
3110         });
3111         mousePopup.add(menuItem);
3112       }
3113     }
3114   }
3115 
3116   protected class ResultTableCellRenderer extends DefaultTableCellRenderer {
3117     @Override
3118     public Component getTableCellRendererComponent(JTable table, Object value,
3119             boolean isSelected, boolean hasFocus, int row, int column) {
3120       String text = (String)value;
3121       if(text == null) {
3122         text = "";
3123       }
3124       int colModel = resultTable.convertColumnIndexToModel(column);
3125       // cut text in the middle if too long
3126       switch(colModel) {
3127         case ResultTableModel.RESULT_COLUMN:
3128         case ResultTableModel.FEATURES_COLUMN:
3129           if(text.length() > ResultTableModel.MAX_COL_WIDTH) {
3130             text =
3131                     text.substring(0, ResultTableModel.MAX_COL_WIDTH / 2)
3132                             "..."
3133                             + text.substring(text.length()
3134                                     (ResultTableModel.MAX_COL_WIDTH / 2));
3135           }
3136           text = text.replaceAll("(?:\r?\n)|\r"" ");
3137           text = text.replaceAll("\t"" ");
3138           break;
3139         default:
3140           // do nothing
3141           break;
3142       }
3143       Component component =
3144               super.getTableCellRendererComponent(table, text, isSelected,
3145                       hasFocus, row, column);
3146       if(!(component instanceof JLabel)) {
3147         return component;
3148       }
3149       JLabel label = (JLabel)component;
3150       label.setHorizontalAlignment(SwingConstants.LEFT);
3151       String tip = null;
3152       // add tooltips
3153       switch(colModel) {
3154         case ResultTableModel.LEFT_CONTEXT_COLUMN:
3155           label.setHorizontalAlignment(SwingConstants.RIGHT);
3156           break;
3157         case ResultTableModel.RESULT_COLUMN:
3158         case ResultTableModel.FEATURES_COLUMN:
3159           tip = value != null (String)value : "";
3160           if(tip.length() > ResultTableModel.MAX_COL_WIDTH) {            
3161             if(tip.length() 1000) {
3162               tip =
3163                       tip.substring(01000 2"<br>...<br>"
3164                               + tip.substring(tip.length() (1000 2));
3165             }
3166             tip = tip.replaceAll("\\s*\n\\s*""<br>");
3167             tip = tip.replaceAll("\\s+"" ");
3168             tip =
3169                     "<html><table width=\""
3170                             (tip.length() 150 "500" "100%")
3171                             "\" border=\"0\" cellspacing=\"0\">" "<tr><td>"
3172                             + tip + "</td></tr>" "</table></html>";
3173           }
3174           if(colModel == ResultTableModel.RESULT_COLUMN) {
3175             label.setHorizontalAlignment(SwingConstants.CENTER);
3176           }
3177           break;
3178         default:
3179           // do nothing
3180           break;
3181       }
3182       label.setToolTipText(tip);
3183       return label;
3184     }
3185   }
3186 
3187   /**
3188    * Table model for the Result Tables.
3189    */
3190   protected class ResultTableModel extends AbstractTableModel {
3191 
3192     public ResultTableModel() {
3193       featureByTypeMap = new HashMap<String, String>();
3194     }
3195 
3196     @Override
3197     public int getRowCount() {
3198       return results.size();
3199     }
3200 
3201     @Override
3202     public int getColumnCount() {
3203       return COLUMN_COUNT;
3204     }
3205 
3206     @Override
3207     public String getColumnName(int columnIndex) {
3208       switch(columnIndex) {
3209         case LEFT_CONTEXT_COLUMN:
3210           return "Left context";
3211         case RESULT_COLUMN:
3212           return "Match";
3213         case RIGHT_CONTEXT_COLUMN:
3214           return "Right context";
3215         case FEATURES_COLUMN:
3216           return "Features";
3217         case QUERY_COLUMN:
3218           return "Query";
3219         case DOCUMENT_COLUMN:
3220           return "Document";
3221         case SET_COLUMN:
3222           return "Annotation set";
3223         default:
3224           return "?";
3225       }
3226     }
3227 
3228     @Override
3229     public Class<?> getColumnClass(int columnIndex) {
3230       return String.class;
3231     }
3232 
3233     @Override
3234     public boolean isCellEditable(int rowIndex, int columnIndex) {
3235       return false;
3236     }
3237 
3238     @Override
3239     public Object getValueAt(int rowIndex, int columnIndex) {
3240       Pattern result = (Pattern)results.get(rowIndex);
3241       switch(columnIndex) {
3242         case LEFT_CONTEXT_COLUMN:
3243           return result.getPatternText(result.getLeftContextStartOffset(),
3244                   result.getStartOffset()).replaceAll("[\n ]+"" ");
3245         case RESULT_COLUMN:
3246           return result.getPatternText(result.getStartOffset(),
3247                   result.getEndOffset());
3248         case RIGHT_CONTEXT_COLUMN:
3249           return result.getPatternText(result.getEndOffset(),
3250                   result.getRightContextEndOffset()).replaceAll("[\n ]+"" ");
3251         case FEATURES_COLUMN:
3252           StringBuffer buffer = new StringBuffer();
3253           for(Map.Entry<String, String> featureType : featureByTypeMap
3254                   .entrySet()) {
3255             String type = featureType.getKey();
3256             String feature = featureType.getValue();
3257             List<PatternAnnotation> annotations =
3258                     result.getPatternAnnotations(result.getStartOffset(),
3259                             result.getEndOffset());
3260             buffer.append(type).append('.').append(feature).append('=');
3261             for(PatternAnnotation annotation : annotations) {
3262               if(annotation.getType().equals(type)
3263                       && annotation.getFeature(feature!= null) {
3264                 buffer.append(annotation.getFeatures().get(feature)).append(
3265                         ", ");
3266               }
3267             }
3268             if(buffer.length() 2) {
3269               if(buffer.codePointAt(buffer.length() 2== ',') {
3270                 // delete the last ", "
3271                 buffer.delete(buffer.length() 2, buffer.length());
3272                 // and replace it with a "; "
3273                 buffer.append("; ");
3274               else if(buffer.codePointAt(buffer.length() 1== '=') {
3275                 // delete the last "Type.Feature="
3276                 buffer.delete(
3277                         buffer.length() - type.length() - feature.length() 2,
3278                         buffer.length());
3279               }
3280             }
3281           }
3282           if(buffer.length() 2) {
3283             // delete the last "; "
3284             buffer.delete(buffer.length() 2, buffer.length());
3285           }
3286           return buffer.toString();
3287         case QUERY_COLUMN:
3288           return result.getQueryString();
3289         case DOCUMENT_COLUMN:
3290           return result.getDocumentID();
3291         case SET_COLUMN:
3292           return result.getAnnotationSetName();
3293         default:
3294           return Object.class;
3295       }
3296     }
3297 
3298     @Override
3299     public void fireTableDataChanged() {
3300       // reinitialise types and features to display in the "Features"
3301       // column
3302       featureByTypeMap.clear();
3303       for(int row = 0; row < numStackRows; row++) {
3304         if(!stackRows[row][DISPLAY].equals("false")
3305                 && !stackRows[row][FEATURE].equals("")) {
3306           String feature = stackRows[row][FEATURE];
3307           String type = stackRows[row][ANNOTATION_TYPE];
3308           featureByTypeMap.put(type, feature);
3309         }
3310       }
3311       super.fireTableDataChanged();
3312     }
3313 
3314     /** Maximum number of characters for the result column. */
3315     static public final int MAX_COL_WIDTH = 40;
3316 
3317     static public final int LEFT_CONTEXT_COLUMN = 0;
3318 
3319     static public final int RESULT_COLUMN = 1;
3320 
3321     static public final int RIGHT_CONTEXT_COLUMN = 2;
3322 
3323     static public final int FEATURES_COLUMN = 3;
3324 
3325     static public final int QUERY_COLUMN = 4;
3326 
3327     static public final int DOCUMENT_COLUMN = 5;
3328 
3329     static public final int SET_COLUMN = 6;
3330 
3331     static public final int COLUMN_COUNT = 7;
3332 
3333     protected Map<String, String> featureByTypeMap;
3334   }
3335 
3336   /**
3337    * Panel that shows a table of shortcut, annotation type and feature
3338    * to display in the central view of the GUI.
3339    */
3340   protected class ConfigureStackViewFrame extends JFrame {
3341 
3342     private final int REMOVE = columnNames.length;
3343 
3344     private JTable configureStackViewTable;
3345 
3346     public ConfigureStackViewFrame(String title) {
3347       super(title);
3348 
3349       setLayout(new BorderLayout());
3350 
3351       JScrollPane scrollPane =
3352               new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
3353                       JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
3354       scrollPane.getViewport().setOpaque(true);
3355 
3356       configureStackViewTableModel = new ConfigureStackViewTableModel();
3357       configureStackViewTable = new XJTable(configureStackViewTableModel);
3358       ((XJTable)configureStackViewTable).setSortable(false);
3359       configureStackViewTable.setCellSelectionEnabled(true);
3360 
3361       // combobox used as cell editor
3362 
3363       String[] s = {"Crop middle""Crop start""Crop end"};
3364       JComboBox<String> cropBox = new JComboBox<String>(s);
3365 
3366       // set the cell renderer and/or editor for each column
3367 
3368       configureStackViewTable.getColumnModel().getColumn(DISPLAY)
3369               .setCellRenderer(new DefaultTableCellRenderer() {
3370                 @Override
3371                 public Component getTableCellRendererComponent(JTable table,
3372                         Object color, boolean isSelected, boolean hasFocus,
3373                         int row, int col) {
3374                   JCheckBox checkBox = new JCheckBox();
3375                   checkBox.setHorizontalAlignment(SwingConstants.CENTER);
3376                   checkBox.setToolTipText("Tick to display this row in central section.");
3377                   checkBox.setSelected((!table.getValueAt(row, col).equals(
3378                           "false")));
3379                   return checkBox;
3380                 }
3381               });
3382 
3383       final class DisplayCellEditor extends AbstractCellEditor implements
3384                                                               TableCellEditor,
3385                                                               ActionListener {
3386         JCheckBox checkBox;
3387 
3388         public DisplayCellEditor() {
3389           checkBox = new JCheckBox();
3390           checkBox.setHorizontalAlignment(SwingConstants.CENTER);
3391           checkBox.addActionListener(this);
3392         }
3393 
3394         @Override
3395         public boolean shouldSelectCell(EventObject anEvent) {
3396           return false;
3397         }
3398 
3399         @Override
3400         public void actionPerformed(ActionEvent e) {
3401           fireEditingStopped();
3402         }
3403 
3404         @Override
3405         public Object getCellEditorValue() {
3406           return (checkBox.isSelected()) "true" "false";
3407         }
3408 
3409         @Override
3410         public Component getTableCellEditorComponent(JTable table,
3411                 Object value, boolean isSelected, int row, int col) {
3412           checkBox.setSelected((!table.getValueAt(row, col).equals("false")));
3413           return checkBox;
3414         }
3415       }
3416       configureStackViewTable.getColumnModel().getColumn(DISPLAY)
3417               .setCellEditor(new DisplayCellEditor());
3418 
3419       configureStackViewTable.getColumnModel().getColumn(SHORTCUT)
3420               .setCellRenderer(new DefaultTableCellRenderer() {
3421                 @Override
3422                 public Component getTableCellRendererComponent(JTable table,
3423                         Object color, boolean isSelected, boolean hasFocus,
3424                         int row, int col) {
3425                   Component c =
3426                           super.getTableCellRendererComponent(table, color,
3427                                   isSelected, hasFocus, row, col);
3428                   if(instanceof JComponent) {
3429                     ((JComponent)c)
3430                             .setToolTipText("Shortcut can be used in queries "
3431                                     "instead of \"AnnotationType.Feature\".");
3432                   }
3433                   c.setBackground(UIManager.getColor("CheckBox.background"));
3434                   return c;
3435                 }
3436               });
3437 
3438       DefaultCellEditor cellEditor = new DefaultCellEditor(new JTextField());
3439       cellEditor.setClickCountToStart(0);
3440       configureStackViewTable.getColumnModel().getColumn(SHORTCUT)
3441               .setCellEditor(cellEditor);
3442 
3443       configureStackViewTable.getColumnModel().getColumn(ANNOTATION_TYPE)
3444               .setCellRenderer(new DefaultTableCellRenderer() {
3445                 @Override
3446                 public Component getTableCellRendererComponent(JTable table,
3447                         Object color, boolean isSelected, boolean hasFocus,
3448                         int row, int col) {
3449                   String[] s = {stackRows[row][ANNOTATION_TYPE]};
3450                   return new JComboBox<String>(s);
3451                 }
3452               });
3453 
3454       final class FeatureCellEditor extends AbstractCellEditor implements
3455                                                               TableCellEditor,
3456                                                               ActionListener {
3457         private JComboBox<String> featuresBox;
3458 
3459         public FeatureCellEditor() {
3460           featuresBox = new JComboBox<String>();
3461           featuresBox.setMaximumRowCount(10);
3462           featuresBox.addActionListener(this);
3463         }
3464 
3465         @Override
3466         public void actionPerformed(ActionEvent e) {
3467           fireEditingStopped();
3468         }
3469 
3470         @Override
3471         public Object getCellEditorValue() {
3472           return (featuresBox.getSelectedItem() == null"" : featuresBox
3473                   .getSelectedItem();
3474         }
3475 
3476         @Override
3477         public Component getTableCellEditorComponent(JTable table,
3478                 Object value, boolean isSelected, int row, int col) {
3479           TreeSet<String> ts = new TreeSet<String>(stringCollator);
3480           if(populatedAnnotationTypesAndFeatures
3481                   .containsKey(configureStackViewTable.getValueAt(row,
3482                           ANNOTATION_TYPE))) {
3483             // this annotation type still exists in the datastore
3484             ts.addAll(populatedAnnotationTypesAndFeatures
3485                     .get(configureStackViewTable.getValueAt(row,
3486                             ANNOTATION_TYPE)));
3487           }
3488           DefaultComboBoxModel<String> dcbm = new DefaultComboBoxModel<String>(ts.toArray(new String[ts.size()]));
3489           dcbm.insertElementAt(""0);
3490           featuresBox.setModel(dcbm);
3491           featuresBox.setSelectedItem(ts
3492                   .contains(configureStackViewTable
3493                           .getValueAt(row, col)) ? configureStackViewTable
3494                   .getValueAt(row, col"");
3495           return featuresBox;
3496         }
3497       }
3498       configureStackViewTable.getColumnModel().getColumn(FEATURE)
3499               .setCellEditor(new FeatureCellEditor());
3500 
3501       configureStackViewTable.getColumnModel().getColumn(FEATURE)
3502               .setCellRenderer(new DefaultTableCellRenderer() {
3503                 @Override
3504                 public Component getTableCellRendererComponent(JTable table,
3505                         Object color, boolean isSelected, boolean hasFocus,
3506                         int row, int col) {
3507                   String[] s = {stackRows[row][FEATURE]};
3508                   return new JComboBox<String>(s);
3509                 }
3510               });
3511 
3512       cellEditor = new DefaultCellEditor(cropBox);
3513       cellEditor.setClickCountToStart(0);
3514       configureStackViewTable.getColumnModel().getColumn(CROP)
3515               .setCellEditor(cellEditor);
3516       configureStackViewTable.getColumnModel().getColumn(CROP)
3517               .setCellRenderer(new DefaultTableCellRenderer() {
3518                 @Override
3519                 public Component getTableCellRendererComponent(JTable table,
3520                         Object color, boolean isSelected, boolean hasFocus,
3521                         int row, int col) {
3522                   String[] s = {stackRows[row][CROP]};
3523                   return new JComboBox<String>(s);
3524                 }
3525               });
3526 
3527       final class AddRemoveCellEditorRenderer extends AbstractCellEditor
3528                                                                         implements
3529                                                                         TableCellRenderer,
3530                                                                         TableCellEditor,
3531                                                                         ActionListener {
3532         private JButton button;
3533 
3534         public AddRemoveCellEditorRenderer() {
3535           button = new JButton();
3536           button.setHorizontalAlignment(SwingConstants.CENTER);
3537           button.addActionListener(this);
3538         }
3539 
3540         @Override
3541         public Component getTableCellRendererComponent(JTable table,
3542                 Object color, boolean isSelected, boolean hasFocus, int row,
3543                 int col) {
3544           if(row == numStackRows) {
3545             // add button if it's the last row of the table
3546             button.setIcon(MainFrame.getIcon("crystal-clear-action-edit-add"));
3547             button.setToolTipText("Click to add this line.");
3548           else {
3549             // remove button otherwise
3550             button.setIcon(MainFrame
3551                     .getIcon("crystal-clear-action-button-cancel"));
3552             button.setToolTipText("Click to remove this line.");
3553           }
3554           button.setSelected(isSelected);
3555           return button;
3556         }
3557 
3558         @Override
3559         public boolean shouldSelectCell(EventObject anEvent) {
3560           return false;
3561         }
3562 
3563         @Override
3564         public void actionPerformed(ActionEvent e) {
3565           int row = configureStackViewTable.getEditingRow();
3566           fireEditingStopped();
3567           if(row == numStackRows) {
3568             if(stackRows[row][ANNOTATION_TYPE!= null
3569                     && !stackRows[row][ANNOTATION_TYPE].equals("")) {
3570               if(numStackRows == maxStackRows) {
3571                 JOptionPane.showMessageDialog(configureStackViewFrame,
3572                         "The number of rows is limited to " + maxStackRows
3573                                 ".""Alert", JOptionPane.ERROR_MESSAGE);
3574               else {
3575                 // add a new row
3576                 numStackRows++;
3577                 configureStackViewTableModel
3578                         .fireTableRowsInserted(row, row + 1);