/* FeaturePlotGroup.java
 *
 * created: Wed Dec 16 1998
 *
 * This file is part of Artemis
 * 
 * Copyright (C) 1998,1999,2000  Genome Research Limited
 * 
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Header: /nfs/disk222/yeastpub/Repository/powmap/diana/components/FeaturePlotGroup.java,v 1.18 2000/05/23 14:08:51 kmr Exp $
 */

package diana.components;

import diana.*;
import diana.sequence.*;
import diana.plot.*;

import java.awt.*;
import java.awt.event.*;
import java.util.Vector;

/**
 *  This is a super-component containing several FeaturePlot components, each
 *  of which can toggled off and on.  The component contains a row of toggle
 *  buttons and then the FeaturePlot components below.
 *
 *  @author Kim Rutherford
 *  @version $Id: FeaturePlotGroup.java,v 1.18 2000/05/23 14:08:51 kmr Exp $
 **/

public class FeaturePlotGroup extends Frame
    implements EntryChangeListener, FeatureChangeListener {
  /**
   *  Create a new FeaturePlotGroup component for the given feature.
   **/
  public FeaturePlotGroup (Feature feature) {
    super ("Graphs for: " + feature.getIDString ());
    this.feature = feature;
    this.entry = feature.getEntry ();

    // don't repeat an algorithm in this array.
    final FeatureAlgorithm [] plot_value_producers = {
      new HydrophobicityAlgorithm (getFeature ()),
      new HydrophilicityAlgorithm (getFeature ()),
      new CoilFeatureAlgorithm (getFeature ())
    };

    final Font font = Options.getOptions ().getFont ();

    setFont (font);

    GridBagLayout gridbag = new GridBagLayout();
    setLayout (gridbag);

    GridBagConstraints c = new GridBagConstraints();

    c.fill = GridBagConstraints.HORIZONTAL;
    c.anchor = GridBagConstraints.NORTH;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    c.weighty = 0;

    final FlowLayout flow_layout = new FlowLayout (FlowLayout.LEFT); 
    final Panel button_panel = new Panel (flow_layout);
    gridbag.setConstraints (button_panel, c);
    add (button_panel);

    final Label new_label = new Label ("Feature Algorithms: ");
    button_panel.add (new_label);

    c.fill = GridBagConstraints.BOTH;
    c.weighty = 1;
    c.insets = new Insets (0,0,5,0);
    
    for (int i = 0 ; i < plot_value_producers.length ; ++i) {
      final FeatureAlgorithm this_algorithm = plot_value_producers[i];

      final boolean show_algorithm = true;
      
      // create a button and a FeaturePlot component for each algorithm.  the
      // FeaturePlot is initially hidden.
      final Checkbox new_checkbox =
        new Checkbox (this_algorithm.getAlgorithmName (), show_algorithm);

      new_checkbox.addItemListener (new ItemListener () {
        public void itemStateChanged (ItemEvent event) {
          final String check_label =
            ((Checkbox) event.getSource ()).getLabel ();

          setVisibleByName (check_label,
                            event.getStateChange () == ItemEvent.SELECTED);
        }
      });

      button_panel.add (new_checkbox);

      final FeaturePlot new_feature_plot = new FeaturePlot (this_algorithm);

      gridbag.setConstraints (new_feature_plot, c);
      add (new_feature_plot);
      new_feature_plot.setVisible (show_algorithm);
    }

    getFeature ().getEntry ().addEntryChangeListener (this);
    getFeature ().addFeatureChangeListener (this);

    addWindowListener (new WindowAdapter () {
      public void windowClosing (WindowEvent event) {
        stopListening ();
        FeaturePlotGroup.this.dispose ();
      }
    });

    addComponentListener (new ComponentAdapter () {
      public void componentResized (ComponentEvent event) {
        fixScrollbar ();
        fireAdjustmentEvent (scrollbar.getValue ());
      }
    });

    int new_x_size = feature.getTranslation ().length () + 50;

    if (new_x_size > 1000) {
      new_x_size = 1000;
    }

    scrollbar = new Scrollbar (Scrollbar.HORIZONTAL);
    c.fill = GridBagConstraints.HORIZONTAL;
    c.weighty = 0;
    gridbag.setConstraints (scrollbar, c);
    scrollbar.addAdjustmentListener (new AdjustmentListener () {
      public void adjustmentValueChanged(AdjustmentEvent e) {
        fireAdjustmentEvent (e.getValue ());
      }
    });
    add (scrollbar);

    final Component [] children = getComponents ();

    for (int i = 0 ; i < children.length ; ++i) {
      if (children[i] instanceof FeaturePlot) {
        addDisplayAdjustmentListener ((FeaturePlot)children[i]);
      }
    }
    

    bottom_button_panel = new Panel ();
    c.fill = GridBagConstraints.HORIZONTAL;
    c.weighty = 0;
    gridbag.setConstraints (bottom_button_panel, c);
    add (bottom_button_panel);

    close_button = new Button ("Close");
    close_button.addActionListener (new ActionListener () {
      public void actionPerformed (ActionEvent e) {
        stopListening ();
        FeaturePlotGroup.this.dispose ();
      }
    });

    bottom_button_panel.add (close_button);
    
    pack ();

    final int new_y_size =
      200 * plot_value_producers.length + button_panel.getSize ().height;
    
    // give each FeaturePlot component a height of 200
    setSize (new_x_size, new_y_size);

    final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    setLocation (new Point ((screen.width - getSize ().width) / 2,
                            (screen.height - getSize ().height) / 2));

    show ();

    fixScrollbar ();
    
    fireAdjustmentEvent (1);
  }

  /**
   *  Remove this object as a entry change listener and then call
   *  stopListening () on each FeaturePlot component.
   **/
  private void stopListening () {
    getEntry ().removeEntryChangeListener (this);

    final Component [] children = getComponents ();

    for (int i = 0 ; i < children.length ; ++i) {
      if (children[i] instanceof FeaturePlot) {
        ((FeaturePlot)children[i]).stopListening ();
      }
    }

    getFeature ().getEntry ().removeEntryChangeListener (this);
    getFeature ().removeFeatureChangeListener (this);
  }
  
  /**
   *  Set the scrollbar maximum, minimum and value.
   **/
  private void fixScrollbar () {
    final int feature_length = getFeature ().getTranslation ().length ();

    int scroll_max = feature_length - getSize ().width;

    if (scroll_max < 1) {
      scroll_max = 1;
    }

//      System.out.println ("feature_length: " + feature_length);
    
//      System.out.println ("setValues: " + 1 + " "  + getSize ().width + " " +
//                          1 + " " + scroll_max);

    scrollbar.setValues (1, getSize ().width,
                         1, scroll_max + getSize ().width);

    scrollbar.setBlockIncrement (getSize ().width);
  }

  /**
   *  Adds the specified event adjustemnt listener to receive adjustment
   *  change events from this object.
   *  @param l the event change listener.
   **/
  public void addDisplayAdjustmentListener (DisplayAdjustmentListener l) {
    adjustment_listener_list.addElement (l);
  }

  /**
   *  Removes the specified event listener so that it no longer receives
   *  adjustment change events from this object.
   *  @param l the event change listener.
   **/
  public void removeDisplayAdjustmentListener (DisplayAdjustmentListener l) {
    adjustment_listener_list.removeElement (l);
  }

  /**
   *  Implementation of the DisplayAdjustmentListener interface.  Invoked when
   *  a component (FeatureDisplay) scrolls or changes the scale.  Sends the
   *  event to all the BasePlot components in this BasePlotGroup.
   **/
  public void displayAdjustmentValueChanged (DisplayAdjustmentEvent event) {
    final Component [] children = getComponents ();

    for (int i = 0 ; i < children.length ; ++i) {
      if (children[i] instanceof BasePlot) {
        ((BasePlot)children[i]).displayAdjustmentValueChanged (event);
      }
    }
  }

  /**
   *  Implementation of the FeatureChangeListener interface.
   *  @param event The change event.
   **/
  public void featureChanged (FeatureChangeEvent event) {
//     System.out.println ("FeaturePlot: feature change event type: " +
//                         event.getType ());
    
    fixScrollbar ();
    fireAdjustmentEvent (scrollbar.getValue ());
  }

  /**
   *  Implementation of the EntryChangeListener interface.  We listen to
   *  EntryChange events so we can delete this component if the feature gets
   *  deleted.
   **/
  public void entryChanged (EntryChangeEvent event) {
//     System.out.println ("in FeaturePlotGroup: entry change event type: " +
//                         event.getType () + " for entry " +
//                         event.getSource () + " for feature " +
//                         event.getFeature ());
    switch (event.getType ()) {
    case EntryChangeEvent.FEATURE_DELETED:
      if (event.getFeature () == getFeature ()) {
        stopListening ();
        dispose ();
      }
      break;
    default:
      // do nothing;
      break;
    }
  }

  /**
   *  Send a DisplayAdjustmentEvent to the objects that are listening for it.
   **/
  private void fireAdjustmentEvent (final int scroll_value) {
//      System.out.println ("fireAdjustmentEvent (): " + scroll_value +
//                          "  max: " + scrollbar.getMaximum ());

    final int feature_length = getFeature ().getTranslation ().length ();

    int end_value = scroll_value + getSize ().width;

    if (end_value > feature_length) {
      end_value = feature_length;
    }

    final DisplayAdjustmentEvent event =
      new DisplayAdjustmentEvent (this,
                                  scroll_value,
                                  end_value,
                                  getSize ().width,
                                  1,
                                  0); // the last arg will be ignored

    final Vector targets;

    // copied from a book - synchronising the whole method might cause a
    // deadlock
    synchronized (this) {
      targets = (Vector) adjustment_listener_list.clone ();
    }

    for ( int i = 0 ; i < targets.size () ; ++i ) {
      DisplayAdjustmentListener target =
        (DisplayAdjustmentListener) targets.elementAt (i);

      target.displayAdjustmentValueChanged (event);
    }
  }

  /**
   *  Find the Checkbox component in this FeaturePlotGroup object that has the
   *  given label.
   **/
  private Component findCheckbox (String find_label) {
    final Component [] children = getComponents ();

    for (int i = 0 ; i < children.length ; ++i) {
      if (children[i] instanceof FeaturePlot) {
        final String component_algorithm_name =
          ((FeaturePlot)children[i]).getAlgorithm ().getAlgorithmName ();
        if (component_algorithm_name.equals (find_label)) {
          return children[i];
        }
      }
    }

    return null;
  }

  /**
   *  Given an algorithm name, find and set the visibility of the
   *  corresponding FeaturePlot component.
   **/
  private void setVisibleByName (String label, boolean visible) {
    final Component plot = FeaturePlotGroup.this.findCheckbox (label);

    plot.setSize (plot.getSize ().width, 100);
    plot.setVisible (visible);
    pack ();
  }

  /**
   *  Return the feature that this component is plotting.
   **/
  private Feature getFeature () {
    return feature;
  }

  /**
   *  Return the Entry that contains the Feature this object is displaying.
   **/
  private Entry getEntry () {
    return entry;
  }

  /**
   *  The feature we are plotting.
   **/
  private Feature feature;

  /**
   *  The Entry that contains the Feature this object is displaying.
   **/
  private Entry entry;

  /**
   *  Pressing this button will distroy the Frame.
   **/
  private Button close_button;
  
  /**
   *  A Panel to hold the buttons.
   **/
  private Panel bottom_button_panel;

  private Scrollbar scrollbar;

  /**
   *  A vector of those objects listening for adjustment events.
   **/
  final private Vector adjustment_listener_list = new Vector ();
}
