/* 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: /cvsroot/pathsoft/artemis/uk/ac/sanger/artemis/components/FeaturePlotGroup.java,v 1.1 2004/06/09 09:46:43 tjc Exp $
 */

package uk.ac.sanger.artemis.components;

import uk.ac.sanger.artemis.*;
import uk.ac.sanger.artemis.sequence.*;
import uk.ac.sanger.artemis.plot.*;

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

import javax.swing.*;

/**
 *  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.1 2004/06/09 09:46:43 tjc Exp $
 **/

public class FeaturePlotGroup extends JFrame
    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();
    getContentPane ().setLayout (gridbag);

    GridBagConstraints c = new GridBagConstraints();

    c.anchor = GridBagConstraints.NORTH;
    c.gridwidth = GridBagConstraints.REMAINDER;
    c.gridheight = 1;
    c.weightx = 1;
    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 FeaturePlot new_feature_plot = new FeaturePlot (this_algorithm);

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

    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 () + SCROLL_BAR_SIZE;

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

    if (new_x_size < 300) {
      new_x_size = 300;
    }

    scrollbar = new JScrollBar (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 ());
      }
    });
    getContentPane ().add (scrollbar);

    final Component [] children = getContentPane ().getComponents ();

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


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

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

    bottom_button_panel.add (close_button);

    pack ();

    // give each FeaturePlot component a height of 200
    setSize (new_x_size, getSize ().height);

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

    show ();

    fixScrollbar ();

    fireAdjustmentEvent (1);
  }

  /**
   *  Fudge factor to take the scroll bar width into account when creating
   *  windows and scrollbars.
   **/
  private final int SCROLL_BAR_SIZE = 100;

  /**
   *  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;

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

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

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

  /**
   *  Adds the specified event adjustemnt listener to receive adjustment
   *  change events from this object.
   *  @param l the event change listener.
   **/
  private  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.
   **/
  private  void removeDisplayAdjustmentListener (DisplayAdjustmentListener l) {
    adjustment_listener_list.removeElement (l);
  }

  /**
   *  Implementation of the FeatureChangeListener interface.
   *  @param event The change event.
   **/
  public void featureChanged (FeatureChangeEvent event) {
    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) {
    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) {
    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,// this arg will be ignored
                                  false,
                                  DisplayAdjustmentEvent.SCALE_ADJUST_EVENT);

    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);
    }
  }

  /**
   *  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 JFrame.
   **/
  private JButton close_button;

  /**
   *  A JPanel to hold the buttons.
   **/
  private JPanel bottom_button_panel;

  private JScrollBar scrollbar;

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