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