OntologyClassView.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 - 14/12/2009
0011  *
0012  *  $Id$
0013  */
0014 
0015 package gate.gui.docview;
0016 
0017 import gate.Annotation;
0018 import gate.AnnotationSet;
0019 import gate.FeatureMap;
0020 import gate.Gate;
0021 import gate.LanguageResource;
0022 import gate.Resource;
0023 import gate.creole.ontology.OClass;
0024 import gate.creole.ontology.OConstants;
0025 import gate.creole.ontology.OResource;
0026 import gate.creole.ontology.Ontology;
0027 import gate.creole.ontology.OntologyModificationListener;
0028 import gate.creole.ontology.RDFProperty;
0029 import gate.event.CreoleEvent;
0030 import gate.event.CreoleListener;
0031 import gate.gui.MainFrame;
0032 import gate.gui.annedit.AnnotationData;
0033 import gate.gui.annedit.AnnotationDataImpl;
0034 import gate.gui.ontology.OntologyItemComparator;
0035 import gate.util.LuckyException;
0036 import gate.util.OptionsMap;
0037 
0038 import java.awt.BorderLayout;
0039 import java.awt.Color;
0040 import java.awt.Component;
0041 import java.awt.FlowLayout;
0042 import java.awt.GridBagConstraints;
0043 import java.awt.GridBagLayout;
0044 import java.awt.Insets;
0045 import java.awt.Rectangle;
0046 import java.awt.event.ActionEvent;
0047 import java.awt.event.ActionListener;
0048 import java.awt.event.ItemEvent;
0049 import java.awt.event.ItemListener;
0050 import java.awt.event.MouseAdapter;
0051 import java.awt.event.MouseEvent;
0052 import java.util.ArrayList;
0053 import java.util.Collections;
0054 import java.util.Enumeration;
0055 import java.util.EventObject;
0056 import java.util.HashMap;
0057 import java.util.HashSet;
0058 import java.util.Iterator;
0059 import java.util.LinkedHashSet;
0060 import java.util.List;
0061 import java.util.Map;
0062 import java.util.Set;
0063 import java.util.Vector;
0064 
0065 import javax.swing.AbstractAction;
0066 import javax.swing.AbstractCellEditor;
0067 import javax.swing.BorderFactory;
0068 import javax.swing.DefaultComboBoxModel;
0069 import javax.swing.JCheckBox;
0070 import javax.swing.JComboBox;
0071 import javax.swing.JLabel;
0072 import javax.swing.JMenuItem;
0073 import javax.swing.JPanel;
0074 import javax.swing.JPopupMenu;
0075 import javax.swing.JScrollPane;
0076 import javax.swing.JTextArea;
0077 import javax.swing.JTree;
0078 import javax.swing.SwingConstants;
0079 import javax.swing.SwingUtilities;
0080 import javax.swing.Timer;
0081 import javax.swing.UIManager;
0082 import javax.swing.border.Border;
0083 import javax.swing.event.MouseInputAdapter;
0084 import javax.swing.event.TreeExpansionEvent;
0085 import javax.swing.event.TreeSelectionEvent;
0086 import javax.swing.event.TreeSelectionListener;
0087 import javax.swing.event.TreeWillExpandListener;
0088 import javax.swing.text.BadLocationException;
0089 import javax.swing.tree.DefaultMutableTreeNode;
0090 import javax.swing.tree.DefaultTreeModel;
0091 import javax.swing.tree.ExpandVetoException;
0092 import javax.swing.tree.TreeCellEditor;
0093 import javax.swing.tree.TreeCellRenderer;
0094 import javax.swing.tree.TreePath;
0095 import javax.swing.tree.TreeSelectionModel;
0096 
0097 /**
0098  * Document view that displays an ontology class tree to annotate a document.
0099  *
0100  * tick the checkbox of a class to show the instances highlighted in the text
0101  * selected class will be used when creating a new annotation from a text
0102  *    selection in the document
0103  * take only the first 20 characters of the selection for the instance name
0104  *    and add a number if already existing
0105  * allow multiple ontologies to be used at the same time
0106  * put each ontology in a JPanel with triangle icons to hide/show the panel,
0107  *    hidden by default
0108  * open only first level of classes when opening an ontology
0109  * load lazily the ontology trees
0110  * context menu for classes to hide/show them, saved in user configuration
0111  */
0112 @SuppressWarnings("serial")
0113 public class OntologyClassView extends AbstractDocumentView
0114     implements CreoleListener, OntologyModificationListener {
0115 
0116   public OntologyClassView() {
0117 
0118     colorByClassMap = new HashMap<OClass, Color>();
0119     highlightedClasses = new HashSet<OClass>();
0120     highlightsDataByClassMap = new HashMap<OClass, List<TextualDocumentView.HighlightData>>();
0121     treeByOntologyMap = new HashMap<Ontology, JTree>();
0122     String prefix = getClass().getName() '.';
0123     hiddenClassesSet = userConfig.getSet(prefix + "hiddenclasses");
0124     itemComparator = new OntologyItemComparator();
0125   }
0126 
0127   @Override
0128   protected void initGUI() {
0129 
0130     // get a pointer to the text view used to display
0131     // the selected annotations
0132     Iterator<DocumentView> centralViewsIter = owner.getCentralViews().iterator();
0133     while(textView == null && centralViewsIter.hasNext()){
0134       DocumentView aView = centralViewsIter.next();
0135       if(aView instanceof TextualDocumentView)
0136         textView = (TextualDocumentViewaView;
0137     }
0138     textArea = textView.getTextView();
0139     // get a pointer to the instance view
0140     Iterator<DocumentView> horizontalViewsIter = owner.getHorizontalViews().iterator();
0141     while(instanceView == null && horizontalViewsIter.hasNext()){
0142       DocumentView aView = horizontalViewsIter.next();
0143       if (aView instanceof OntologyInstanceView) {
0144         instanceView = (OntologyInstanceViewaView;
0145       }
0146     }
0147     instanceView.setOwner(owner);
0148 
0149     mainPanel = new JPanel(new GridBagLayout());
0150     GridBagConstraints gbc = new GridBagConstraints();
0151     gbc.gridx = 0;
0152     treesPanel = new JPanel();
0153     treesPanel.setLayout(new GridBagLayout());
0154     // add a disclosure panel for each loaded ontology in the system
0155     boolean isOntologyLoaded = false;
0156     List<LanguageResource> resources =
0157       gate.Gate.getCreoleRegister().getPublicLrInstances();
0158     for (LanguageResource resource : resources) {
0159       if (resource instanceof Ontology) {
0160         loadOntology((Ontologyresource);
0161         isOntologyLoaded = true;
0162       }
0163     }
0164     gbc.weightx = 1;
0165     gbc.weighty = 1;
0166     gbc.fill = GridBagConstraints.BOTH;
0167     gbc.anchor = GridBagConstraints.NORTHWEST;
0168     mainPanel.add(new JScrollPane(treesPanel), gbc);
0169     setComboBox = new JComboBox<String>();
0170     setComboBox.setEditable(true);
0171     setComboBox.setToolTipText(
0172       "Annotation set where to load/save the annotations");
0173     gbc.weighty = 0;
0174     gbc.fill = GridBagConstraints.HORIZONTAL;
0175     gbc.anchor = GridBagConstraints.SOUTH;
0176     mainPanel.add(setComboBox, gbc);
0177 
0178     initListeners();
0179 
0180     // fill the annotation sets list
0181     List<String> annotationSets = new ArrayList<String>();
0182     annotationSets.add("");
0183     annotationSets.addAll(document.getAnnotationSetNames());
0184     Collections.sort(annotationSets);
0185     setComboBox.setModel(new DefaultComboBoxModel<String>(
0186       new Vector<String>(annotationSets)));
0187 
0188     if (isOntologyLoaded) {
0189       // find the first set that contains annotations used before by this view
0190       selectedSet = "";
0191       for (int i = 0; i < setComboBox.getItemCount(); i++) {
0192         String setName = setComboBox.getItemAt(i);
0193         if (setColorTreeNodesWhenInstancesFound(setName)) {
0194           selectedSet = setName;
0195           break;
0196         }
0197       }
0198       setComboBox.setSelectedItem(selectedSet);
0199     else {
0200       messageLabel = new JLabel(
0201         "<html><p><font color=red>Load at least one ontology.");
0202       messageLabel.setHorizontalAlignment(SwingConstants.CENTER);
0203       messageLabel.setBorder(BorderFactory.createEmptyBorder(5252));
0204       messageLabel.setBackground(
0205         UIManager.getColor("Tree.selectionBackground"));
0206       gbc = new GridBagConstraints();
0207       treesPanel.add(messageLabel, gbc);
0208     }
0209   }
0210 
0211   protected void initListeners() {
0212 
0213     Gate.getCreoleRegister().addCreoleListener(this);
0214 
0215     setComboBox.addItemListener(new ItemListener() {
0216       @Override
0217       public void itemStateChanged(ItemEvent e) {
0218         selectedSet = (StringsetComboBox.getSelectedItem();
0219         setColorTreeNodesWhenInstancesFound(selectedSet);
0220         // unselect annotations
0221         SwingUtilities.invokeLater(new Runnable() { @Override
0222         public void run() {
0223           for (OClass oClass : highlightedClasses) {
0224             if (highlightsDataByClassMap.containsKey(oClass)) {
0225               textView.removeHighlights(highlightsDataByClassMap.get(oClass));
0226             }
0227           }
0228           highlightsDataByClassMap.clear();
0229           highlightedClasses.clear();
0230           // update showing trees
0231           for (JTree tree : treeByOntologyMap.values()) {
0232             if (tree.isShowing()) {
0233               tree.revalidate();
0234             }
0235           }
0236         }});
0237       }
0238     });
0239 
0240     // a listener that stops or restarts a timer which calls an action
0241     mouseStoppedMovingAction = new MouseStoppedMovingAction();
0242     mouseMovementTimer = new javax.swing.Timer(
0243       MOUSE_MOVEMENT_TIMER_DELAY, mouseStoppedMovingAction);
0244     mouseMovementTimer.setRepeats(false);
0245     textMouseListener = new TextMouseListener();
0246   }
0247 
0248   @Override
0249   protected void registerHooks() {
0250     textArea.addMouseListener(textMouseListener);
0251     textArea.addMouseMotionListener(textMouseListener);
0252     // reselect annotations
0253     SwingUtilities.invokeLater(new Runnable() { @Override
0254     public void run() {
0255       for (OClass oClass : new HashSet<OClass>(highlightedClasses)) {
0256         if (highlightsDataByClassMap.containsKey(oClass)) {
0257           textView.addHighlights(highlightsDataByClassMap.get(oClass));
0258         }
0259       }
0260     }});
0261     // show the instance view at the bottom
0262     if (!instanceView.isActive()) {
0263       owner.setBottomView(owner.horizontalViews.indexOf(instanceView));
0264     }
0265   }
0266 
0267   @Override
0268   protected void unregisterHooks() {
0269     textArea.removeMouseListener(textMouseListener);
0270     textArea.removeMouseMotionListener(textMouseListener);
0271     // unselect annotations
0272     SwingUtilities.invokeLater(new Runnable() { @Override
0273     public void run() {
0274       for (OClass oClass : highlightedClasses) {
0275         if (highlightsDataByClassMap.containsKey(oClass)) {
0276           textView.removeHighlights(highlightsDataByClassMap.get(oClass));
0277         }
0278       }
0279     }});
0280     // hide the instance view at the bottom
0281     if (instanceView.isActive()) {
0282       owner.setBottomView(-1);
0283     }
0284   }
0285 
0286   @Override
0287   public void cleanup() {
0288     super.cleanup();
0289     Gate.getCreoleRegister().removeCreoleListener(this);
0290     document = null;
0291     // save hidden classes to be reused next time
0292     String prefix = getClass().getName() '.';
0293     userConfig.put(prefix + "hiddenclasses", hiddenClassesSet);
0294   }
0295 
0296   @Override
0297   public Component getGUI() {
0298     return mainPanel;
0299   }
0300 
0301   @Override
0302   public int getType() {
0303     return VERTICAL;
0304   }
0305 
0306   @Override
0307   public void resourceLoaded(CreoleEvent e) {
0308     if (e.getResource() instanceof Ontology) {
0309       if (messageLabel != null
0310        && treesPanel.isAncestorOf(messageLabel)) {
0311         treesPanel.remove(messageLabel);
0312         // find the first set that contains annotations used before by this view
0313         selectedSet = "";
0314         for (int i = 0; i < setComboBox.getItemCount(); i++) {
0315           String setName = setComboBox.getItemAt(i);
0316           if (setColorTreeNodesWhenInstancesFound(setName)) {
0317             selectedSet = setName;
0318             break;
0319           }
0320         }
0321         setComboBox.setSelectedItem(selectedSet);
0322       }
0323       Ontology ontology = (Ontologye.getResource();
0324       loadOntology(ontology);
0325       // listen to modification of classes in the ontology to rebuild the tree
0326       ontology.addOntologyModificationListener(this);
0327     }
0328   }
0329 
0330   @Override
0331   public void resourceUnloaded(CreoleEvent e) {
0332     if (e.getResource() instanceof Ontology) {
0333       Ontology ontology = (Ontologye.getResource();
0334       JTree tree = treeByOntologyMap.remove(ontology);
0335       for (Component component : treesPanel.getComponents()) {
0336         if (component instanceof JPanel
0337         && ((JPanelcomponent).isAncestorOf(tree)) {
0338           treesPanel.remove(component);
0339         }
0340       }
0341       treesPanel.revalidate();
0342     }
0343   }
0344 
0345   @Override
0346   public void datastoreOpened(CreoleEvent e) { /* do nothing */ }
0347 
0348   @Override
0349   public void datastoreCreated(CreoleEvent e) { /* do nothing */ }
0350 
0351   @Override
0352   public void datastoreClosed(CreoleEvent e) { /* do nothing */ }
0353 
0354   @Override
0355   public void resourceRenamed(Resource resource, String oldName,
0356                               String newName) { /* do nothing */ }
0357   @Override
0358   public void resourceRelationChanged(Ontology ontology, OResource
0359     resource1, OResource resource2, int eventType) { /* do nothing */  }
0360 
0361   @Override
0362   public void resourcePropertyValueChanged(Ontology ontology, OResource
0363     resource, RDFProperty property, Object value, int eventType) {
0364     /* do nothing */  }
0365 
0366   @Override
0367   public void resourcesRemoved(Ontology ontology, String[] resources) {  }
0368 
0369   @Override
0370   public void resourceAdded(Ontology ontology, OResource resource) {
0371     if (resource instanceof OClass) {
0372       final JTree tree = treeByOntologyMap.get(ontology);
0373       DefaultMutableTreeNode node =
0374         (DefaultMutableTreeNodetree.getModel().getRoot();
0375       final Enumeration<?> enumeration = node.preorderEnumeration();
0376       SwingUtilities.invokeLater(new Runnable() { @Override
0377       public void run() {
0378         // traverse the expanded class tree and update all the nodes
0379         while (enumeration.hasMoreElements()) {
0380           DefaultMutableTreeNode node =
0381             (DefaultMutableTreeNodeenumeration.nextElement();
0382           Object userObject = node.getUserObject();
0383           if (userObject != null
0384           && !userObject.equals("Loading...")) {
0385             // node already expanded
0386             OClass oClass = (OClassuserObject;
0387             Set<OClass> classes = oClass.getSubClasses(
0388               OConstants.Closure.DIRECT_CLOSURE);
0389             // readd all the children node
0390             addNodes(tree, node, classes, false);
0391           }
0392         }
0393       }});
0394     }
0395   }
0396 
0397   @Override
0398   public void ontologyReset(Ontology ontology) { /* do nothing */ }
0399 
0400   /**
0401    * Extract annotations that have been created by this view and
0402    * colored the corresponding tree class node if found.
0403    @param setName the annotation set name to search
0404    @return true if and only if at least one annotation has been found
0405    */
0406   protected boolean setColorTreeNodesWhenInstancesFound(String setName) {
0407     boolean returnValue = false;
0408     List<LanguageResource> resources =
0409       gate.Gate.getCreoleRegister().getPublicLrInstances();
0410     Map<String, Ontology> ontologyMap = new HashMap<String, Ontology>();
0411     for (LanguageResource resource : resources) {
0412       if (resource instanceof Ontology) {
0413         Ontology ontology = (Ontologyresource;
0414         String ontologyName = ontology.getDefaultNameSpace();
0415         ontologyName = ontologyName.substring(0, ontologyName.length()-1);
0416         ontologyMap.put(ontologyName, ontology);
0417       }
0418     }
0419     for (Annotation annotation :
0420         document.getAnnotations(setName).get(ANNOTATION_TYPE)) {
0421       FeatureMap features = annotation.getFeatures();
0422       if (features.get(ONTOLOGY!= null
0423        && features.get(CLASS!= null
0424        && features.get(INSTANCE!= null) {
0425         // find the corresponding ontology
0426         Ontology ontology = ontologyMap.get(features.get(ONTOLOGY));
0427         if (ontology != null) {
0428           // choose a background color for the annotation type tree node
0429           OClass oClass = ontology.getOClass(ontology
0430             .createOURI((Stringfeatures.get(CLASS)));
0431           if (oClass != null) {
0432             colorByClassMap.put(oClass,
0433               AnnotationSetsView.getColor(setName, oClass.getName()));
0434             returnValue = true;
0435           }
0436         }
0437       }
0438     }
0439     return returnValue;
0440   }
0441 
0442   /**
0443    * Add the ontology in a disclosure panel, closed at start.
0444    @param ontology ontology to display
0445    */
0446   protected void loadOntology(final Ontology ontology) {
0447 
0448     // create the class tree
0449     final JTree tree = new JTree(new Object[]{"Loading..."});
0450     treeByOntologyMap.put(ontology, tree);
0451     tree.setRootVisible(false);
0452     tree.setShowsRootHandles(true);
0453     tree.setEditable(true);
0454     tree.getSelectionModel().setSelectionMode(
0455       TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
0456 
0457     final JPanel treePanel = new JPanel(new BorderLayout());
0458     final JCheckBox disclosureCheckBox = new JCheckBox(
0459       ontology.getName(), MainFrame.getIcon("closed")false);
0460     disclosureCheckBox.setSelectedIcon(MainFrame.getIcon("expanded"));
0461     treePanel.add(disclosureCheckBox, BorderLayout.NORTH);
0462 
0463     // show/hide the tree when clicking the disclosure checkbox
0464     disclosureCheckBox.addActionListener(new ActionListener() {
0465       boolean isTreeBuilt = false;
0466       @Override
0467       public void actionPerformed(ActionEvent e) {
0468         if (disclosureCheckBox.isSelected()) {
0469           if (!isTreeBuilt) {
0470             tree.expandRow(0)// expands "Loading..." node
0471             buildClassTree(tree, ontology);
0472             isTreeBuilt = true;
0473           }
0474           treePanel.add(tree, BorderLayout.CENTER);
0475         else {
0476           treePanel.remove(tree);
0477         }
0478         treesPanel.repaint();
0479       }
0480     });
0481 
0482     // context menu to show root classes
0483     disclosureCheckBox.addMouseListener(new MouseAdapter() {
0484       @Override
0485       public void mousePressed(MouseEvent e) { processMouseEvent(e)}
0486       @Override
0487       public void mouseReleased(MouseEvent e) { processMouseEvent(e)}
0488       @Override
0489       public void mouseClicked(MouseEvent e) { processMouseEvent(e)}
0490       protected void processMouseEvent(MouseEvent e) {
0491         JPopupMenu popup = new JPopupMenu();
0492         if (e.isPopupTrigger()) {
0493           popup.add(new JMenuItem(
0494             new AbstractAction("Show all root classes") {
0495             @Override
0496             public void actionPerformed(ActionEvent e) {
0497               if (!disclosureCheckBox.isSelected()) {
0498                 disclosureCheckBox.doClick();
0499               }
0500               hiddenClassesSet.clear();
0501               DefaultMutableTreeNode node = (DefaultMutableTreeNode)
0502                 tree.getModel().getRoot();
0503               final Set<OClass> classes = ontology.getOClasses(true);
0504               // add first level nodes to the tree
0505               addNodes(tree, node, classes, false);
0506             }
0507           }));
0508           popup.show(e.getComponent(), e.getX(), e.getY());
0509         }
0510       }
0511     });
0512 
0513     // when a class is selected in the tree update the instance table
0514     tree.getSelectionModel().addTreeSelectionListener(
0515       new TreeSelectionListener() {
0516         @Override
0517         public void valueChanged(TreeSelectionEvent e) {
0518           if (e.getNewLeadSelectionPath() == null) {
0519            if (treeByOntologyMap.get(selectedClass.getOntology()).equals(tree)){
0520              // only nullify selectedClass if unselect from the same tree
0521             selectedClass = null;
0522            }
0523           else {
0524             if (tree.getSelectionCount() == 1) { // a class is selected
0525             DefaultMutableTreeNode node = (DefaultMutableTreeNode)
0526               e.getNewLeadSelectionPath().getLastPathComponent();
0527             selectedClass = (OClassnode.getUserObject();
0528             else // several classes are selected
0529               selectedClass = null;
0530             }
0531             // clear selection in other trees
0532             for (JTree aTree : treeByOntologyMap.values()) {
0533               if (!aTree.equals(tree)) {
0534                 aTree.clearSelection();
0535               }
0536             }
0537           }
0538           instanceView.updateInstanceTable(selectedClass);
0539         }
0540       }
0541     );
0542 
0543     // context menu to hide/show classes
0544     tree.addMouseListener(new MouseAdapter() {
0545       @Override
0546       public void mousePressed(MouseEvent e) {
0547         TreePath path = tree.getClosestPathForLocation(e.getX(), e.getY());
0548         if (e.isPopupTrigger()
0549         && !tree.isPathSelected(path)) {
0550           // if right click outside the selection then reset selection
0551           tree.getSelectionModel().setSelectionPath(path);
0552         }
0553         processMouseEvent(e);
0554       }
0555       @Override
0556       public void mouseReleased(MouseEvent e) {
0557         processMouseEvent(e);
0558       }
0559       @Override
0560       public void mouseClicked(MouseEvent e) {
0561         processMouseEvent(e);
0562       }
0563       protected void processMouseEvent(MouseEvent e) {
0564         JPopupMenu popup = new JPopupMenu();
0565         if (!e.isPopupTrigger()) { return}
0566         popup.add(new JMenuItem(
0567           new AbstractAction("Hide selected classes") {
0568           @Override
0569           public void actionPerformed(ActionEvent e) {
0570             DefaultTreeModel treeModel = (DefaultTreeModeltree.getModel();
0571             TreePath[] selectedPaths = tree.getSelectionPaths();
0572             for (TreePath selectedPath : selectedPaths) {
0573               DefaultMutableTreeNode node = (DefaultMutableTreeNode)
0574                 selectedPath.getLastPathComponent();
0575               if (node.getParent() != null) {
0576                 treeModel.removeNodeFromParent(node);
0577                 Object userObject = node.getUserObject();
0578                 OClass oClass = (OClassuserObject;
0579                 hiddenClassesSet.add(oClass.getONodeID().toString());
0580               }
0581             }
0582           }
0583         }));
0584 
0585         if (tree.getSelectionCount() == 1) {
0586           popup.add(new JMenuItem(new AbstractAction("Show all sub classes") {
0587             @Override
0588             public void actionPerformed(ActionEvent e) {
0589               DefaultMutableTreeNode node = (DefaultMutableTreeNode)
0590                 tree.getSelectionPath().getLastPathComponent();
0591               Object userObject = node.getUserObject();
0592               OClass oClass = (OClassuserObject;
0593               Set<OClass> classes = oClass.getSubClasses(
0594                 OClass.Closure.DIRECT_CLOSURE);
0595               addNodes(tree, node, classes, false);
0596             }
0597           }));
0598         }
0599         popup.show(e.getComponent(), e.getX(), e.getY());
0600       }
0601     });
0602 
0603     GridBagConstraints  gbc = new GridBagConstraints();
0604     gbc.fill = GridBagConstraints.BOTH;
0605     gbc.anchor = GridBagConstraints.NORTHWEST;
0606     gbc.gridx = 0;
0607     gbc.weightx = 1;
0608     gbc.weighty = 1;
0609     treesPanel.add(treePanel, gbc);
0610   }
0611 
0612   /**
0613    * Build the class tree from the ontology.
0614    * Based on {@link gate.gui.ontology.OntologyEditor#rebuildModel()}.
0615    @param tree tree to build
0616    @param ontology ontology to use
0617    */
0618   protected void buildClassTree(final JTree tree, Ontology ontology) {
0619     if (ontology == null) { return}
0620 
0621     // listener to lazily create children nodes
0622     tree.addTreeWillExpandListener(new TreeWillExpandListener() {
0623       @Override
0624       public void treeWillExpand(TreeExpansionEvent event)
0625         throws ExpandVetoException {
0626         DefaultMutableTreeNode node = (DefaultMutableTreeNode)
0627           event.getPath().getLastPathComponent();
0628         DefaultMutableTreeNode nodeFirstChild =
0629           (DefaultMutableTreeNodenode.getChildAt(0);
0630         if (nodeFirstChild.getUserObject().equals("Loading...")) {
0631           // if this node has not already been expanded
0632           node.removeAllChildren();
0633           Object userObject = node.getUserObject();
0634           OClass oClass = (OClassuserObject;
0635           Set<OClass> classes =
0636             oClass.getSubClasses(OClass.Closure.DIRECT_CLOSURE);
0637           // add children nodes to the current tree node
0638           addNodes(tree, node, classes, true);
0639         }
0640       }
0641       @Override
0642       public void treeWillCollapse(TreeExpansionEvent event)
0643         throws ExpandVetoException /* do nothing */  }
0644     });
0645 
0646     final DefaultMutableTreeNode node = new DefaultMutableTreeNode(null, true);
0647     final Set<OClass> classes = ontology.getOClasses(true);
0648     // add first level nodes to the tree
0649     addNodes(tree, node, classes, true);
0650     SwingUtilities.invokeLater(new Runnable() { @Override
0651     public void run() {
0652       tree.setModel(new DefaultTreeModel(node));
0653       tree.setCellRenderer(new ClassTreeCellRenderer());
0654       tree.setCellEditor(new ClassTreeCellEditor(tree));
0655       DefaultMutableTreeNode node = (DefaultMutableTreeNode)
0656         tree.getModel().getRoot();
0657       Enumeration<?> enumeration = node.children();
0658       // expand tree until second level
0659       while (enumeration.hasMoreElements()) {
0660         node = (DefaultMutableTreeNodeenumeration.nextElement();
0661         tree.expandPath(new TreePath(node.getPath()));
0662       }
0663     }});
0664   }
0665 
0666   /**
0667    * Add children nodes to the parent node in the tree.
0668    @param tree tree to update
0669    @param parent parent node
0670    @param newChildren children classes to add
0671    @param filterClasses if children nodes contain hidden classes
0672    * then don't add them.
0673    */
0674   protected void addNodes(JTree tree, DefaultMutableTreeNode parent,
0675                           Set<OClass> newChildren,
0676                           boolean filterClasses) {
0677     // list the existing children classes of the parent node
0678     List<OClass> children = new ArrayList<OClass>();
0679     for (int i = 0; i < parent.getChildCount(); i++) {
0680       DefaultMutableTreeNode node =
0681         (DefaultMutableTreeNodeparent.getChildAt(i);
0682       Object userObject = node.getUserObject();
0683       if (userObject instanceof OClass) {
0684         OClass oClass = (OClassuserObject;
0685         children.add(oClass);
0686       else if (userObject.equals("Loading...")) {
0687         parent.removeAllChildren();
0688         children.clear();
0689         break;
0690       }
0691     }
0692     int index = -1;
0693     DefaultTreeModel treeModel = (DefaultTreeModeltree.getModel();
0694     List<OClass> subClasses = new ArrayList<OClass>(newChildren);
0695     Collections.sort(subClasses, itemComparator);
0696     // for each children classes to add to the parent node
0697     for (OClass subClass : subClasses) {
0698       index++;
0699       if (index > parent.getChildCount()) { index = parent.getChildCount()}
0700       if (filterClasses) {
0701         if (hiddenClassesSet.contains(subClass.getONodeID().toString())) {
0702           // this class is filtered so skip it
0703           continue;
0704         }
0705       else {
0706         hiddenClassesSet.remove(subClass.getONodeID().toString());
0707       }
0708       DefaultMutableTreeNode subNode = new DefaultMutableTreeNode(subClass);
0709       if (!filterClasses || !children.contains(subClass)) {
0710         if (!subClass.getSubClasses(OClass.Closure.DIRECT_CLOSURE).isEmpty()) {
0711           subNode.insert(new DefaultMutableTreeNode("Loading...")0);
0712         }
0713       }
0714       if (!children.contains(subClass)) {
0715         // add the children node if not already existing
0716         treeModel.insertNodeInto(subNode, parent, index);
0717       }
0718     }
0719     tree.expandPath(new TreePath(parent.getPath()));
0720   }
0721 
0722   /**
0723    * A mouse listener used for events in the text view.
0724    * Stop or restart the timer that will call {@link MouseStoppedMovingAction}.
0725    * Based on {@link AnnotationSetsView.TextMouseListener}.
0726    */
0727   protected class TextMouseListener extends MouseInputAdapter {
0728     @Override
0729     public void mouseDragged(MouseEvent e){
0730       //do not create annotations while dragging
0731       mouseMovementTimer.stop();
0732     }
0733     @Override
0734     public void mouseMoved(MouseEvent e){
0735       //this triggers select annotation leading to edit annotation or new
0736       //annotation actions
0737       //ignore movement if CTRL pressed or dragging
0738       int modEx = e.getModifiersEx();
0739       if((modEx & MouseEvent.CTRL_DOWN_MASK!= 0){
0740         mouseMovementTimer.stop();
0741         return;
0742       }
0743       if((modEx & MouseEvent.BUTTON1_DOWN_MASK!= 0){
0744         mouseMovementTimer.stop();
0745         return;
0746       }
0747       //check the text location is real
0748       int textLocation = textArea.viewToModel(e.getPoint());
0749       try {
0750         Rectangle viewLocation = textArea.modelToView(textLocation);
0751         //expand the rectangle a bit
0752         int error = 10;
0753         viewLocation = new Rectangle(viewLocation.x - error,
0754                                      viewLocation.y - error,
0755                                      viewLocation.width + 2*error,
0756                                      viewLocation.height + 2*error);
0757         if(viewLocation.contains(e.getPoint())){
0758           mouseStoppedMovingAction.setTextLocation(textLocation);
0759         }else{
0760           mouseStoppedMovingAction.setTextLocation(-1);
0761         }
0762       }
0763       catch(BadLocationException ble) {
0764         throw new LuckyException(ble);
0765       }finally{
0766         mouseMovementTimer.restart();
0767       }
0768     }
0769     @Override
0770     public void mouseExited(MouseEvent e){
0771       mouseMovementTimer.stop();
0772     }
0773   }
0774 
0775   /**
0776    * Add the text selection to the filter instance table to enable creating
0777    * a new instance from the selection or adding it as a new label to an
0778    * existing instance.
0779    * Based on {@link AnnotationSetsView.MouseStoppedMovingAction}.
0780    */
0781   protected class MouseStoppedMovingAction extends AbstractAction {
0782     @Override
0783     public void actionPerformed(ActionEvent evt) {
0784       List<LanguageResource> resources =
0785         gate.Gate.getCreoleRegister().getPublicLrInstances();
0786       Map<String, Ontology> ontologyMap = new HashMap<String, Ontology>();
0787       for (LanguageResource resource : resources) {
0788         if (resource instanceof Ontology) {
0789           Ontology ontology = (Ontologyresource;
0790           String ontologyName = ontology.getDefaultNameSpace();
0791           ontologyName = ontologyName.substring(0, ontologyName.length()-1);
0792           ontologyMap.put(ontologyName, ontology);
0793         }
0794       }
0795       // check for annotations at mouse location
0796       String setName = (StringsetComboBox.getSelectedItem();
0797       for (Annotation annotation : document.getAnnotations(setName)
0798             .get(ANNOTATION_TYPE).get(Math.max(0l, textLocation-1),
0799               Math.min(document.getContent().size(), textLocation+1))) {
0800         final FeatureMap features = annotation.getFeatures();
0801         if (features.get(ONTOLOGY!= null
0802          && features.get(CLASS!= null
0803          && features.get(INSTANCE!= null) {
0804           // find the corresponding ontology
0805           final Ontology ontology =
0806             ontologyMap.get(features.get(ONTOLOGY));
0807           if (ontology != null) {
0808             OClass oClass = ontology.getOClass(ontology
0809               .createOURI((Stringfeatures.get(CLASS)));
0810             // find if the annotation class is highlighted
0811             if (oClass != null
0812              && highlightedClasses.contains(oClass)) {
0813               final JTree tree = treeByOntologyMap.get(ontology);
0814               DefaultMutableTreeNode node =
0815                 (DefaultMutableTreeNodetree.getModel().getRoot();
0816               Enumeration<?> nodesEnum = node.preorderEnumeration();
0817               boolean done = false;
0818               // traverse the class tree
0819               while(!done && nodesEnum.hasMoreElements()) {
0820                 node = (DefaultMutableTreeNodenodesEnum.nextElement();
0821                 done = node.getUserObject() instanceof OClass
0822                     && node.getUserObject().equals(oClass);
0823               }
0824               if (done) {
0825                 // select the class in the tree
0826                 TreePath nodePath = new TreePath(node.getPath());
0827                 tree.setSelectionPath(nodePath);
0828                 tree.scrollPathToVisible(nodePath);
0829                 SwingUtilities.invokeLater(new Runnable() { @Override
0830                 public void run() {
0831                   // select the annotation in the instances table
0832                   instanceView.selectInstance(ontology.getOInstance(
0833                     ontology.createOURI((Stringfeatures.get(INSTANCE))));
0834                 }});
0835                 break;
0836               }
0837             }
0838           }
0839         }
0840       }
0841 
0842       int start = textArea.getSelectionStart();
0843       int end   = textArea.getSelectionEnd();
0844       String selectedText = textArea.getSelectedText();
0845       if (textLocation == -1
0846        || selectedClass == null
0847        || selectedText == null
0848        || start > textLocation
0849        || end < textLocation
0850        || start == end) {
0851         return;
0852       }
0853       // remove selection
0854       textArea.setSelectionStart(start);
0855       textArea.setSelectionEnd(start);
0856       instanceView.addSelectionToFilter(selectedSet, selectedText, start, end);
0857     }
0858 
0859     public void setTextLocation(int textLocation){
0860       this.textLocation = textLocation;
0861     }
0862     int textLocation;
0863   }
0864 
0865   public void setClassHighlighted(final OClass oClass, boolean isHighlighted) {
0866     final JTree tree = treeByOntologyMap.get(oClass.getOntology());
0867     if (isHighlighted) {
0868       // find all annotations for the class
0869       final List<AnnotationData> annotationsData =
0870         new ArrayList<AnnotationData>();
0871       AnnotationSet annotationSet = document.getAnnotations(selectedSet);
0872       String ontologyName = oClass.getOntology().getDefaultNameSpace();
0873       ontologyName = ontologyName.substring(0, ontologyName.length()-1);
0874       for (Annotation annotation : annotationSet.get(ANNOTATION_TYPE)) {
0875         FeatureMap features = annotation.getFeatures();
0876         if (features.get(ONTOLOGY!= null
0877         && features.get(ONTOLOGY).equals(ontologyName)
0878         && features.get(CLASS!= null
0879         && features.get(CLASS).equals(oClass.getONodeID().toString())
0880         && features.get(INSTANCE!= null) {
0881           annotationsData.add(new AnnotationDataImpl(annotationSet,annotation));
0882         }
0883       }
0884       highlightedClasses.add(oClass);
0885       if (annotationsData.isEmpty()) {
0886         // no instance annotation for this class
0887         colorByClassMap.remove(oClass);
0888         SwingUtilities.invokeLater(new Runnable() { @Override
0889         public void run() {
0890           if (highlightsDataByClassMap.containsKey(oClass)) {
0891             textView.removeHighlights(highlightsDataByClassMap.get(oClass));
0892           }
0893           highlightsDataByClassMap.remove(oClass);
0894           tree.repaint();
0895         }});
0896       else {
0897         final Color color;
0898         if (colorByClassMap.containsKey(oClass)) {
0899           color = colorByClassMap.get(oClass);
0900         else {
0901           color = AnnotationSetsView.getColor(selectedSet,oClass.getName());
0902           colorByClassMap.put(oClass, color);
0903         }
0904         SwingUtilities.invokeLater(new Runnable() { @Override
0905         public void run() {
0906           highlightsDataByClassMap.put(oClass,
0907             textView.addHighlights(annotationsData, color));
0908           tree.repaint();
0909         }});
0910       }
0911     else // if (!isHighlighted)
0912       highlightedClasses.remove(oClass);
0913         SwingUtilities.invokeLater(new Runnable() { @Override
0914         public void run() {
0915           if (highlightsDataByClassMap.containsKey(oClass)) {
0916             textView.removeHighlights(highlightsDataByClassMap.get(oClass));
0917           }
0918           tree.repaint();
0919         }});
0920     }
0921   }
0922 
0923   /**
0924    * To see if it's worth using it to optimise highlights display.
0925    @param set set
0926    @param annotation annotation
0927    @param oClass class
0928    @param tree tree
0929    */
0930   public void highlightInstance(AnnotationSet set, Annotation annotation,
0931                                 final OClass oClass, final JTree tree) {
0932     final AnnotationData annotationData = new AnnotationDataImpl(set, annotation);
0933     final List<TextualDocumentView.HighlightData> highlightsData = highlightsDataByClassMap.containsKey(oClass?
0934       highlightsDataByClassMap.get(oClassnew ArrayList<TextualDocumentView.HighlightData>();
0935     highlightedClasses.add(oClass);
0936     final Color color;
0937     if (colorByClassMap.containsKey(oClass)) {
0938       color = colorByClassMap.get(oClass);
0939     else {
0940       color = AnnotationSetsView.getColor(set.getName(),oClass.getName());
0941       colorByClassMap.put(oClass, color);
0942     }
0943     SwingUtilities.invokeLater(new Runnable() { @Override
0944     public void run() {
0945       highlightsData.add(textView.addHighlight(annotationData, color));
0946       highlightsDataByClassMap.put(oClass, highlightsData);
0947       tree.repaint();
0948     }});
0949   }
0950 
0951   protected class ClassTreeCellRenderer extends JPanel
0952       implements TreeCellRenderer {
0953 
0954     protected Object userObject;
0955     protected JCheckBox checkBox;
0956     protected JLabel label;
0957     private Color selectionColor =
0958       UIManager.getColor("Tree.selectionBackground");
0959     private Color backgroundColor = UIManager.getColor("Tree.textBackground");
0960     private Border normalBorder =
0961       BorderFactory.createLineBorder(backgroundColor, 1);
0962     private Border selectionBorder =
0963       BorderFactory.createLineBorder(selectionColor, 1);
0964 
0965     protected Object getUserObject() {
0966       return userObject;
0967     }
0968 
0969     protected JCheckBox getCheckBox() {
0970       return checkBox;
0971     }
0972 
0973     public ClassTreeCellRenderer() {
0974       setLayout(new FlowLayout(FlowLayout.LEFT, 20));
0975       setBorder(normalBorder);
0976       setOpaque(true);
0977       setBackground(backgroundColor);
0978       checkBox = new JCheckBox();
0979       checkBox.setMargin(new Insets(0000));
0980       checkBox.setOpaque(true);
0981       checkBox.setBackground(backgroundColor);
0982       add(checkBox);
0983       label = new JLabel();
0984       label.setOpaque(true);
0985       label.setBackground(backgroundColor);
0986       add(label);
0987     }
0988 
0989     @Override
0990     public Component getTreeCellRendererComponent(JTree tree, Object value,
0991                                boolean isSelected, boolean isExpanded,
0992                                boolean isLeaf, int row, boolean hasFocus) {
0993       DefaultMutableTreeNode node = (DefaultMutableTreeNodevalue;
0994       userObject = node.getUserObject();
0995       if (node.getUserObject() == null) { return this}
0996       OClass oClass = (OClassnode.getUserObject();
0997       checkBox.setSelected(highlightedClasses.contains(oClass));
0998       checkBox.setBackground(isSelected ? selectionColor : backgroundColor);
0999       label.setText(oClass.getName());
1000       label.setBackground(colorByClassMap.containsKey(oClass?
1001         colorByClassMap.get(oClass: isSelected ?
1002           selectionColor : backgroundColor);
1003         setBackground(isSelected ? selectionColor : backgroundColor);
1004       setBorder(isSelected ? selectionBorder : normalBorder);
1005 
1006       return this;
1007     }
1008   }
1009 
1010   protected class ClassTreeCellEditor extends AbstractCellEditor
1011       implements TreeCellEditor {
1012 
1013     ClassTreeCellRenderer renderer = new ClassTreeCellRenderer();
1014     JTree tree;
1015 
1016     public ClassTreeCellEditor(JTree tree) {
1017       this.tree = tree;
1018     }
1019 
1020     @Override
1021     public Object getCellEditorValue() {
1022       boolean isSelected = renderer.getCheckBox().isSelected();
1023       Object userObject = renderer.getUserObject();
1024       OClass oClass = (OClassuserObject;
1025       // show/hide highlights according to the checkbox state
1026       setClassHighlighted(oClass, isSelected);
1027       return userObject;
1028     }
1029 
1030     @Override
1031     public boolean isCellEditable(EventObject event) {
1032       boolean returnValue = false;
1033       if (event instanceof MouseEvent) {
1034         MouseEvent mouseEvent = (MouseEventevent;
1035         TreePath path = tree.getPathForLocation(mouseEvent.getX(),
1036                                                 mouseEvent.getY());
1037         if (path != null) {
1038           Object node = path.getLastPathComponent();
1039           if ((node != null&& (node instanceof DefaultMutableTreeNode)) {
1040             Rectangle r = tree.getPathBounds(path);
1041             int x = mouseEvent.getX() - r.x;
1042             JCheckBox checkbox = renderer.getCheckBox();
1043             // checks if the mouse click was on the checkbox not the label
1044             returnValue = x > && x < checkbox.getPreferredSize().width;
1045           }
1046         }
1047       }
1048       return returnValue;
1049     }
1050 
1051     @Override
1052     public Component getTreeCellEditorComponent(final JTree tree, Object value,
1053         boolean selected, boolean expanded, boolean leaf, int row) {
1054 
1055       // reuse renderer as an editor
1056       Component editor = renderer.getTreeCellRendererComponent(tree, value,
1057           true, expanded, leaf, row, true);
1058 
1059       // stop editing when checkbox has state changed
1060       renderer.getCheckBox().addItemListener(new ItemListener() {
1061         @Override
1062         public void itemStateChanged(ItemEvent itemEvent) {
1063           stopCellEditing();
1064         }
1065      });
1066 
1067       return editor;
1068     }
1069   }
1070 
1071   public String getSelectedSet() {
1072     return selectedSet;
1073   }
1074 
1075   // external resources
1076   protected TextualDocumentView textView;
1077   protected JTextArea textArea;
1078   protected OntologyInstanceView instanceView;
1079 
1080   // UI components
1081   protected JPanel mainPanel;
1082   protected JLabel messageLabel;
1083   protected JPanel treesPanel;
1084   protected JComboBox<String> setComboBox;
1085 
1086   // local objects
1087   /** Class that has the lead selection in the focused ontology tree. */
1088   protected OClass selectedClass;
1089   /** Classes highlighted in the document with their checkboxes ticked
1090    *  in the class tree. */
1091   protected Set<OClass> highlightedClasses;
1092   /** Colors for class and their instances only if the latter exist. */
1093   protected Map<OClass, Color> colorByClassMap;
1094   /** HighlightData list for each class. */
1095   protected Map<OClass, List<TextualDocumentView.HighlightData>> highlightsDataByClassMap;
1096   /** Link trees with their ontologies. */
1097   protected Map<Ontology, JTree> treeByOntologyMap;
1098   /** Classes to hide in the trees. */
1099   protected LinkedHashSet<String> hiddenClassesSet;
1100   /** Annotation set name where to read/save the instance annotations. */
1101   protected String selectedSet;
1102   protected OntologyItemComparator itemComparator;
1103   protected MouseStoppedMovingAction mouseStoppedMovingAction;
1104   protected TextMouseListener textMouseListener;
1105   protected Timer mouseMovementTimer;
1106   protected static final int MOUSE_MOVEMENT_TIMER_DELAY = 500;
1107   protected OptionsMap userConfig = Gate.getUserConfig();
1108 
1109   // constants for annotation feature, annotation type
1110   protected static final String ONTOLOGY =
1111     gate.creole.ANNIEConstants.LOOKUP_ONTOLOGY_FEATURE_NAME;
1112   protected static final String CLASS =
1113     gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME;
1114   protected static final String INSTANCE =
1115     gate.creole.ANNIEConstants.LOOKUP_INSTANCE_FEATURE_NAME;
1116   protected static final String ANNOTATION_TYPE = "Mention";
1117 }