/** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: John Thompson, Greenstone Digital Library, University of Waikato * * Copyright (C) 1999 New Zealand Digital Library Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ package org.greenstone.gatherer.cdm; import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; import java.util.jar.*; import javax.swing.*; import javax.swing.event.*; import org.apache.xerces.parsers.*; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.DebugStream; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.greenstone.Classifiers; import org.greenstone.gatherer.gui.DesignPaneHeader; import org.greenstone.gatherer.gui.GComboBox; import org.greenstone.gatherer.gui.GLIButton; import org.greenstone.gatherer.remote.RemoteGreenstoneServer; import org.greenstone.gatherer.util.JarTools; import org.greenstone.gatherer.util.StaticStrings; import org.greenstone.gatherer.util.Utility; import org.greenstone.gatherer.util.XMLTools; import org.w3c.dom.*; import org.xml.sax.*; /** This class is responsible for keeping track of all the classifiers assigned to this collection, and providing methods for adding and removing them. * @author John Thompson, Greenstone Digital Library, University of Waikato * @version 2.3 */ public class ClassifierManager extends DOMProxyListModel { /** The controls for editing the contents of this manager. */ private Control controls = null; private DOMProxyListModel model; /** Constructor. * @see org.greenstone.gatherer.cdm.DynamicListModel * @see org.greenstone.gatherer.collection.CollectionManager */ public ClassifierManager () { super (CollectionDesignManager.collect_config.getDocumentElement (), StaticStrings.CLASSIFY_ELEMENT, new Classifier ()); this.model = this; DebugStream.println ("ClassifierManager: " + getSize () + " classifiers parsed."); // Force the assigned classifiers to be loaded and cached now for (int i = 0; i < getSize (); i++) { getElementAt (i); } } /** Retrieve a list of the classifiers that are available to be added to the collection. */ private Object[] getAvailableClassifiers () { ArrayList available = new ArrayList (); // Add all the non-abstract core Greenstone classifiers ArrayList classifiers_list = Classifiers.getClassifiersList (); for (int i = 0; i < classifiers_list.size (); i++) { Classifier classifier = (Classifier) classifiers_list.get (i); if (!classifier.isAbstract ()) { available.add (classifier); } } // Sort the available classifiers into alphabetical order Collections.sort (available); return available.toArray (); } /** Method to assign a classifier (i.e., add a new classifier onto the list). * @param classifier The base Classifier to assign. * @see org.greenstone.gatherer.cdm.DynamicListModel */ private void assignClassifier (Classifier classifier) { if(!contains (classifier)) { Element element = classifier.getElement (); // Locate where we should insert this new classifier. Node target_node = CollectionConfiguration.findInsertionPoint (element); add (root, classifier, target_node); // tell the format manager to update the names of its format statements Gatherer.c_man.getCollection ().cdm.format_manager.refresh (); } } /** Destructor. */ public void destroy () { if (controls != null) { controls.destroy (); controls = null; } } /** Method to retrieve the classifier with the given index. * @param index The index of the desired classifier as an int. * @return The requested Classifier or null if no such classifier exists. */ public Classifier getClassifier (int index) { if(0 <= index && index < getSize ()) { return (Classifier) getElementAt (index); } return null; } /** Method to retrieve the control for this manager. * @return the Control for editing classifiers */ public Control getControls () { if(controls == null) { // Build controls this.controls = new ClassifierControl (); } return controls; } /** Called when the detail mode has changed which in turn may cause several design elements to be available/hidden * @param mode the new mode as an int */ public void modeChanged (int mode) { } /** Determine if the Phind classifier has been assigned. * @return true if it has, false otherwise */ public boolean isPhindClassifierAssigned () { for(int i = 0; i < getSize (); i++) { Classifier classifier = (Classifier) getElementAt (i); if(classifier.getName ().equalsIgnoreCase (StaticStrings.PHIND_CLASSIFIER)) { return true; } classifier = null; } return false; } /** Determine if a DateList classifier has been assigned * @return true if it has, false otherwise */ public boolean isDateListClassifierAssigned () { for(int i = 0; i < getSize (); i++) { Classifier classifier = (Classifier) getElementAt (i); if(classifier.getName ().equalsIgnoreCase (StaticStrings.DATELIST_CLASSIFIER)) { return true; } classifier = null; } return false; } /** Method to move a classifier in the list order. * @param classifier the Classifier you want to move. * @param direction true to move the classifier up, false to move it down. * @param all true to move to move all the way, false for a single step. */ private void moveClassifier (Classifier classifier, boolean direction, boolean all) { if(getSize () < 2) { DebugStream.println ("Not enough classifiers to allow moving."); return; } if(all) { // Move to top if(direction) { // Remove the moving classifier remove (classifier); // Retrieve the first classifier Classifier first_classifier = (Classifier) getElementAt (0); // Add the moving classifier before the first classifier addBefore (classifier, first_classifier); first_classifier = null; } else { // Remove the moving classifier remove (classifier); // And add after last classifier add (getSize (), classifier); } } else { // Try to move the classifier one step in the desired direction. int index = indexOf (classifier); ///ystem.err.println("Index of " + classifier + " = " + index); if(direction) { index--; if(index < 0) { String args[] = new String[2]; args[0] = Dictionary.get ("CDM.ClassifierManager.Classifier"); args[1] = classifier.getName (); JOptionPane.showMessageDialog (Gatherer.g_man, Dictionary.get ("CDM.Move.At_Top", args), Dictionary.get ("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE); return; } remove (classifier); add (index, classifier); } else { index++; if(index >= getSize ()) { String args[] = new String[2]; args[0] = Dictionary.get ("CDM.ClassifierManager.Classifier_Str"); args[1] = classifier.getName (); JOptionPane.showMessageDialog (Gatherer.g_man, Dictionary.get ("CDM.Move.At_Bottom", args), Dictionary.get ("CDM.Move.Title"), JOptionPane.ERROR_MESSAGE); return; } remove (classifier); add (index, classifier); } } // tell the format manager to update the names of its format statements Gatherer.c_man.getCollection ().cdm.format_manager.refresh (); } /** This method removes an assigned classifier. I was tempted to call it unassign, but remove is more consistant. Note that there is no way to remove a classifier from the library. * @param classifier The Classifier to remove * @see org.greenstone.gatherer.cdm.DynamicListModel */ private void removeClassifier (Classifier classifier) { remove (classifier); // tell the format manager to update the names of its format statements Gatherer.c_man.getCollection ().cdm.format_manager.refresh (); } // When remove, move, and add(assign) classifiers, this method has to be called to get the feature combobox // on the 'format feature' panel of the 'format' panel up to date. // private void informFormatManager() { // //this is really to call the gainFocus() method // CollectionDesignManager.format_manager.getControls(); // } /** A class which provides controls for assigned and editing classifiers. */ private class ClassifierControl extends JPanel implements Control { /** A combobox containing all of the known classifiers, including those that may have already been assigned. */ private JComboBox classifier_combobox = null; /** Button for adding classifiers. */ private JButton add = null; /** Button for configuring the selected classifier. */ private JButton configure = null; private JButton move_down_button; private JButton move_up_button; /** Button to remove the selected classifier. */ private JButton remove = null; /** A list of assigned classifiers. */ private JList classifier_list = null; /** Constructor. * @see org.greenstone.gatherer.cdm.ClassifierManager.ClassifierControl.AddListener * @see org.greenstone.gatherer.cdm.ClassifierManager.ClassifierControl.ConfigureListener * @see org.greenstone.gatherer.cdm.ClassifierManager.ClassifierControl.RemoveListener */ public ClassifierControl () { // Create this.setComponentOrientation(Dictionary.getOrientation()); add = new GLIButton (Dictionary.get ("CDM.ClassifierManager.Add"), Dictionary.get ("CDM.ClassifierManager.Add_Tooltip")); JPanel button_pane = new JPanel (); button_pane.setComponentOrientation(Dictionary.getOrientation()); JPanel central_pane = new JPanel (); central_pane.setComponentOrientation(Dictionary.getOrientation()); configure = new GLIButton (Dictionary.get ("CDM.ClassifierManager.Configure"), Dictionary.get ("CDM.ClassifierManager.Configure_Tooltip")); configure.setEnabled (false); JPanel header_pane = new DesignPaneHeader ("CDM.GUI.Classifiers", "classifiers"); ClassifierComboboxListener ccl = new ClassifierComboboxListener (); classifier_combobox = new JComboBox (getAvailableClassifiers ()); classifier_combobox.setComponentOrientation(Dictionary.getOrientation()); classifier_combobox.setOpaque (!Utility.isMac ()); classifier_combobox.setEditable (false); if(classifier_combobox.getItemCount () > 0) { classifier_combobox.setSelectedIndex (0); ccl.itemStateChanged (new ItemEvent (classifier_combobox, 0, null, ItemEvent.SELECTED)); } JLabel classifier_label = new JLabel (Dictionary.get ("CDM.ClassifierManager.Classifier")); classifier_label.setComponentOrientation(Dictionary.getOrientation()); classifier_list = new JList (model); classifier_list.setComponentOrientation(Dictionary.getOrientation()); classifier_list.setOpaque (true); classifier_list.setSelectionMode (ListSelectionModel.SINGLE_SELECTION); JLabel classifier_list_label = new JLabel (Dictionary.get ("CDM.ClassifierManager.Assigned")); classifier_list_label.setComponentOrientation(Dictionary.getOrientation()); classifier_list_label.setOpaque (true); JPanel classifier_list_pane = new JPanel (); classifier_list_pane.setComponentOrientation(Dictionary.getOrientation()); JPanel classifier_pane = new JPanel (); classifier_pane.setComponentOrientation(Dictionary.getOrientation()); remove = new GLIButton (Dictionary.get ("CDM.ClassifierManager.Remove"), Dictionary.get ("CDM.ClassifierManager.Remove_Tooltip")); remove.setEnabled (false); JPanel temp = new JPanel (new BorderLayout ()); temp.setComponentOrientation(Dictionary.getOrientation()); JPanel move_button_pane = new JPanel (); move_button_pane.setComponentOrientation(Dictionary.getOrientation()); move_up_button = new GLIButton (Dictionary.get ("CDM.Move.Move_Up"), JarTools.getImage ("arrow-up.gif"), Dictionary.get ("CDM.Move.Move_Up_Tooltip")); move_up_button.setEnabled (false); move_down_button = new GLIButton (Dictionary.get ("CDM.Move.Move_Down"), JarTools.getImage ("arrow-down.gif"), Dictionary.get ("CDM.Move.Move_Down_Tooltip")); move_down_button.setEnabled (false); // Listeners add.addActionListener (new AddListener ()); classifier_combobox.addItemListener (ccl); configure.addActionListener (new ConfigureListener ()); remove.addActionListener (new RemoveListener ()); remove.addActionListener (CollectionDesignManager.buildcol_change_listener); classifier_list.addMouseListener (new ClickListener ()); classifier_list.addListSelectionListener (new ListListener ()); ccl = null; MoveListener ml = new MoveListener (); move_down_button.addActionListener (ml); move_down_button.addActionListener (CollectionDesignManager.buildcol_change_listener); move_up_button.addActionListener (ml); move_up_button.addActionListener (CollectionDesignManager.buildcol_change_listener); // Layout JPanel tmp; move_button_pane.setLayout (new GridLayout (4,1)); move_button_pane.add (move_up_button); tmp = new JPanel (); tmp.setComponentOrientation(Dictionary.getOrientation()); move_button_pane.add (tmp); tmp = new JPanel (); tmp.setComponentOrientation(Dictionary.getOrientation()); move_button_pane.add (tmp); move_button_pane.add (move_down_button); classifier_list_label.setBorder (BorderFactory.createEmptyBorder (0,2,0,2)); classifier_list_pane.setLayout (new BorderLayout ()); classifier_list_pane.add (classifier_list_label, BorderLayout.NORTH); classifier_list_pane.add (new JScrollPane (classifier_list), BorderLayout.CENTER); classifier_list_pane.add (move_button_pane, BorderLayout.LINE_END); classifier_label.setBorder (BorderFactory.createEmptyBorder (0,0,5,0)); classifier_pane.setBorder (BorderFactory.createEmptyBorder (5,0,5,0)); classifier_pane.setLayout (new BorderLayout (5,0)); classifier_pane.add (classifier_label, BorderLayout.LINE_START); classifier_pane.add (classifier_combobox, BorderLayout.CENTER); button_pane.setLayout (new GridLayout (1, 3)); button_pane.add (add); button_pane.add (configure); button_pane.add (remove); temp.add (classifier_pane, BorderLayout.NORTH); temp.add (button_pane, BorderLayout.SOUTH); central_pane.setBorder (BorderFactory.createEmptyBorder (5,0,0,0)); central_pane.setLayout (new BorderLayout ()); central_pane.add (classifier_list_pane, BorderLayout.CENTER); central_pane.add (temp, BorderLayout.SOUTH); setBorder (BorderFactory.createEmptyBorder (0,5,0,0)); setLayout (new BorderLayout ()); add (header_pane, BorderLayout.NORTH); add (central_pane, BorderLayout.CENTER); } /** Method which acts like a destructor, tidying up references to persistant objects. */ public void destroy () { add = null; classifier_combobox = null; classifier_list = null; configure = null; //instructions = null; remove = null; } public void gainFocus () { } public void loseFocus () { } private class AddListener implements ActionListener { public void actionPerformed (ActionEvent event) { if (classifier_combobox.getSelectedItem () != null) { // This must be done on a new thread for the remote building code new AddClassifierTask (classifier_combobox.getSelectedItem ().toString ()).start (); } } } private class AddClassifierTask extends Thread { private String classifier_name; public AddClassifierTask (String classifier_name) { this.classifier_name = classifier_name; } public void run () { // Retrieve the classifier Classifier classifier = Classifiers.getClassifier (classifier_name, true); if (classifier == null) { System.err.println ("Error: getClassifier() returned null."); return; } // Create a new element in the DOM Element new_classifier_element = CollectionConfiguration.createElement (StaticStrings.CLASSIFY_ELEMENT); new_classifier_element.setAttribute (StaticStrings.TYPE_ATTRIBUTE, classifier.getName ()); Classifier new_classifier = new Classifier (new_classifier_element, classifier); ArgumentConfiguration ac = new ArgumentConfiguration (new_classifier); ac.addOKButtonActionListener (CollectionDesignManager.buildcol_change_listener); if (ac.display ()) { assignClassifier (new_classifier); classifier_list.setSelectedValue (new_classifier, true); } } } /** This listener reacts to changes in the current selection of the classifier combobox. */ private class ClassifierComboboxListener implements ItemListener { /** When a user selects a certain classifier, update the tooltip to show the classifier description. */ public void itemStateChanged (ItemEvent event) { if(event.getStateChange () == ItemEvent.SELECTED) { // Retrieve the selected classifier Classifier current_selection = (Classifier) classifier_combobox.getSelectedItem (); // And reset the tooltip. classifier_combobox.setToolTipText (Utility.formatHTMLWidth (current_selection.getDescription (), 40)); current_selection = null; } } } /** Listens for double clicks apon the list and react as if the configure button was pushed. */ private class ClickListener extends MouseAdapter { /** Called whenever the mouse is clicked over a registered component, we use this to chain through to the configure prompt. * @param event A MouseEvent containing information about the mouse click. */ public void mouseClicked (MouseEvent event) { if(event.getClickCount () == 2 ) { if(!classifier_list.isSelectionEmpty ()) { Classifier classifier = (Classifier) classifier_list.getSelectedValue (); ArgumentConfiguration ac = new ArgumentConfiguration (classifier); ac.addOKButtonActionListener (CollectionDesignManager.buildcol_change_listener); if (ac.display ()) { refresh (classifier); } ac.destroy (); ac = null; } } } } /** This class listens for actions upon the configure button in the controls, and if detected creates a new ArgumentConfiguration dialog box to allow for configuration. */ private class ConfigureListener implements ActionListener { /** Any implementation of ActionListener must include this method so that we can be informed when an action has occured on one of our target controls. * @param event An ActionEvent containing information garnered from the control action. * @see org.greenstone.gatherer.cdm.ArgumentConfiguration * @see org.greenstone.gatherer.cdm.Classifier */ public void actionPerformed (ActionEvent event) { if(!classifier_list.isSelectionEmpty ()) { Classifier classifier = (Classifier) classifier_list.getSelectedValue (); ArgumentConfiguration ac = new ArgumentConfiguration (classifier); ac.addOKButtonActionListener (CollectionDesignManager.buildcol_change_listener); if (ac.display ()) { refresh (classifier); } ac.destroy (); ac = null; } } } /** listens for changes in the list selection and enables the configure and remove buttons if there is a selection, disables them if there is no selection */ private class ListListener implements ListSelectionListener { public void valueChanged (ListSelectionEvent e) { if (!e.getValueIsAdjusting ()) { // we get two events for one change in list selection - use the false one ( the second one) if (classifier_list.isSelectionEmpty ()) { move_up_button.setEnabled (false); move_down_button.setEnabled (false); configure.setEnabled (false); remove.setEnabled (false); } else { configure.setEnabled (true); remove.setEnabled (true); int selected_index = classifier_list.getSelectedIndex (); move_up_button.setEnabled (selected_index !=0); move_down_button.setEnabled (selected_index != model.getSize ()-1); } } } } /** Listens for actions apon the move buttons in the manager controls, and if detected calls the moveClassifier() method of the manager with the appropriate details. */ private class MoveListener implements ActionListener { /** Any implementation of ActionListener must include this method so that we can be informed when an action has occured on one of our target controls. * @param event An ActionEvent containing information garnered from the control action. */ public void actionPerformed (ActionEvent event) { if(!classifier_list.isSelectionEmpty ()) { Object object = classifier_list.getSelectedValue (); if(object instanceof Classifier) { Classifier classifier = (Classifier) object; if(event.getSource () == move_up_button) { moveClassifier (classifier, true, false); } else if(event.getSource () == move_down_button) { moveClassifier (classifier, false, false); } classifier_list.setSelectedValue (classifier, true); } } } } /** This class listens for actions upon the remove button in the controls, and if detected calls the removeClassifier() method. */ private class RemoveListener implements ActionListener { /** Any implementation of ActionListener must include this method so that we can be informed when an action has occured on one of our target controls. * @param event An ActionEvent containing information garnered from the control action. */ public void actionPerformed (ActionEvent event) { if(classifier_list.isSelectionEmpty ()) { remove.setEnabled (false); return; } int selected_index = classifier_list.getSelectedIndex (); Object selected_classifier = classifier_list.getSelectedValue (); if (!(selected_classifier instanceof Classifier)) { return; // what else could we have here??? } removeClassifier ((Classifier)selected_classifier); if (selected_index >= classifier_list.getModel ().getSize ()) { selected_index--; } if (selected_index >=0) { classifier_list.setSelectedIndex (selected_index); } else { // no more classifiers in the list remove.setEnabled (false); } } } } }