/**
*#########################################################################
*
* 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);
}
}
}
}
}