GazetteerEditor.java
0001 /*
0002  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0004  *
0005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0006  *  software, licenced under the GNU Library General Public License,
0007  *  Version 2, June 1991 (in the distribution as file licence.html,
0008  *  and also available at http://gate.ac.uk/gate/licence.html).
0009  *
0010  *  Thomas Heitz, 1 March 2010
0011  *
0012  *  $Id$
0013  */
0014 
0015 package gate.gui;
0016 
0017 import gate.Resource;
0018 import gate.creole.AbstractVisualResource;
0019 import gate.creole.ResourceInstantiationException;
0020 import gate.creole.gazetteer.Gazetteer;
0021 import gate.creole.gazetteer.GazetteerEvent;
0022 import gate.creole.gazetteer.GazetteerList;
0023 import gate.creole.gazetteer.GazetteerListener;
0024 import gate.creole.gazetteer.GazetteerNode;
0025 import gate.creole.gazetteer.LinearDefinition;
0026 import gate.creole.gazetteer.LinearNode;
0027 import gate.creole.metadata.CreoleResource;
0028 import gate.creole.metadata.GuiType;
0029 import gate.swing.XJFileChooser;
0030 import gate.swing.XJTable;
0031 import gate.util.Err;
0032 import gate.util.ExtensionFileFilter;
0033 import gate.util.Files;
0034 import gate.util.GateRuntimeException;
0035 
0036 import java.awt.BorderLayout;
0037 import java.awt.Color;
0038 import java.awt.Component;
0039 import java.awt.FlowLayout;
0040 import java.awt.Insets;
0041 import java.awt.Point;
0042 import java.awt.Toolkit;
0043 import java.awt.datatransfer.Clipboard;
0044 import java.awt.datatransfer.DataFlavor;
0045 import java.awt.datatransfer.UnsupportedFlavorException;
0046 import java.awt.event.ActionEvent;
0047 import java.awt.event.ActionListener;
0048 import java.awt.event.KeyAdapter;
0049 import java.awt.event.KeyEvent;
0050 import java.awt.event.MouseAdapter;
0051 import java.awt.event.MouseEvent;
0052 import java.io.File;
0053 import java.io.FilenameFilter;
0054 import java.io.IOException;
0055 import java.net.MalformedURLException;
0056 import java.net.URL;
0057 import java.text.Collator;
0058 import java.util.ArrayList;
0059 import java.util.Arrays;
0060 import java.util.Date;
0061 import java.util.HashMap;
0062 import java.util.LinkedHashMap;
0063 import java.util.List;
0064 import java.util.Locale;
0065 import java.util.Map;
0066 import java.util.Timer;
0067 import java.util.TimerTask;
0068 import java.util.regex.Pattern;
0069 import java.util.regex.PatternSyntaxException;
0070 
0071 import javax.swing.AbstractAction;
0072 import javax.swing.Action;
0073 import javax.swing.ActionMap;
0074 import javax.swing.DefaultComboBoxModel;
0075 import javax.swing.InputMap;
0076 import javax.swing.JButton;
0077 import javax.swing.JCheckBox;
0078 import javax.swing.JComboBox;
0079 import javax.swing.JComponent;
0080 import javax.swing.JFileChooser;
0081 import javax.swing.JLabel;
0082 import javax.swing.JOptionPane;
0083 import javax.swing.JPanel;
0084 import javax.swing.JPopupMenu;
0085 import javax.swing.JScrollPane;
0086 import javax.swing.JSplitPane;
0087 import javax.swing.JTable;
0088 import javax.swing.JTextField;
0089 import javax.swing.JToolTip;
0090 import javax.swing.KeyStroke;
0091 import javax.swing.ListSelectionModel;
0092 import javax.swing.Popup;
0093 import javax.swing.PopupFactory;
0094 import javax.swing.SwingUtilities;
0095 import javax.swing.event.DocumentEvent;
0096 import javax.swing.event.DocumentListener;
0097 import javax.swing.event.ListSelectionEvent;
0098 import javax.swing.event.ListSelectionListener;
0099 import javax.swing.event.TableModelEvent;
0100 import javax.swing.event.TableModelListener;
0101 import javax.swing.table.AbstractTableModel;
0102 import javax.swing.table.DefaultTableCellRenderer;
0103 import javax.swing.table.DefaultTableModel;
0104 import javax.swing.text.BadLocationException;
0105 import javax.swing.text.Document;
0106 import javax.swing.text.JTextComponent;
0107 
0108 /**
0109  * Editor for {@link gate.creole.gazetteer.Gazetteer ANNIE Gazetteer}.
0110 <pre>
0111  Main features:
0112 - left table with 5 columns (List name, Major, Minor, Language, AnnotationType)
0113  for the definition
0114 - right table with 1+n columns (Value, Feature 1...Feature n) for the lists
0115 - 'Save' on the context menu of the resources tree and tab
0116 - context menu on both tables to delete selected rows
0117 - drop down list with .lst files in directory
0118 - text fields and buttons to add a list/entry
0119 - for the second table, a button to filter the list and another to add columns
0120 - both tables sorted case insensitively on the first column by default
0121 - display in red the list name when the list is modified
0122 - for the separator character test when editing feature columns
0123 - make feature map ordered
0124 - remove feature/value columns when containing only spaces or empty
0125 </pre>
0126 */
0127 @SuppressWarnings("serial")
0128 @CreoleResource(name="Gazetteer Editor", comment="Gazetteer viewer and editor.", helpURL="http://gate.ac.uk/userguide/sec:gazetteers:anniegazeditor", guiType=GuiType.LARGE, mainViewer=true, resourceDisplayed="gate.creole.gazetteer.AbstractGazetteer")
0129 public class GazetteerEditor extends AbstractVisualResource
0130     implements GazetteerListener, ActionsPublisher {
0131 
0132   public GazetteerEditor() {
0133     definitionTableModel = new DefaultTableModel();
0134     definitionTableModel.addColumn("List name");
0135     definitionTableModel.addColumn("Major");
0136     definitionTableModel.addColumn("Minor");
0137     definitionTableModel.addColumn("Language");
0138     definitionTableModel.addColumn("Annotation type");
0139     listTableModel = new ListTableModel();
0140     actions = new ArrayList<Action>();
0141     actions.add(new SaveAndReinitialiseGazetteerAction());
0142     actions.add(new SaveAsGazetteerAction());
0143   }
0144 
0145   @Override
0146   public Resource init() throws ResourceInstantiationException {
0147     initGUI();
0148     initListeners();
0149     return this;
0150   }
0151 
0152   protected void initGUI() {
0153     collator = Collator.getInstance(Locale.ENGLISH);
0154     collator.setStrength(Collator.TERTIARY);
0155 
0156     /*************************/
0157     /* Definition table pane */
0158     /*************************/
0159 
0160     JPanel definitionPanel = new JPanel(new BorderLayout());
0161     JPanel definitionTopPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
0162     newListComboBox = new JComboBox<String>();
0163     newListComboBox.setEditable(true);
0164     newListComboBox.setPrototypeDisplayValue("123456789012345");
0165     newListComboBox.setToolTipText(
0166       "Lists available in the gazetteer directory");
0167     newListButton = new JButton("Add");
0168     // enable/disable button according to the text field content
0169     JTextComponent listTextComponent = (JTextField)
0170       newListComboBox.getEditor().getEditorComponent();
0171     listTextComponent.getDocument().addDocumentListener(new DocumentListener() {
0172       @Override
0173       public void insertUpdate(DocumentEvent e) { update(e)}
0174       @Override
0175       public void removeUpdate(DocumentEvent e) { update(e)}
0176       @Override
0177       public void changedUpdate(DocumentEvent e) { update(e)}
0178       public void update(DocumentEvent e) {
0179         Document document = e.getDocument();
0180         try {
0181           String value = document.getText(0, document.getLength());
0182           if (value.trim().length() == 0) {
0183             newListButton.setEnabled(false);
0184             newListButton.setText("Add");
0185           else if (value.contains(":")) {
0186             newListButton.setEnabled(false);
0187             newListButton.setText("Colon Char Forbidden");
0188           else if (linearDefinition.getLists().contains(value)) {
0189             // this list already exists in the gazetteer
0190             newListButton.setEnabled(false);
0191             newListButton.setText("Existing");
0192           else {
0193             newListButton.setEnabled(true);
0194             newListButton.setText("Add");
0195           }
0196         catch (BadLocationException ble) {
0197           ble.printStackTrace();
0198         }
0199       }
0200     });
0201     newListComboBox.getEditor().getEditorComponent()
0202         .addKeyListener(new KeyAdapter() {
0203       @Override
0204       public void keyPressed(KeyEvent e) {
0205         if (e.getKeyCode() == KeyEvent.VK_ENTER) {
0206           // Enter key in the text field add the entry to the table
0207           newListButton.doClick();
0208         }
0209       }
0210     });
0211     newListButton.setToolTipText("<html>Add a list in the gazetteer"
0212       "&nbsp;&nbsp;<font color=#667799><small>Enter"
0213       "&nbsp;&nbsp;</small></font></html>");
0214     newListButton.setMargin(new Insets(2222));
0215     newListButton.addActionListener(new AbstractAction() {
0216       @Override
0217       public void actionPerformed(ActionEvent e) {
0218         String listName = (StringnewListComboBox.getEditor().getItem();
0219         newListComboBox.removeItem(listName);
0220         // update the table
0221         definitionTableModel.addRow(new Object[]{listName, """"""""});
0222         // update the gazetteer
0223         LinearNode linearNode = new LinearNode(listName, """""""");
0224         linearDefinition.add(linearNode);
0225         linearDefinition.getNodesByListNames().put(listName, linearNode);
0226         GazetteerList gazetteerList;
0227         try {
0228           gazetteerList = linearDefinition.loadSingleList(listName, true);
0229         catch (ResourceInstantiationException rie) {
0230           rie.printStackTrace();
0231           return;
0232         }
0233         linearDefinition.getListsByNode().put(linearNode, gazetteerList);
0234         // select the new list
0235         final int row = definitionTable.rowModelToView(
0236           definitionTable.getRowCount()-1);
0237         final int column = definitionTable.convertColumnIndexToView(0);
0238         definitionTable.setRowSelectionInterval(row, row);
0239         SwingUtilities.invokeLater(new Runnable() {
0240           @Override
0241           public void run() {
0242             // scroll to the selected new list
0243             definitionTable.scrollRectToVisible(
0244               definitionTable.getCellRect(row, column, true));
0245             definitionTable.requestFocusInWindow();
0246           }
0247         });
0248       }
0249     });
0250     definitionTopPanel.add(newListComboBox);
0251     definitionTopPanel.add(newListButton);
0252     definitionPanel.add(definitionTopPanel, BorderLayout.NORTH);
0253     definitionTable = new XJTable() {
0254       // shift + Delete keys delete the selected rows
0255       @Override
0256       protected void processKeyEvent(KeyEvent e) {
0257         if (e.getKeyCode() == KeyEvent.VK_DELETE
0258         && ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK!= 0)) {
0259           new DeleteSelectedLinearNodeAction().actionPerformed(null);
0260         else {
0261           super.processKeyEvent(e);
0262         }
0263       }
0264     };
0265     definitionTable.setSelectionMode(
0266       ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
0267     definitionTable.setRowSelectionAllowed(true);
0268     definitionTable.setColumnSelectionAllowed(false);
0269     definitionTable.setEnableHidingColumns(true);
0270     definitionTable.setAutoResizeMode(XJTable.AUTO_RESIZE_OFF);
0271     definitionTable.setModel(definitionTableModel);
0272     definitionTable.setSortable(true);
0273     definitionTable.setSortedColumn(0);
0274     // use red colored font for modified lists name
0275     definitionTable.getColumnModel().getColumn(0).setCellRenderer(
0276       new DefaultTableCellRenderer() {
0277         @Override
0278         public Component getTableCellRendererComponent(JTable table,
0279             Object value, boolean isSelected, boolean hasFocus,
0280             int row, int column) {
0281           super.getTableCellRendererComponent(
0282             table, value, isSelected, hasFocus, row, column);
0283           setForeground(table.getForeground());
0284           LinearNode linearNode = linearDefinition.getNodesByListNames().get(value);
0285           if (linearNode != null) {
0286             GazetteerList gazetteerList = linearDefinition.getListsByNode().get(linearNode);
0287             if (gazetteerList != null && gazetteerList.isModified()) {
0288               setForeground(Color.RED);
0289             }
0290           }
0291           return this;
0292         }
0293       });
0294     definitionPanel.add(new JScrollPane(definitionTable), BorderLayout.CENTER);
0295 
0296     /*******************/
0297     /* List table pane */
0298     /*******************/
0299 
0300     JPanel listPanel = new JPanel(new BorderLayout());
0301     JPanel listTopPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
0302     listEntryTextField = new JTextField(10);
0303     listEntryTextField.setEnabled(false);
0304     final JButton filterListButton = new JButton("Filter");
0305     filterListButton.setToolTipText("Filter the list on the text entered");
0306     filterListButton.setMargin(new Insets(2222));
0307     filterListButton.setEnabled(false);
0308     filterListButton.addActionListener(new AbstractAction() {
0309       @Override
0310       public void actionPerformed(ActionEvent e) {
0311         String filter = listEntryTextField.getText().trim();
0312         listTableModel.setFilterText(filter);
0313         listTableModel.fireTableDataChanged();
0314       }
0315     });
0316     final JButton newEntryButton = new JButton("Add");
0317     newEntryButton.setToolTipText("<html>Add the text in the list"
0318       "&nbsp;&nbsp;<font color=#667799><small>Enter"
0319       "&nbsp;&nbsp;</small></font></html>");
0320     newEntryButton.setMargin(new Insets(2222));
0321     newEntryButton.setEnabled(false);
0322     newEntryButton.addActionListener(new AbstractAction() {
0323       @Override
0324       public void actionPerformed(ActionEvent e) {
0325         // update the gazetteer
0326         GazetteerNode newGazetteerNode = new GazetteerNode(listEntryTextField.getText());
0327         
0328         // if you don't set the separator then all features/values
0329         // entered before the list is saved and reinitialised are lost
0330         newGazetteerNode.setSeparator(listTableModel.gazetteerList
0331                 .getSeparator());
0332         
0333         listTableModel.addRow(newGazetteerNode);
0334         listTableModel.setFilterText("");
0335         listEntryTextField.setText("");
0336         listTableModel.fireTableDataChanged();
0337         // scroll and select the new row
0338         final int row = listTable.rowModelToView(listTable.getRowCount()-1);
0339         final int column = listTable.convertColumnIndexToView(0);
0340         listEntryTextField.setText("");
0341         listEntryTextField.requestFocusInWindow();
0342         SwingUtilities.invokeLater(new Runnable() {
0343           @Override
0344           public void run() {
0345             listTable.scrollRectToVisible(
0346               listTable.getCellRect(row, column, true));
0347             listTable.setRowSelectionInterval(row, row);
0348             listTable.setColumnSelectionInterval(column, column);
0349             GazetteerList gazetteerList = linearDefinition.getListsByNode().get(selectedLinearNode);
0350             gazetteerList.setModified(true);
0351             definitionTable.repaint();
0352           }
0353         });
0354       }
0355     });
0356     // Enter key in the text field add the entry to the table
0357     listEntryTextField.addKeyListener(new KeyAdapter() {
0358       @Override
0359       public void keyPressed(KeyEvent e) {
0360         if (e.getKeyCode() == KeyEvent.VK_ENTER) {
0361           newEntryButton.doClick();
0362         }
0363       }
0364     });
0365     // enable/disable button according to the text field content
0366     listEntryTextField.getDocument().addDocumentListener(new DocumentListener() {
0367       @Override
0368       public void insertUpdate(DocumentEvent e) { update(e)}
0369       @Override
0370       public void removeUpdate(DocumentEvent e) { update(e)}
0371       @Override
0372       public void changedUpdate(DocumentEvent e) { update(e)}
0373       public void update(DocumentEvent e) {
0374         Document document = e.getDocument();
0375         try {
0376           String value = document.getText(0, document.getLength());
0377           if (value.trim().length() == 0) {
0378             newEntryButton.setEnabled(false);
0379             newEntryButton.setText("Add");
0380             filterListButton.setEnabled(false);
0381             // stop filtering the list
0382             listTableModel.setFilterText("");
0383             listTableModel.fireTableDataChanged();
0384           else if (linearDefinition.getSeparator() != null
0385                   && linearDefinition.getSeparator().length() 0
0386                   && value.contains(linearDefinition.getSeparator())) {
0387             newEntryButton.setEnabled(false);
0388             newEntryButton.setText("Char Forbidden: "
0389               + linearDefinition.getSeparator());
0390             filterListButton.setEnabled(false);
0391           else {
0392             // check if the entry already exists in the list
0393             GazetteerList gazetteerList = linearDefinition.getListsByNode().get(selectedLinearNode);
0394             boolean found = false;
0395             for (Object object : gazetteerList) {
0396               GazetteerNode node = (GazetteerNodeobject;
0397               if (node.getEntry().equals(value)) {
0398                 found = true;
0399                 break;
0400               }
0401             }
0402             if (found) {
0403               newEntryButton.setEnabled(false);
0404               newEntryButton.setText("Existing ");
0405             else {
0406               newEntryButton.setEnabled(true);
0407               newEntryButton.setText("Add");
0408             }
0409             filterListButton.setEnabled(true);
0410           }
0411         catch (BadLocationException ble) {
0412           ble.printStackTrace();
0413         }
0414       }
0415     });
0416     addColumnsButton = new JButton("+Cols");
0417     addColumnsButton.setToolTipText("Add a couple of columns: Feature, Value");
0418     addColumnsButton.setMargin(new Insets(2222));
0419     addColumnsButton.setEnabled(false);
0420     addColumnsButton.addActionListener(new AbstractAction() {
0421       @Override
0422       public void actionPerformed(ActionEvent e) {
0423         if (linearDefinition.getSeparator() == null
0424          || linearDefinition.getSeparator().length() == 0) {
0425           String separator = JOptionPane.showInputDialog(
0426             MainFrame.getInstance()"Type a character separator to separate" +
0427               "\nfeatures in the gazetteers lists.",
0428             "Feature Separator", JOptionPane.QUESTION_MESSAGE);
0429           if (separator == null
0430            || separator.equals("")) {
0431             return;
0432           }
0433           linearDefinition.setSeparator(separator);
0434         }
0435         listTableModel.addEmptyFeatureColumns();
0436         // cancel filtering and redisplay the table
0437         listEntryTextField.setText("");
0438         listTableModel.setFilterText("");
0439         listTableModel.fireTableStructureChanged();
0440       }
0441     });
0442     listTopPanel.add(listEntryTextField);
0443     listTopPanel.add(filterListButton);
0444     listTopPanel.add(newEntryButton);
0445     listTopPanel.add(addColumnsButton);
0446     listTopPanel.add(listCountLabel = new JLabel());
0447     listPanel.add(listTopPanel, BorderLayout.NORTH);
0448     listTable = new XJTable() {
0449       // shift + Delete keys delete the selected rows
0450       @Override
0451       protected void processKeyEvent(KeyEvent e) {
0452         if (e.getKeyCode() == KeyEvent.VK_DELETE
0453         && ((e.getModifiersEx() & KeyEvent.SHIFT_DOWN_MASK!= 0)) {
0454           new DeleteSelectedGazetteerNodeAction().actionPerformed(null);
0455         else {
0456           super.processKeyEvent(e);
0457         }
0458       }
0459     };
0460     listTable.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
0461     listTable.setRowSelectionAllowed(true);
0462     listTable.setColumnSelectionAllowed(true);
0463     listTable.setEnableHidingColumns(true);
0464     listTable.setAutoResizeMode(XJTable.AUTO_RESIZE_OFF);
0465     listTable.setModel(listTableModel);
0466     listTable.setSortable(true);
0467     listTable.setSortedColumn(0);
0468     listPanel.add(new JScrollPane(listTable), BorderLayout.CENTER);
0469     // select all the rows containing the text from filterTextField
0470 //    listEntryTextField.getDocument().addDocumentListener(
0471 //        new DocumentListener() {
0472 //      private Timer timer = new Timer("Gazetteer list filter timer", true);
0473 //      private TimerTask timerTask;
0474 //      public void changedUpdate(DocumentEvent e) { /* do nothing */ }
0475 //      public void insertUpdate(DocumentEvent e) { update(); }
0476 //      public void removeUpdate(DocumentEvent e) { update(); }
0477 //      private void update() {
0478 //        if (timerTask != null) { timerTask.cancel(); }
0479 //        Date timeToRun = new Date(System.currentTimeMillis() + 300);
0480 //        timerTask = new TimerTask() { public void run() {
0481 //          String filter = listEntryTextField.getText().trim();
0482 //          listTableModel.setFilterText(filter);
0483 //          listTableModel.fireTableDataChanged();
0484 //        }};
0485 //        // add a delay
0486 //        timer.schedule(timerTask, timeToRun);
0487 //      }
0488 //    });
0489     listTopPanel.add(caseInsensitiveCheckBox = new JCheckBox("Match Case"));
0490     caseInsensitiveCheckBox.setSelected(true);
0491     caseInsensitiveCheckBox.setToolTipText("Match Case");
0492     caseInsensitiveCheckBox.addActionListener(new ActionListener() {
0493       @Override
0494       public void actionPerformed(ActionEvent e) {
0495         // redisplay the table with the new filter option
0496         listEntryTextField.setText(listEntryTextField.getText());
0497       }
0498     });
0499     listTopPanel.add(regexCheckBox = new JCheckBox("Regex"));
0500     regexCheckBox.setToolTipText("Regular Expression");
0501     regexCheckBox.addActionListener(new ActionListener() {
0502       @Override
0503       public void actionPerformed(ActionEvent e) {
0504         listEntryTextField.setText(listEntryTextField.getText());
0505       }
0506     });
0507     listTopPanel.add(onlyValueCheckBox = new JCheckBox("Value"));
0508     onlyValueCheckBox.setToolTipText("Filter only Value column");
0509     onlyValueCheckBox.addActionListener(new ActionListener() {
0510       @Override
0511       public void actionPerformed(ActionEvent e) {
0512         listEntryTextField.setText(listEntryTextField.getText());
0513       }
0514     });
0515 
0516     JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true);
0517     splitPane.add(definitionPanel);
0518     splitPane.add(listPanel);
0519     splitPane.setResizeWeight(0.33);
0520     setLayout(new BorderLayout());
0521     add(splitPane, BorderLayout.CENTER);
0522   }
0523 
0524   protected void initListeners() {
0525 
0526     // display the list corresponding to the selected row
0527     definitionTable.getSelectionModel().addListSelectionListener(
0528       new ListSelectionListener() {
0529         @Override
0530         public void valueChanged(ListSelectionEvent e) {
0531           if (e.getValueIsAdjusting()
0532            || definitionTable.isEditing()) {
0533             return;
0534           }
0535           if (definitionTable.getSelectedRowCount() != 1) {
0536             // zero or several lists selected
0537             listTableModel.setGazetteerList(new GazetteerList());
0538             selectedLinearNode = null;
0539             listEntryTextField.setEnabled(false);
0540             addColumnsButton.setEnabled(false);
0541           else // one list selected
0542             String listName = (StringdefinitionTable.getValueAt(
0543               definitionTable.getSelectedRow(),
0544               definitionTable.convertColumnIndexToView(0));
0545             selectedLinearNode = linearDefinition.getNodesByListNames().get(listName);
0546             if (selectedLinearNode != null) {
0547               listTableModel.setGazetteerList(linearDefinition.getListsByNode().get(selectedLinearNode));
0548             }
0549             listEntryTextField.setEnabled(true);
0550             addColumnsButton.setEnabled(true);
0551           }
0552           if (!listEntryTextField.getText().equals("")) {
0553             listEntryTextField.setText("");
0554           }
0555           listTableModel.setFilterText("");
0556           listTableModel.fireTableStructureChanged();
0557           if (definitionTable.getSelectedRow() != -1) {
0558             if (selectedLinearNode != null) {
0559               for (int col = ; col < listTable.getColumnCount(); col++) {
0560                 listTable.setComparator(col, collator);
0561               }
0562               // TODO: this is only to sort the rows, how to avoid it?
0563               listTableModel.fireTableDataChanged();
0564             }
0565           }
0566         }
0567       }
0568     );
0569 
0570     // update linear nodes with changes in the definition table
0571     definitionTableModel.addTableModelListener(new TableModelListener() {
0572       @Override
0573       public void tableChanged(TableModelEvent e) {
0574         if (selectedLinearNode == null) { return}
0575         int row = e.getFirstRow();
0576         int column = e.getColumn();
0577         GazetteerList gazetteerList = linearDefinition.getListsByNode().get(selectedLinearNode);
0578         switch (e.getType()) {
0579           case TableModelEvent.UPDATE:
0580             if (row == -|| column == -1) { return}
0581             if (column == 0) { // list filename
0582               String newListFileName = (String)
0583                 definitionTableModel.getValueAt(row, column);
0584               String oldListFileName = selectedLinearNode.getList();
0585               if (oldListFileName != null
0586                && oldListFileName.equals(newListFileName)) { return}
0587               try // save the previous list
0588                 gazetteerList.store();
0589                 MainFrame.getInstance().statusChanged("Previous list saved in "
0590                   + gazetteerList.getURL().getPath());
0591               catch (ResourceInstantiationException rie) {
0592                 Err.prln("Unable to save the list.\n" + rie.getMessage());
0593                 return;
0594               }
0595               try // change the list URL
0596                 File source = Files.fileFromURL(gazetteerList.getURL());
0597                 File destination = new File(source.getParentFile(),
0598                   newListFileName);
0599                 gazetteerList.setURL(destination.toURI().toURL());
0600                 gazetteerList.setModified(false);
0601               catch (MalformedURLException mue) {
0602                 Err.prln("File name invalid.\n" + mue.getMessage());
0603                 return;
0604               }
0605               linearDefinition.getListsByNode().remove(selectedLinearNode);
0606               // update the node with the new list file name
0607               selectedLinearNode.setList(newListFileName);
0608               linearDefinition.getListsByNode()
0609                 .put(selectedLinearNode, gazetteerList);
0610               linearDefinition.getNodesByListNames().remove(oldListFileName);
0611               linearDefinition.getNodesByListNames()
0612                 .put(newListFileName, selectedLinearNode);
0613               linearDefinition.setModified(true);
0614             else if (column == 1) { // major type
0615               String newMajorType = (String)
0616                 definitionTableModel.getValueAt(row, column);
0617               String oldMajorType = selectedLinearNode.getMajorType();
0618               if (oldMajorType != null
0619                && oldMajorType.equals(newMajorType)) { return}
0620               linearDefinition.getListsByNode().remove(selectedLinearNode);
0621               selectedLinearNode.setMajorType(newMajorType);
0622               linearDefinition.getListsByNode()
0623                 .put(selectedLinearNode, gazetteerList);
0624               linearDefinition.getNodesByListNames()
0625                 .put(selectedLinearNode.getList(), selectedLinearNode);
0626               linearDefinition.setModified(true);
0627             else if (column == 2) { // minor type
0628               String newMinorType = (String)
0629                 definitionTableModel.getValueAt(row, column);
0630               String oldMinorType = selectedLinearNode.getMinorType();
0631               if (oldMinorType != null
0632                && oldMinorType.equals(newMinorType)) { return}
0633               linearDefinition.getListsByNode().remove(selectedLinearNode);
0634               selectedLinearNode.setMinorType(newMinorType);
0635               linearDefinition.getListsByNode()
0636                 .put(selectedLinearNode, gazetteerList);
0637               linearDefinition.getNodesByListNames()
0638                 .put(selectedLinearNode.getList(), selectedLinearNode);
0639               linearDefinition.setModified(true);
0640             else if (column == 3) { // language
0641               String newLanguage = (String)
0642                 definitionTableModel.getValueAt(row, column);
0643               String oldLanguage = selectedLinearNode.getLanguage();
0644               if (oldLanguage != null
0645                && oldLanguage.equals(newLanguage)) { return}
0646               linearDefinition.getListsByNode().remove(selectedLinearNode);
0647               selectedLinearNode.setLanguage(newLanguage);
0648               linearDefinition.getListsByNode()
0649                 .put(selectedLinearNode, gazetteerList);
0650               linearDefinition.getNodesByListNames()
0651                 .put(selectedLinearNode.getList(), selectedLinearNode);
0652               linearDefinition.setModified(true);
0653             else  if (column == 4) { // annotation type
0654               String newAnnotationType = (String)
0655                 definitionTableModel.getValueAt(row, column);
0656               String oldAnnotationType = selectedLinearNode.getAnnotationType();
0657               if (oldAnnotationType != null
0658                && oldAnnotationType.equals(newAnnotationType)) { return}
0659               linearDefinition.getListsByNode().remove(selectedLinearNode);
0660               selectedLinearNode.setAnnotationType(newAnnotationType);
0661               linearDefinition.getListsByNode()
0662                 .put(selectedLinearNode, gazetteerList);
0663               linearDefinition.getNodesByListNames()
0664                 .put(selectedLinearNode.getList(), selectedLinearNode);
0665               linearDefinition.setModified(true);
0666             }
0667             break;
0668         }
0669       }
0670     });
0671 
0672     // context menu to delete a row
0673     definitionTable.addMouseListener(new MouseAdapter() {
0674       @Override
0675       public void mouseClicked(MouseEvent me) {
0676         processMouseEvent(me);
0677       }
0678       @Override
0679       public void mouseReleased(MouseEvent me) {
0680         processMouseEvent(me);
0681       }
0682       @Override
0683       public void mousePressed(MouseEvent me) {
0684         JTable table = (JTableme.getSource();
0685         int row = table.rowAtPoint(me.getPoint());
0686         if(me.isPopupTrigger()
0687         && !table.isRowSelected(row)) {
0688           // if right click outside the selection then reset selection
0689           table.getSelectionModel().setSelectionInterval(row, row);
0690         }
0691         processMouseEvent(me);
0692       }
0693       protected void processMouseEvent(MouseEvent me) {
0694         XJTable table = (XJTableme.getSource();
0695         if (me.isPopupTrigger()
0696           && table.getSelectedRowCount() 0) {
0697           JPopupMenu popup = new JPopupMenu();
0698           if (table.getSelectedRowCount() == 1) {
0699             popup.add(new ReloadGazetteerListAction());
0700             popup.addSeparator();
0701           }
0702           popup.add(new DeleteSelectedLinearNodeAction());
0703           popup.show(table, me.getX(), me.getY());
0704         }
0705       }
0706     });
0707 
0708     // context menu to delete a row
0709     listTable.addMouseListener(new MouseAdapter() {
0710       @Override
0711       public void mouseClicked(MouseEvent me) {
0712         processMouseEvent(me);
0713       }
0714       @Override
0715       public void mouseReleased(MouseEvent me) {
0716         processMouseEvent(me);
0717       }
0718       @Override
0719       public void mousePressed(MouseEvent me) {
0720         JTable table = (JTableme.getSource();
0721         int row = table.rowAtPoint(me.getPoint());
0722         if(me.isPopupTrigger()
0723         && !table.isRowSelected(row)) {
0724           // if right click outside the selection then reset selection
0725           table.getSelectionModel().setSelectionInterval(row, row);
0726         }
0727         processMouseEvent(me);
0728       }
0729       protected void processMouseEvent(MouseEvent me) {
0730         XJTable table = (XJTableme.getSource();
0731         if (me.isPopupTrigger()
0732           && table.getSelectedRowCount() 0) {
0733           JPopupMenu popup = new JPopupMenu();
0734           popup.add(new CopySelectionAction());
0735           popup.add(new PasteSelectionAction());
0736           popup.addSeparator();
0737           popup.add(new FillDownSelectionAction());
0738           popup.add(new ClearSelectionAction());
0739           popup.addSeparator();
0740           popup.add(new DeleteSelectedGazetteerNodeAction());
0741           popup.show(table, me.getX(), me.getY());
0742         }
0743       }
0744     });
0745 
0746     listTableModel.addTableModelListener(new TableModelListener() {
0747       @Override
0748       public void tableChanged(TableModelEvent e) {
0749         listCountLabel.setText(String.valueOf(listTableModel.getRowCount())
0750         " entries ");
0751       }
0752     });
0753 
0754     // add key shortcuts for global actions
0755     InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
0756     ActionMap actionMap = getActionMap();
0757     inputMap.put(KeyStroke.getKeyStroke("control S")"save");
0758     actionMap.put("save", actions.get(0));
0759     inputMap.put(KeyStroke.getKeyStroke("control shift S")"save as");
0760     actionMap.put("save as", actions.get(1));
0761 
0762     // add key shortcuts for the list table actions
0763     inputMap = listTable.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
0764     actionMap = listTable.getActionMap();
0765     inputMap.put(KeyStroke.getKeyStroke("control V")"paste table selection");
0766     actionMap.put("paste table selection"new PasteSelectionAction());
0767   }
0768 
0769   @Override
0770   public void setTarget(Object target) {
0771     if (null == target) {
0772       throw new GateRuntimeException("The resource set is null.");
0773     }
0774     if ((target instanceof Gazetteer) ) {
0775       throw new GateRuntimeException(
0776         "The resource set must be of type gate.creole.gazetteer.Gazetteer\n"+
0777         "and not " + target.getClass());
0778     }
0779     if(((Gazetteer)target).getListsURL() == null) {
0780       // not a file based gazetteer: we cannot display it.
0781       throw new IllegalArgumentException("Not a file-based gazetteer.");
0782     }
0783     ((Gazetteertarget).addGazetteerListener(this);
0784     processGazetteerEvent(new GazetteerEvent(target, GazetteerEvent.REINIT));
0785   }
0786 
0787   @Override
0788   public void processGazetteerEvent(GazetteerEvent e) {
0789     gazetteer = (Gazetteere.getSource();
0790 
0791     // read and display the definition of the gazetteer
0792     if (e.getType() == GazetteerEvent.REINIT) {
0793       linearDefinition = gazetteer.getLinearDefinition();
0794       if (null == linearDefinition) {
0795         throw new GateRuntimeException(
0796           "Linear definition of a gazetteer should not be null.");
0797       }
0798 
0799       // reload the lists with ordered feature maps
0800       try {
0801         if (linearDefinition.getSeparator() != null
0802          && linearDefinition.getSeparator().length() 0) {
0803           linearDefinition.loadLists(true);
0804         }
0805       catch (ResourceInstantiationException rie) {
0806         rie.printStackTrace();
0807         return;
0808       }
0809 
0810       // add the gazetteer definition data to the table
0811       definitionTableModel.setRowCount(0);
0812       ArrayList<String> values = new ArrayList<String>();
0813       for (Object object : linearDefinition.getNodes()) {
0814         LinearNode node = (LinearNodeobject;
0815         values.add(node.getList() == null "" : node.getList());
0816         values.add(node.getMajorType() == null "" : node.getMajorType());
0817         values.add(node.getMinorType() == null "" : node.getMinorType());
0818         values.add(node.getLanguage() == null "" : node.getLanguage());
0819         values.add(node.getAnnotationType() == null "" : node.getAnnotationType());
0820         definitionTableModel.addRow(values.toArray());
0821         values.clear();
0822       }
0823       for (int col = ; col < definitionTable.getColumnCount(); col++) {
0824         definitionTable.setComparator(col, collator);
0825       }
0826 
0827       // update file list name in the drop down list
0828       File gazetteerDirectory = new File(
0829         Files.fileFromURL(gazetteer.getListsURL()).getParent());
0830       File[] files = gazetteerDirectory.listFiles(new FilenameFilter() {
0831         @Override
0832         public boolean accept(File dir, String name) {
0833           return name.endsWith(".lst")
0834             && !linearDefinition.getLists().contains(name);
0835         }
0836       });
0837       String[] filenames = new String[files == null : files.length];
0838       for (int i = 0; i < filenames.length; i++) {
0839         filenames[i= files[i].getName();
0840       }
0841       Arrays.sort(filenames, collator);
0842       newListComboBox.setModel(new DefaultComboBoxModel<String>(filenames));
0843       if (filenames.length == 0) {
0844         newListButton.setEnabled(false);
0845       }
0846     }
0847   }
0848 
0849   protected class ListTableModel extends AbstractTableModel {
0850 
0851     public ListTableModel() {
0852       gazetteerListFiltered = new GazetteerList();
0853     }
0854 
0855     @Override
0856     public int getRowCount() {
0857       return gazetteerListFiltered.size();
0858     }
0859 
0860     @Override
0861     public int getColumnCount() {
0862       if (columnCount > -1) { return columnCount; }
0863       if (gazetteerListFiltered == null) { return 0}
0864       columnCount = 1;
0865       // read all the features maps to find the biggest one
0866       for (Object object : gazetteerListFiltered) {
0867         GazetteerNode node = (GazetteerNodeobject;
0868         Map<String,Object> map = node.getFeatureMap();
0869         if (map != null && columnCount < 2*map.size()+1) {
0870           columnCount = 2*map.size() 1;
0871         }
0872       }
0873       return columnCount;
0874     }
0875 
0876     @Override
0877     public String getColumnName(int column) {
0878       if (column == 0) {
0879         return "Value";
0880       else {
0881         int featureCount = (column + (column % 2)) 2;
0882         if (column % == 1) {
0883           return "Feature " + featureCount;
0884         else {
0885           return "Value " + featureCount;
0886         }
0887       }
0888     }
0889 
0890     @Override
0891     public boolean isCellEditable(int row, int column) {
0892       return true;
0893     }
0894 
0895     @Override
0896     public Object getValueAt(int row, int column) {
0897       GazetteerNode node = gazetteerListFiltered.get(row);
0898       if (column == 0) {
0899         return node.getEntry();
0900       else {
0901         Map<String,Object> featureMap = node.getFeatureMap();
0902         if (featureMap == null
0903          || featureMap.size()*< column) {
0904           return "";
0905         }
0906         List<String> features = new ArrayList<String>(featureMap.keySet());
0907         int featureCount = (column + (column % 2)) 2;
0908         if (column % == 1) {
0909           return features.get(featureCount-1);
0910         else {
0911           return featureMap.get(features.get(featureCount-1));
0912         }
0913       }
0914     }
0915 
0916     @Override
0917     public void setValueAt(Object value, int row, int column) {
0918       if (row == -|| column == -1) { return}
0919       // remove separator characters that are contained in the value
0920       // and display a tooltip to explain it
0921       if (linearDefinition.getSeparator() != null
0922        && linearDefinition.getSeparator().length() 0
0923        && ((String)value).contains(linearDefinition.getSeparator())) {
0924         final Point point = listTable.getCellRect(listTable.getSelectedRow(),
0925           listTable.getSelectedColumn()true).getLocation();
0926         point.translate(listTable.getLocationOnScreen().x,
0927           listTable.getLocationOnScreen().y);
0928         final Timer timer = new Timer("GazetteerEditor tooltip timer"true);
0929         SwingUtilities.invokeLater(new Runnable() { @Override
0930         public void run() {
0931           if (!listTable.isShowing()) { return}
0932           JToolTip toolTip = listTable.createToolTip();
0933           toolTip.setTipText("No separator character allowed: [" +
0934             linearDefinition.getSeparator() "]");
0935           PopupFactory popupFactory = PopupFactory.getSharedInstance();
0936           final Popup popup = popupFactory.getPopup(
0937             listTable, toolTip, point.x, point.y - 20);
0938           popup.show();
0939           Date timeToRun = new Date(System.currentTimeMillis() 3000);
0940           timer.schedule(new TimerTask() { @Override
0941           public void run() {
0942             SwingUtilities.invokeLater(new Runnable() { @Override
0943             public void run() {
0944               popup.hide()// hide the tooltip after some time
0945             }});
0946           }}, timeToRun);
0947         }});
0948         value = ((String)value).replaceAll(
0949           "\\Q"+linearDefinition.getSeparator()+"\\E""");
0950       }
0951       GazetteerNode gazetteerNode = gazetteerListFiltered.get(row);
0952       if (column == 0) {
0953         // update entry
0954         gazetteerNode.setEntry((Stringvalue);
0955       else {
0956         // update the whole feature map
0957         Map<String,Object> newFeatureMap = new LinkedHashMap<String,Object>();
0958         for (int col = 1; col+< getColumnCount(); col += 2) {
0959           String feature = (String) ((col == column?
0960             value : getValueAt(row, col));
0961           String val = (String) ((col+== column?
0962             value : (StringgetValueAt(row, col+1));
0963           newFeatureMap.put(feature, val);
0964         }
0965         gazetteerNode.setFeatureMap(newFeatureMap);
0966         fireTableRowsUpdated(row, row);
0967       }
0968       gazetteerList.setModified(true);
0969       definitionTable.repaint();
0970     }
0971 
0972     @Override
0973     public void fireTableStructureChanged() {
0974       columnCount = -1;
0975       super.fireTableStructureChanged();
0976     }
0977 
0978     @Override
0979     public void fireTableChanged(TableModelEvent e) {
0980       if (gazetteerList == null) { return}
0981       if (filter.length() 1) {
0982         gazetteerListFiltered.clear();
0983         gazetteerListFiltered.addAll(gazetteerList);
0984         super.fireTableChanged(e);
0985       else {
0986         filterRows();
0987         // same as super.fireTableDataChanged() to avoid recursion
0988         super.fireTableChanged(new TableModelEvent(this));
0989       }
0990     }
0991 
0992     /**
0993      * Filter the table rows against this filter.
0994      @param filter string used to filter rows
0995      */
0996     public void setFilterText(String filter) {
0997       this.filter = filter;
0998     }
0999 
1000     protected void filterRows() {
1001       String patternText = filter;
1002       String prefixPattern = regexCheckBox.isSelected() "":"\\Q";
1003       String suffixPattern = regexCheckBox.isSelected() "":"\\E";
1004       patternText = prefixPattern + patternText + suffixPattern;
1005       Pattern pattern;
1006       try {
1007         pattern = caseInsensitiveCheckBox.isSelected() ?
1008           Pattern.compile(patternText, Pattern.CASE_INSENSITIVE:
1009           Pattern.compile(patternText);
1010       catch (PatternSyntaxException e) {
1011         return;
1012       }
1013       gazetteerListFiltered.clear();
1014       for (Object object : gazetteerList) {
1015         GazetteerNode node = (GazetteerNodeobject;
1016         boolean match = false;
1017         Map<String,Object> map = node.getFeatureMap();
1018         if (map != null && !onlyValueCheckBox.isSelected()) {
1019           for (String key : map.keySet()) {
1020             if (pattern.matcher(key).find()
1021              || pattern.matcher((Stringmap.get(key)).find()) { 
1022               match = true;
1023               break;
1024             }
1025           }
1026         }
1027         if (match || pattern.matcher(node.getEntry()).find()) {
1028           // gazetteer node matches the filter
1029           gazetteerListFiltered.add(node);
1030         }
1031       }
1032     }
1033 
1034     public void addEmptyFeatureColumns() {
1035       // find the first row fully filled with value
1036       if (getColumnCount() == 1) {
1037         GazetteerNode node = gazetteerListFiltered.get(0);
1038         Map<String, Object> map = new HashMap<String, Object>();
1039         // add a couple of rows
1040         map.put("""");
1041         node.setFeatureMap(map);
1042       else {
1043         for (Object object : gazetteerListFiltered) {
1044           GazetteerNode node = (GazetteerNodeobject;
1045           Map<String,Object> map = node.getFeatureMap();
1046           if (map != null
1047           && (2*map.size()+1== getColumnCount()) {
1048             map.put("""");
1049             break;
1050           }
1051         }
1052       }
1053       for (Object object : gazetteerList) {
1054         GazetteerNode node = (GazetteerNodeobject;
1055         node.setSeparator(linearDefinition.getSeparator());
1056       }
1057     }
1058 
1059     public void addRow(GazetteerNode gazetteerNode) {
1060       gazetteerList.add(gazetteerNode);
1061     }
1062 
1063     /**
1064      @param row row index in the model
1065      */
1066     public void removeRow(int row) {
1067       gazetteerList.remove(gazetteerListFiltered.get(row));
1068     }
1069 
1070     public void setGazetteerList(GazetteerList gazetteerList) {
1071       this.gazetteerList = gazetteerList;
1072     }
1073 
1074     private int columnCount = -1;
1075     private String filter = "";
1076     private GazetteerList gazetteerList;
1077     private GazetteerList gazetteerListFiltered;
1078   }
1079 
1080   @Override
1081   public List<Action> getActions() {
1082     return actions;
1083   }
1084 
1085   protected class ReloadGazetteerListAction extends AbstractAction {
1086     public ReloadGazetteerListAction() {
1087       super("Reload List");
1088     }
1089     @Override
1090     public void actionPerformed(ActionEvent e) {
1091       if (selectedLinearNode == null) { return}
1092       GazetteerList gazetteerList = linearDefinition.getListsByNode().get(selectedLinearNode);
1093       gazetteerList.clear();
1094       try {
1095         gazetteerList.load(true);
1096       catch (ResourceInstantiationException rie) {
1097         rie.printStackTrace();
1098         return;
1099       }
1100       // reselect the row to redisplay the list
1101       int row = definitionTable.getSelectedRow();
1102       definitionTable.clearSelection();
1103       definitionTable.getSelectionModel().setSelectionInterval(row, row);
1104     }
1105   }
1106 
1107   protected class SaveAndReinitialiseGazetteerAction extends AbstractAction {
1108     public SaveAndReinitialiseGazetteerAction() {
1109       super("Save and Reinitialise");
1110       putValue(SHORT_DESCRIPTION,
1111         "Save the definition and all the lists then reinitialise");
1112       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control S"));
1113     }
1114     @Override
1115     public void actionPerformed(ActionEvent e) {
1116       try {
1117         if (linearDefinition.isModified()) {
1118           linearDefinition.store();
1119         }
1120         for (Object object : linearDefinition.getListsByNode().values()) {
1121           GazetteerList gazetteerList = (GazetteerListobject;
1122           if (gazetteerList.isModified()) {
1123             gazetteerList.store();
1124           }
1125         }
1126         gazetteer.reInit();
1127         MainFrame.getInstance().statusChanged("Gazetteer saved in " +
1128           linearDefinition.getURL().getPath());
1129         definitionTable.repaint();
1130 
1131       catch (ResourceInstantiationException re) {
1132         MainFrame.getInstance().statusChanged(
1133           "Unable to save the Gazetteer.");
1134         Err.prln("Unable to save the Gazetteer.\n" + re.getMessage());
1135       }
1136     }
1137   }
1138 
1139   protected class SaveAsGazetteerAction extends AbstractAction {
1140     public SaveAsGazetteerAction() {
1141       super("Save as...");
1142       putValue(SHORT_DESCRIPTION, "Save the definition and all the lists");
1143       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control shift S"));
1144     }
1145     @Override
1146     public void actionPerformed(ActionEvent e) {
1147       XJFileChooser fileChooser = MainFrame.getFileChooser();
1148       ExtensionFileFilter filter =
1149         new ExtensionFileFilter("Gazetteer files""def");
1150       fileChooser.addChoosableFileFilter(filter);
1151       fileChooser.setMultiSelectionEnabled(false);
1152       fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
1153       fileChooser.setDialogTitle("Select a file name...");
1154       fileChooser.setResource(GazetteerEditor.class.getName());
1155       int result = fileChooser.showSaveDialog(GazetteerEditor.this);
1156       if (result == JFileChooser.APPROVE_OPTION) {
1157         File selectedFile = fileChooser.getSelectedFile();
1158         if (selectedFile == null) { return}
1159         try {
1160           URL previousURL = linearDefinition.getURL();
1161           linearDefinition.setURL(selectedFile.toURI().toURL());
1162           linearDefinition.store();
1163           linearDefinition.setURL(previousURL);
1164           for (Object object : linearDefinition.getListsByNode().values()) {
1165             GazetteerList gazetteerList = (GazetteerListobject;
1166             previousURL = gazetteerList.getURL();
1167             gazetteerList.setURL(new File(selectedFile.getParentFile(),
1168               Files.fileFromURL(gazetteerList.getURL()).getName())
1169               .toURI().toURL());
1170             gazetteerList.store();
1171             gazetteerList.setURL(previousURL);
1172             gazetteerList.setModified(false);
1173           }
1174           MainFrame.getInstance().statusChanged("Gazetteer saved in " +
1175             selectedFile.getAbsolutePath());
1176           definitionTable.repaint();
1177 
1178         catch (ResourceInstantiationException re) {
1179           MainFrame.getInstance().statusChanged(
1180             "Unable to save the Gazetteer.");
1181           Err.prln("Unable to save the Gazetteer.\n" + re.getMessage());
1182         catch (MalformedURLException mue) {
1183           mue.printStackTrace();
1184         }
1185       }
1186     }
1187   }
1188 
1189   protected class DeleteSelectedLinearNodeAction extends AbstractAction {
1190     public DeleteSelectedLinearNodeAction() {
1191       super(definitionTable.getSelectedRowCount() ?
1192         "Delete Rows" "Delete Row");
1193       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("shift DELETE"));
1194     }
1195 
1196     @Override
1197     public void actionPerformed(ActionEvent e) {
1198       int[] rowsToDelete = definitionTable.getSelectedRows();
1199       definitionTable.clearSelection();
1200       for (int i = 0; i < rowsToDelete.length; i++) {
1201         rowsToDelete[i= definitionTable.rowViewToModel(rowsToDelete[i]);
1202       }
1203       Arrays.sort(rowsToDelete);
1204       for (int i = rowsToDelete.length-1; i >= 0; i--) {
1205         definitionTableModel.removeRow(rowsToDelete[i]);
1206         linearDefinition.remove(rowsToDelete[i]);
1207       }
1208     }
1209   }
1210 
1211   protected class DeleteSelectedGazetteerNodeAction extends AbstractAction {
1212     public DeleteSelectedGazetteerNodeAction() {
1213       super(listTable.getSelectedRowCount() ?
1214         "Delete Rows" "Delete Row");
1215       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("shift DELETE"));
1216     }
1217 
1218     @Override
1219     public void actionPerformed(ActionEvent e) {
1220       int[] rowsToDelete = listTable.getSelectedRows();
1221       listTable.clearSelection();
1222       for (int i = 0; i < rowsToDelete.length; i++) {
1223         rowsToDelete[i= listTable.rowViewToModel(rowsToDelete[i]);
1224       }
1225       Arrays.sort(rowsToDelete);
1226       for (int i = rowsToDelete.length-1; i >= 0; i--) {
1227         listTableModel.removeRow(rowsToDelete[i]);
1228       }
1229       listTableModel.fireTableDataChanged();
1230     }
1231   }
1232 
1233   protected class FillDownSelectionAction extends AbstractAction {
1234     public FillDownSelectionAction() {
1235       super("Fill Down Selection");
1236       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control D"));
1237     }
1238 
1239     @Override
1240     public void actionPerformed(ActionEvent e) {
1241       int[] rows = listTable.getSelectedRows();
1242       int[] columns = listTable.getSelectedColumns();
1243       listTable.clearSelection();
1244       for (int column : columns) {
1245         String firstCell = (StringlistTable.getValueAt(rows[0], column);
1246         for (int row : rows) {
1247           listTableModel.setValueAt(firstCell,
1248             listTable.rowViewToModel(row),
1249             listTable.convertColumnIndexToModel(column));
1250         }
1251         listTableModel.fireTableDataChanged();
1252       }
1253     }
1254   }
1255 
1256   protected class CopySelectionAction extends AbstractAction {
1257     public CopySelectionAction() {
1258       super("Copy Selection");
1259       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control C"));
1260     }
1261 
1262     @Override
1263     public void actionPerformed(ActionEvent e) {
1264        listTable.getActionMap().get("copy").actionPerformed(
1265         new ActionEvent(listTable, ActionEvent.ACTION_PERFORMED, null));
1266 //        // generate a Control + V keyboard event
1267 //        listTable.dispatchEvent(
1268 //          new KeyEvent(listTable, KeyEvent.KEY_PRESSED,
1269 //            e.getWhen()+1, KeyEvent.CTRL_DOWN_MASK,
1270 //            KeyEvent.VK_V, KeyEvent.CHAR_UNDEFINED));
1271     }
1272   }
1273 
1274   protected class PasteSelectionAction extends AbstractAction {
1275     public PasteSelectionAction() {
1276       super("Paste Selection");
1277       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control V"));
1278     }
1279 
1280     @Override
1281     public void actionPerformed(ActionEvent e) {
1282       int firstRow = listTable.getSelectedRow();
1283       int firstColumn = listTable.getSelectedColumn();
1284       Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
1285       String valueCopied = null;
1286       try {
1287         if (clipboard.isDataFlavorAvailable(DataFlavor.stringFlavor)) {
1288           valueCopied = (Stringclipboard.getContents(null)
1289             .getTransferData(DataFlavor.stringFlavor);
1290         }
1291       catch (UnsupportedFlavorException e1) {
1292         e1.printStackTrace();
1293       catch (IOException e1) {
1294         e1.printStackTrace();
1295       }
1296       if (valueCopied == null) { return}
1297       int rowToPaste = firstRow;
1298       for (String rowCopied : valueCopied.split("\n")) {
1299         int columnToPaste = firstColumn;
1300         for (String cellCopied : rowCopied.split("\t")) {
1301           listTableModel.setValueAt(cellCopied,
1302             listTable.rowViewToModel(rowToPaste),
1303             listTable.convertColumnIndexToModel(columnToPaste));
1304           if (columnToPaste + > listTable.getColumnCount()) { break}
1305           columnToPaste++;
1306         }
1307         if (rowToPaste + > listTable.getRowCount()) { break}
1308         rowToPaste++;
1309       }
1310       listTableModel.fireTableDataChanged();
1311     }
1312   }
1313 
1314   protected class ClearSelectionAction extends AbstractAction {
1315     public ClearSelectionAction() {
1316       super("Clear Selection");
1317       putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke("control DELETE"));
1318     }
1319 
1320     @Override
1321     public void actionPerformed(ActionEvent e) {
1322       int[] rows = listTable.getSelectedRows();
1323       int[] columns = listTable.getSelectedColumns();
1324       listTable.clearSelection();
1325       for (int column : columns) {
1326         for (int row : rows) {
1327           listTableModel.setValueAt("",
1328             listTable.rowViewToModel(row),
1329             listTable.convertColumnIndexToModel(column));
1330         }
1331         listTableModel.fireTableDataChanged();
1332       }
1333     }
1334   }
1335 
1336   // local variables
1337   protected Gazetteer gazetteer;
1338   /** the linear definition being displayed */
1339   protected LinearDefinition linearDefinition;
1340   /** the linear node currently selected */
1341   protected LinearNode selectedLinearNode;
1342   protected Collator collator;
1343   protected List<Action> actions;
1344 
1345   // user interface components
1346   protected XJTable definitionTable;
1347   protected DefaultTableModel definitionTableModel;
1348   protected XJTable listTable;
1349   protected ListTableModel listTableModel;
1350   protected JComboBox<String> newListComboBox;
1351   protected JButton newListButton;
1352   protected JButton addColumnsButton;
1353   protected JTextField listEntryTextField;
1354   protected JCheckBox regexCheckBox;
1355   protected JCheckBox caseInsensitiveCheckBox;
1356   protected JCheckBox onlyValueCheckBox;
1357   protected JLabel listCountLabel;
1358 }