/*****************************************************************************
*
* Copyright (c) 2000 - 2007, The Regents of the University of California
* Produced at the Lawrence Livermore National Laboratory
* All rights reserved.
*
* This file is part of VisIt. For details, see http://www.llnl.gov/visit/. The
* full copyright notice is contained in the file COPYRIGHT located at the root
* of the VisIt distribution or at http://www.llnl.gov/visit/copyright.html.
*
* Redistribution  and  use  in  source  and  binary  forms,  with  or  without
* modification, are permitted provided that the following conditions are met:
*
*  - Redistributions of  source code must  retain the above  copyright notice,
*    this list of conditions and the disclaimer below.
*  - Redistributions in binary form must reproduce the above copyright notice,
*    this  list of  conditions  and  the  disclaimer (as noted below)  in  the
*    documentation and/or materials provided with the distribution.
*  - Neither the name of the UC/LLNL nor  the names of its contributors may be
*    used to  endorse or  promote products derived from  this software without
*    specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR  IMPLIED WARRANTIES, INCLUDING,  BUT NOT  LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS FOR A PARTICULAR  PURPOSE
* ARE  DISCLAIMED.  IN  NO  EVENT  SHALL  THE  REGENTS  OF  THE  UNIVERSITY OF
* CALIFORNIA, THE U.S.  DEPARTMENT  OF  ENERGY OR CONTRIBUTORS BE  LIABLE  FOR
* ANY  DIRECT,  INDIRECT,  INCIDENTAL,  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT  LIMITED TO, PROCUREMENT OF  SUBSTITUTE GOODS OR
* SERVICES; LOSS OF  USE, DATA, OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER
* CAUSED  AND  ON  ANY  THEORY  OF  LIABILITY,  WHETHER  IN  CONTRACT,  STRICT
* LIABILITY, OR TORT  (INCLUDING NEGLIGENCE OR OTHERWISE)  ARISING IN ANY  WAY
* OUT OF THE  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*****************************************************************************/

#include <cstdlib> // for qsort
#include <qpainter.h>
#include <qpixmap.h>
#include <qpainter.h>
#include <qstyle.h>
#include <qtimer.h>
#include <QEvent>
#include <QKeyEvent>
#include <QPalette>
#include <QPolygon>
#include <QMatrix>
#include <QStyleOption>
#include "QvisSpectrumBar.h"

// Some constants for paging modes.
#define NO_PAGING      -1
#define INCREMENT       0
#define DECREMENT       1
#define PAGE_INCREMENT  2
#define PAGE_DECREMENT  3
#define PAGE_HOME       4
#define PAGE_END        5

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/// Declarations of internally used classes and structs.
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

typedef struct
{
    int   rank;
    float position;
    float color[3];
} ControlPoint;

static int ControlPointCompare(const void *c1, const void *c2);

// ****************************************************************************
// Class: ControlPointList
//
// Purpose:
//   This is a "private" class that contains the control point list used in
//   the QvisSpectrumBar class.
//
// Notes:
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:00:33 PDT 2001
//
// Modifications:
//
// ****************************************************************************

class ControlPointList
{
public:
    ControlPointList();
    ~ControlPointList();
    void  Add(const ControlPoint *cpt);
    bool  CanBeEdited() const;
    int   ChangeSelectedIndex(float _pos, float width, int equal);
    void  Clear();
    float ColorValue(int index) const;
    void  DeleteHighestRank();
    void  GiveHighestRank(int index);
    int   NumColorValues() const;
    int   NumControlPoints() const;
    float Position(int index) const;
    int   Rank(int rank) const;
    void  SetColor(int index, float r, float g, float b);
    void  SetColorValues(const float *cv, int n);
    void  SetEditMode(bool val);
    void  SetPosition(int index, float _pos);
    void  Sort();

    const ControlPoint &operator [](int index) const;
private:
    static const int CPLIST_INCREMENT;
    static const ControlPoint defaultControlPoint1;
    static const ControlPoint defaultControlPoint2;

    bool         editable;
    int          nels;
    int          total_nels;
    int          nvals;
    ControlPoint *list;
    float        *colorvals;
};

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/// QvisSpectrumBar implementation
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

// ****************************************************************************
// Method: QvisSpectrumBar::QvisSpectrumBar
//
// Purpose:
//   Constructor for the QvisSpectrumBar class.
//
// Arguments:
//   parentObject : The widget's parentObject.
//   name   : The name of the widget instance.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:01:21 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 28 10:15:47 PDT 2001
//   Initialized a new bool that allows updates to be suppressed.
//
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap.
//
// ****************************************************************************

QvisSpectrumBar::QvisSpectrumBar(QWidget *parentObject, const char* /*name*/) :
    QWidget(parentObject)
{
    pixmap = 0;

    orientation = HorizontalTop;
    b_smoothing = true;
    b_equalSpacing = false;
    b_sliding = false;
    b_continuousUpdate = false;
    b_suppressUpdates = false;
    margin = 4;
    paging_mode = NO_PAGING;
    shiftApplied = false;

    controlPoints = new ControlPointList;

    // Create a timer used for paging/incrementing/decrementing.
    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(handlePaging()));

    // Set the focus policy to StrongFocus. This means that the widget will
    // accept focus by tabbing and clicking.
    setFocusPolicy(Qt::StrongFocus);

    // Set the widget's minimum width and height.
    setMinimumWidth(50);
    setMinimumHeight(60);
}

// ****************************************************************************
// Method: QvisSpectrumBar::~QvisSpectrumBar
//
// Purpose:
//   The destructor for the QvisSpectrumBar class.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:07:23 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap.
//
// ****************************************************************************

QvisSpectrumBar::~QvisSpectrumBar()
{
    deletePixmap();

    delete controlPoints;
}

// ****************************************************************************
// Method: QvisSpectrumBar::sizeHint
//
// Purpose:
//   Returns the widget's preferred size.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:07:43 PDT 2001
//
// Modifications:
//
// ****************************************************************************

QSize
QvisSpectrumBar::sizeHint() const
{
    QSize retval(250, 100);

    if(orientation == VerticalLeft || orientation == VerticalRight)
        retval = QSize(100, 250);

    return retval;
}

// ****************************************************************************
// Method: QvisSpectrumBar::sizePolicy
//
// Purpose:
//   Returns the widget's size policy. This is how the widget prefers to grow.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:25:27 PDT 2001
//
// Modifications:
//
// ****************************************************************************

QSizePolicy
QvisSpectrumBar::sizePolicy() const
{
    QSizePolicy retval(QSizePolicy::Preferred, QSizePolicy::Fixed);

    if(orientation == VerticalLeft || orientation == VerticalRight)
        retval = QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);

    return retval;
}

// ****************************************************************************
// Method: QvisSpectrumBar::equalSpacing
//
// Purpose:
//   Returns a flag indicating whether or not the widget's control points are
//   in equal spacing mode.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:26:04 PDT 2001
//
// Modifications:
//
// ****************************************************************************

bool
QvisSpectrumBar::equalSpacing() const
{
    return b_equalSpacing;
}

// ****************************************************************************
// Method: QvisSpectrumBar::smoothing
//
// Purpose:
//   Returns a flag indicating whether or not the colors are interpolated
//   between control points.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:26:53 PDT 2001
//
// Modifications:
//
// ****************************************************************************

bool
QvisSpectrumBar::smoothing() const
{
    return b_smoothing;
}

// ****************************************************************************
// Method: QvisSpectrumBar::continuousUpdate
//
// Purpose:
//   Returns a flag indicating whether or not update signals are emitted during
//   mouse dragging of control points.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:27:47 PDT 2001
//
// Modifications:
//
// ****************************************************************************

bool
QvisSpectrumBar::continuousUpdate() const
{
    return b_continuousUpdate;
}

// ****************************************************************************
// Method: QvisSpectrumBar::controlPointColor
//
// Purpose:
//   Returns the color of a control point.
//
// Arguments:
//   index : The control point index starting at 0.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:28:35 PDT 2001
//
// Modifications:
//
// ****************************************************************************

QColor
QvisSpectrumBar::controlPointColor(int index) const
{
    if(index >= 0 && index < controlPoints->NumControlPoints())
        return QColor((int)(controlPoints->operator[](index).color[0] * 255.),
                     (int)(controlPoints->operator[](index).color[1] * 255.),
                     (int)(controlPoints->operator[](index).color[2] * 255.));
    else
        return QColor(0,0,0);
}

// ****************************************************************************
// Method: QvisSpectrumBar::controlPointPosition
//
// Purpose:
//   Returns the position of a control point. This is a value between 0. and 1.
//
// Arguments:
//   index : The zero-based index of the control point.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:29:19 PDT 2001
//
// Modifications:
//
// ****************************************************************************

float
QvisSpectrumBar::controlPointPosition(int index) const
{
    float retval = 0.;

    if(index >= 0 && index < controlPoints->NumControlPoints())
        retval = controlPoints->Position(index);

    return retval;
}

// ****************************************************************************
// Method: QvisSpectrumBar::numControlPoints
//
// Purpose:
//   Returns the number of control points.
//
// Returns:    The number of control points.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:36:33 PDT 2001
//
// Modifications:
//
// ****************************************************************************

int
QvisSpectrumBar::numControlPoints() const
{
    return controlPoints->CanBeEdited() ? controlPoints->NumControlPoints():0;
}

// ****************************************************************************
// Method: QvisSpectrumBar::activeControlPoint
//
// Purpose:
//   Returns the index of the active control point.
//
// Returns:    The index of the active control point.
//
// Programmer: Brad Whitlock
// Creation:   Sat Feb 3 21:53:27 PST 2001
//
// Modifications:
//
// ****************************************************************************

int
QvisSpectrumBar::activeControlPoint() const
{
    return controlPoints->Rank(controlPoints->NumControlPoints() - 1);
}

// ****************************************************************************
// Method: QvisSpectrumBar::setEqualSpacing
//
// Purpose:
//   Sets the widget's equal spacing mode.
//
// Arguments:
//   val : The new equal spacing mode.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:52:58 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setEqualSpacing(bool val)
{
    if(val != b_equalSpacing)
    {
        b_equalSpacing = val;
        updateEntireWidget();
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::setSmoothing
//
// Purpose:
//   Sets the widget's color smoothing mode.
//
// Arguments:
//   val : The new smoothing mode.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:53:37 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 28 10:14:52 PDT 2001
//   Added code to allow updates to be suppressed.
//
// ****************************************************************************

void
QvisSpectrumBar::setSmoothing(bool val)
{
    if(val != b_smoothing)
    {
        b_smoothing = val;

        if(isVisible() && !b_suppressUpdates)
        {
            drawSpectrum();
            update(spectrumArea.x(), spectrumArea.y(), spectrumArea.width(),
                   spectrumArea.height());
        }
        else
            deletePixmap();
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::setContinuousUpdate
//
// Purpose:
//   Sets the flag that is used to determine whether or not to emit signals
//   while moving a control point by dragging the mouse.
//
// Arguments:
//   val : If set to true, it updates as the mouse is dragged.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:54:17 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setContinuousUpdate(bool val)
{
    b_continuousUpdate = val;
}

// ****************************************************************************
// Method: QvisSpectrumBar::suppressUpdates
//
// Purpose:
//   Returns the widget's suppressUpdates flag.
//
// Returns:    The widget's suppressUpdates flag.
//
// Programmer: Brad Whitlock
// Creation:   Wed Mar 28 10:20:39 PDT 2001
//
// Modifications:
//
// ****************************************************************************

bool
QvisSpectrumBar::suppressUpdates() const
{
    return b_suppressUpdates;
}

// ****************************************************************************
// Method: QvisSpectrumBar::setSuppressUpdates
//
// Purpose:
//   Sets the flag that is used to determine whether or not updates should be
//   suppressed.
//
// Arguments:
//   val : If set to true, updates are suppressed.
//
// Programmer: Brad Whitlock
// Creation:   Wed Mar 28 10:18:15 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setSuppressUpdates(bool val)
{
    b_suppressUpdates = val;
}

// ****************************************************************************
// Method: QvisSpectrumBar::addControlPoint
//
// Purpose:
//   Adds a new control point to the widget.
//
// Arguments:
//   color    : The color of the new control point.
//   position : The position of the new control point.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:55:23 PDT 2001
//
// Notes:      Emits controlPointAdded, activeControlPointChanged signals.
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::addControlPoint(const QColor &color, float position)
{
    int          index;
    ControlPoint temp;

    // Init some values.
    controlPoints->SetEditMode(true);

    // Sort the points in case they are out of order.
    controlPoints->Sort();
    index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

    // Store the colors in the control point.
    temp.color[0] = (float)color.red() / 255;
    temp.color[1] = (float)color.green() / 255;
    temp.color[2] = (float)color.blue() / 255;

    // Figure out a position for the new point.
    if(position < 0. || position > 1.)
    {
        // A position of -1 means that we should let the widget figure out
        // where to place the new control point.
        if(position == -1.)
        {
            // Figure the position that should be used.
            if(index == controlPoints->NumControlPoints() - 1)
            {
                // Compute the distance to the next point.
                float dx = controlPoints->Position(index) - controlPoints->Position(index - 1);

                // If the distance is small enough, realign the points and recalculate the
                // distance to the next point.
                if(dx <= 0.)
                {
                    alignControlPoints();
                    dx = controlPoints->Position(index) - controlPoints->Position(index - 1);
                }

                // Add new point to the left.
                temp.position = controlPoints->Position(index - 1) + dx * 0.5f;
            }
            else
            {
                // Compute the distance to the next point.
                float dx = controlPoints->Position(index + 1) - controlPoints->Position(index);

                // If the distance is small enough, realign the points and recalculate the
                // distance to the next point.
                if(dx <= 0.)
                {
                    alignControlPoints();
                    dx = controlPoints->Position(index) - controlPoints->Position(index - 1);
                }

                // Add new point to the right.
                temp.position = controlPoints->Position(index) + dx * 0.5f;
            }
        }
        else
            temp.position = 0.;
    }
    else
        temp.position = position;

    // Add the point.
    controlPoints->Add(&temp);

    // Redraw the widget.
    updateEntireWidget();

    // Get the index of the new point and emit signals.
    index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
    emit controlPointAdded(index, color, temp.position);
    emit activeControlPointChanged(index);
}

// ****************************************************************************
// Method: QvisSpectrumBar::alignControlPoints
//
// Purpose:
//   Sets the position of all of the control points so they have equal spacing.
//
// Programmer: Brad Whitlock
// Creation:   Mon Feb 5 14:12:47 PST 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::alignControlPoints()
{
    // Sort the points in case they are out of order.
    controlPoints->Sort();

    // Set the position in all of the control points.
    int i;
    float _pos = 0.;
    float delta = 1. /(float)(controlPoints->NumControlPoints() - 1);
    float *oldPos = new float[controlPoints->NumControlPoints()];
    for(i = 0; i < controlPoints->NumControlPoints(); ++i)
    {
        // Save the old position
        oldPos[i] = controlPoints->Position(i);

        // Set the new position
        controlPoints->SetPosition(i, _pos);
        _pos += delta;
    }

    // Redraw the entire widget
    updateEntireWidget();

    // Emit a signal for each of the control points that changed.
    for(i = 0; i < controlPoints->NumControlPoints(); ++i)
    {
        if(oldPos[i] != controlPoints->Position(i))
            emit controlPointMoved(i, controlPoints->Position(i));
    }

    // Delete the old position array.
    delete [] oldPos;
}

// ****************************************************************************
// Method: QvisSpectrumBar::removeControlPoint
//
// Purpose:
//   Removes the control point with the highest rank.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:56:16 PDT 2001
//
// Notes:      Emits controlPointRemoved, activeControlPointChanged signals.
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::removeControlPoint()
{
    // Only do the operation if there are more than 2 control points.
    if(controlPoints->NumControlPoints() > 2)
    {
        controlPoints->SetEditMode(true);

        // Delete the highest ranking control point from the control point list.
        int index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
        ControlPoint removedPoint = controlPoints->operator[](index);
        controlPoints->DeleteHighestRank();

        // Redraw the widget.
        updateEntireWidget();

        // Emit information about the control point that was removed.
        QColor temp((int)(removedPoint.color[0] * 255.),
                    (int)(removedPoint.color[1] * 255.),
                    (int)(removedPoint.color[2] * 255.));
        emit controlPointRemoved(index, temp, removedPoint.position);

        // Emit the index of the new active control point.
        index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
        emit activeControlPointChanged(index);
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::setRawColors
//
// Purpose:
//   Sets a palette of colors into the widget. This puts the widget into a
//   mode in which the control points disappear so the color palette can
//   be displayed.
//
// Arguments:
//   colors  : An array of colors stored rgbrgbrgb...
//   ncolors : The number of rgb triples.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:57:15 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setRawColors(unsigned char *colors, int ncolors)
{
    float *fcolors = new float[ncolors * 3];

    // Turn the unsigned chars into floats.
    for(int i = 0; i < ncolors * 3; ++i)
        fcolors[i] = (float)colors[i] / 255;

    // Note that the fcolors array is owned by the controlPoints object after
    // the call to SetColorValues.
    controlPoints->SetColorValues(fcolors, ncolors);
    controlPoints->SetEditMode(false);

    // Redraw the widget.
    updateEntireWidget();
}

// ****************************************************************************
// Method: QvisSpectrumBar::setEditMode
//
// Purpose:
//   Sets the spectrumbar's edit mode. When edit mode is set to false, the
//   control points are not drawn and the widget's spectrum cannot be modified.
//
// Arguments:
//   val : The new edit mode.
//
// Programmer: Brad Whitlock
// Creation:   Tue Mar 27 15:01:02 PST 2001
//
// Modifications:
//   Brad Whitlock, Mon Jul 14 14:44:11 PST 2003
//   I added some value checking.
//
// ****************************************************************************

void
QvisSpectrumBar::setEditMode(bool val)
{
    if(!val)
    {
        float *fcolors = new float[256 * 3];
        unsigned char *raw = getRawColors(256);

        if(raw)
        {
            // Turn the unsigned chars into floats.
            for(int i = 0; i < 256 * 3; ++i)
                fcolors[i] = (float)raw[i] / 255;

            // Note that the fcolors array is owned by the controlPoints object after
            // the call to SetColorValues.
            controlPoints->SetColorValues(fcolors, 256);
            delete [] raw;
        }
    }

    controlPoints->SetEditMode(val);
    updateEntireWidget();
}

// ****************************************************************************
// Method: QvisSpectrumBar::updateEntireWidget
//
// Purpose:
//   Redraws the entire widget or deletes the internal pixmaps so the entire
//   widget is drawn the next time it is shown.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:59:05 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 28 10:13:51 PDT 2001
//   Added code to allow updates to be suppressed.
//
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap.
//
// ****************************************************************************

void
QvisSpectrumBar::updateEntireWidget()
{
    if(isVisible() && !b_suppressUpdates)
    {
        drawControls();
        drawSpectrum();
        update();
    }
    else
        deletePixmap();
}

// ****************************************************************************
// Method: QvisSpectrumBar::deletePixmap
//
// Purpose:
//   Deletes the internal pixmap.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 10:59:49 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::deletePixmap()
{
    delete pixmap;
    pixmap = 0;
}

// ****************************************************************************
// Method: QvisSpectrumBar::setControlPointColor
//
// Purpose:
//   Sets the color of a control point.
//
// Arguments:
//   index : The index of the control point to change.
//   color : The new color of the control point.
//
// Note:       Emits controlPointColorChanged signal.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:00:36 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setControlPointColor(int index, const QColor &color)
{
    if(index >= 0 && index < controlPoints->NumControlPoints())
    {
         float r, g, b;
         r = (float)color.red() / 255.;
         g = (float)color.green() / 255.;
         b = (float)color.blue() / 255.;

         // Set the color and update the widget.
         controlPoints->SetEditMode(true);
         controlPoints->SetColor(index, r, g, b);
         updateEntireWidget();

         // Emit a signal indicating that a control point changed colors.
         emit controlPointColorChanged(index, color);
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::setControlPointPosition
//
// Purpose:
//   Sets the position of a control point.
//
// Arguments:
//   index    : The index of the control point to change.
//   position : The control point's new position.
//
// Note:       Emits controlPointMoved signal.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:08:12 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setControlPointPosition(int index, float position)
{
    if(index >= 0 && index < controlPoints->NumControlPoints())
    {
        if(position < 0.) position = 0.;
        if(position > 1.) position = 1.;

        // Set the position and update the widget.
        controlPoints->SetEditMode(true);
        moveControlPointRedraw(index, position, true);

        // Emit a signal indicating that a control point moved.
        emit controlPointMoved(index, position);
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::setOrientation
//
// Purpose:
//   Sets the widget's orientation.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:09:31 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::setOrientation(QvisSpectrumBar::ControlOrientation)
{
    // Not supported yet.
}

// ****************************************************************************
// Method: QvisSpectrumBar::drawControls
//
// Purpose:
//   Draws all of the control points in descending rank.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:11:19 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 09:12:31 PDT 2002
//   Removed style coding. Made it use a single pixmap. Removed code to sort
//   the control points to fix a bug that made paging expose incorrectly.
//
//   Brad Whitlock, Thu Aug 21 15:41:42 PST 2003
//   I changed how the brush is created so the widget looks better on MacOS X.
//
// ****************************************************************************

void
QvisSpectrumBar::drawControls()
{
    // If the pixmap is not allocated, allocate it.
    bool totalFill = false;
    if(pixmap == 0)
    {
        pixmap = new QPixmap(width(), height());
        totalFill = true;
    }

#ifdef Q_WS_MACX
    QBrush brush(palette().brush(QPalette::Background));
#else
    QBrush brush(palette().brush(QPalette::Button));
#endif

    // Create a painter and fill in the entire area with the background color.
    QPainter paint(pixmap);
    if(totalFill)
        paint.fillRect(0, 0, width(), height(), brush);
    else
        paint.fillRect(controlsArea.x(), controlsArea.y(),
                       controlsArea.width(), controlsArea.height(),
                       brush);

    // If we're not in editable mode, then we don't need to draw any
    // control points.
    if(!controlPoints->CanBeEdited())
        return;

    // Draw all the control points in the list in the order of lowest to
    // highest rank. This ensures that control points can properly overlap.
    int sel_index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
    for(int i = 0; i < controlPoints->NumControlPoints(); ++i)
    {
        // Get the control point with rank i.
        int index = controlPoints->Rank(i);

        // Get the bounding rectangle for the index'th control point.
        QPoint cpLocation(controlPointLocation(index));

        // Create a QColor to use to draw the color in the control point.
        QColor _controlPointColor((int)(controlPoints->operator[](index).color[0] * 255.),
                                 (int)(controlPoints->operator[](index).color[1] * 255.),
                                 (int)(controlPoints->operator[](index).color[2] * 255.));

        // Hard code the select color to yellow for now.
        QColor _selectColor(255, 255, 0);

        // Draw the control point.
        drawControlPoint(&paint,
                         palette().light(),
                         palette().dark(),
#ifdef Q_WS_MACX
                         palette().brush(QPalette::Background),
#else
                         palette().button(),
#endif
                         _selectColor,
                         _controlPointColor,
                         cpLocation.x(), cpLocation.y(),
                         slider.width(), slider.height(),
                         2,
                         orientation, index == sel_index);
    }

    // Make pixmap the background pixmap.
//    setBackgroundPixmap(*pixmap);
     QPalette _palette;
     _palette.setBrush(this->backgroundRole(), QBrush(*pixmap));
     this->setPalette(_palette);

}

// ****************************************************************************
// Method: QvisSpectrumBar::controlPointLocation
//
// Purpose:
//   Returns the position of the upper left corner of a control point.
//
// Arguments:
//   index : The index of the control point.
//
// Returns:    The position of the upper left corner of a control point.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:12:35 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap. Fixed margin.
//
// ****************************************************************************

QPoint
QvisSpectrumBar::controlPointLocation(int index) const
{
    int   offset, _x, _y;
    float _pos;

    // Figure out the position where the control point should be drawn.
    // This ignores position information in equal spacing mode.
    if(equalSpacing())
    {
        // Figure out the offset
        _pos = (float)index /(float)(controlPoints->NumControlPoints() - 1);
        if(orientation == HorizontalTop || orientation == HorizontalBottom)
            offset = spectrumArea.width() / controlPoints->NumControlPoints();
        else
            offset = spectrumArea.height() / controlPoints->NumControlPoints();
    }
    else
    {
        _pos = controlPoints->operator[](index).position;
        offset = 0;
    }

    // Figure the x,_y location of where to draw the control point.
    if(orientation == HorizontalTop || orientation == HorizontalBottom)
    {
        _x = (int)(_pos *(spectrumArea.width() - offset)) + offset/2 + margin;
        _y = controlsArea.y();
    }
    else
    {
        _x = controlsArea.x();
        _y = (int)(_pos *(spectrumArea.height() - offset)) + offset/2;
    }

    return QPoint(_x, _y);
}

// ****************************************************************************
// Method: QvisSpectrumBar::drawControlPoint
//
// Purpose:
//   Draws a control point.
//
// Arguments:
//   paint  : The painter to use to draw the control point.
//   top    : The top highlight color used for the border.
//   bottom : The bottom shadow color used for the border.
//   fore   : The button color used for the control point.
//   sel    : The selection color.
//   cpt    : The control point color.
//   _x      : The leftmost x value.
//   _y      : The topmost y value.
//   w      : The width of the control point.
//   h      : The height of the control point.
//   shadhow_thick : The shadow thickness.
//   orient : The control point orientation.
//   selected : Whether or not the control point is selected.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:15:16 PDT 2001
//
// Modifications:
//   Brad Whitlock, Tue Mar 12 13:38:59 PST 2002
//   Removed all dependencies on style objects.
//
// ****************************************************************************

void
QvisSpectrumBar::drawControlPoint(QPainter *paint, const QBrush &top,
    const QBrush &bottom, const QBrush &fore, const QColor &sel,
    const QColor &cpt, int _x, int _y, int w, int h, int shadow_thick,
    ControlOrientation orient, bool selected)
{
    int wd2 = w >> 1;
    int s2 = (shadow_thick << 1) + 1;
    int h1 = (int)((float)wd2 * 1.7320);
    int h2 = (int)((float)shadow_thick * 1.7320) + 1;
    int h3 = (int)((float)(w -(shadow_thick << 1)) * 1.7320 * 0.5) + 1;
    int w2 = (int)((float)(7 * h3 / 30) * 1.7320);

    int X[17], Y[17];
    QPolygon poly(5);

#define COPY_POINT(DI,SI) poly.setPoint((DI), X[SI], Y[SI])

    // Fill the vertices.
    X[0] = _x + wd2;              Y[0] = _y + h;
    X[1] = _x + w;                Y[1] = _y + h - h1;
    X[2] = _x + w;                Y[2] = _y;
    X[3] = _x;                    Y[3] = _y;
    X[4] = _x;                    Y[4] = _y + h - h1;
    X[5] = _x + wd2;              Y[5] = _y + h - h2;
    X[6] = _x + w - shadow_thick; Y[6] = _y + h - h2 - h3;
    X[7] = _x + w - shadow_thick; Y[7] = _y + shadow_thick;
    X[8] = _x + shadow_thick;     Y[8] = _y + shadow_thick;
    X[9] = _x + shadow_thick;     Y[9] = _y + h - h2 - h3;
    X[10] = _x + w - s2;          Y[10] = _y + h - h2 - h3;
    X[11] = _x + w - s2;          Y[11] = _y + s2;
    X[12] = _x + s2;              Y[12] = _y + s2;
    X[13] = _x + s2;              Y[13] = _y + h - h2 - h3;
    X[14] = _x + wd2;             Y[14] = _y + h - h2 - int(h3 * 0.15);
    X[15] = _x + wd2 + w2;        Y[15] = _y + h - h2 - int(0.85 * h3);
    X[16] = _x + wd2 - w2;        Y[16] = _y + h - h2 - int(0.85 * h3);

    paint->setPen(Qt::NoPen);            // do not draw outline

    // Create a polygon.
    COPY_POINT(0, 0);
    COPY_POINT(1, 1);
    COPY_POINT(2, 6);
    COPY_POINT(3, 5);
    paint->setBrush(bottom);

    paint->drawPolygon(poly.constData(), 4, Qt::OddEvenFill);

    // Create a polygon.
    COPY_POINT(0, 1);
    COPY_POINT(1, 2);
    COPY_POINT(2, 7);
    COPY_POINT(3, 6);
    paint->drawPolygon(poly.constData(), 4, Qt::OddEvenFill);

    // Create a polygon.
    COPY_POINT(0, 2);
    COPY_POINT(1, 3);
    COPY_POINT(2, 8);
    COPY_POINT(3, 7);
    paint->setBrush(top);
    paint->drawPolygon(poly.constData(), 4, Qt::OddEvenFill);

    // Create a polygon.
    COPY_POINT(0, 3);
    COPY_POINT(1, 4);
    COPY_POINT(2, 9);
    COPY_POINT(3, 8);
    paint->drawPolygon(poly.constData(), 4, Qt::OddEvenFill);

    // Create a polygon.
    COPY_POINT(0, 4);
    COPY_POINT(1, 0);
    COPY_POINT(2, 5);
    COPY_POINT(3, 9);
    paint->drawPolygon(poly.constData(), 4, Qt::OddEvenFill);

    // Create a polygon.
    COPY_POINT(0, 5);
    COPY_POINT(1, 6);
    COPY_POINT(2, 7);
    COPY_POINT(3, 8);
    COPY_POINT(4, 9);
    paint->setBrush(fore);
    paint->drawPolygon(poly.constData(), 5, Qt::OddEvenFill);

    // If the width is > 2 times the shadow thickness then we have
    // room to draw the interior color rectangle.
    if(w >(shadow_thick << 1))
    {
        // Create a polygon.
        COPY_POINT(0, 10);
        COPY_POINT(1, 11);
        COPY_POINT(2, 12);
        COPY_POINT(3, 13);
        paint->setBrush(cpt);
        paint->drawPolygon(poly.constData(), 4, Qt::OddEvenFill);

        int boxX, boxY, boxWidth, boxHeight;

        switch(orient)
        {
        case HorizontalTop:
            boxX = X[12];
            boxY = Y[12];
            boxWidth = X[11] - X[12];
            boxHeight = Y[10] - Y[11];
            break;
        default:
            boxX = boxY = boxWidth = boxHeight = 0; // just for now.
        }

        // Draw the sunken bevel around the spectrum.
        drawBox(paint, QRect(boxX, boxY, boxWidth, boxHeight),
          palette().dark().color(), palette().light().color());

        // Create the select polygon.
        if(selected)
        {
            QPalette g(palette());
            g.setColor(QPalette::Button, sel);
            drawArrow(paint, true, X[16], Y[16], w2 << 1,(int)(h3 * 0.65), g);
        }
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::drawBox
//
// Purpose:
//   Draws a highlighted box that looks like the edges of a button.
//
// Arguments:
//   paint : The painter used to draw.
//   r     : The bounding rectangle of the frame
//   light : The light color
//   dark  : The dark color
//   lw    : The width of the box.
//
// Programmer: Brad Whitlock
// Creation:   Tue Mar 12 18:45:56 PST 2002
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::drawBox(QPainter *paint, const QRect &r,
    const QColor &light, const QColor &dark, int lw)
{
    int i;
    int X  = r.x();
    int X2 = r.x() + r.width() - 1;
    int Y  = r.y();
    int Y2 = r.y() + r.height() - 1;

    // Draw the highlight
    paint->setPen(QPen(light));
    for(i = 0; i < lw; ++i)
    {
        paint->drawLine(QPoint(X + i, Y + i), QPoint(X + i, Y2 - i));
        paint->drawLine(QPoint(X + i, Y + i), QPoint(X2 - i, Y + i));
    }

    // Draw the shadow
    paint->setPen(QPen(dark));
    for(i = 0; i < lw; ++i)
    {
        paint->drawLine(QPoint(X + i + 1, Y2 - i), QPoint(X2, Y2 - i));
        paint->drawLine(QPoint(X2 - i, Y + i + 1), QPoint(X2 - i, Y2));
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::drawArrow
//
// Purpose:
//   Draws an arrow.
//
// Note:       This code was copied from Qt 2.3.0 and trimmed down to be
//             less general.
//
// Programmer: Brad Whitlock
// Creation:   Wed Mar 13 09:37:51 PDT 2002
//
// Modifications:
//   Brad Whitlock, Thu Aug 21 15:50:13 PST 2003
//   I changed how the brush is created so the arrows look better on MacOS X.
//
// ****************************************************************************

void
QvisSpectrumBar::drawArrow(QPainter *p, bool down, int _x, int _y, int w, int h,
    const QPalette &g)
{
    QPolygon bFill;            // fill polygon
    QPolygon bTop;             // top shadow.
    QPolygon bBot;             // bottom shadow.
    QPolygon bLeft;            // left shadow.
    QMatrix  matrix;           // xform matrix
    bool vertical = orientation == HorizontalTop ||
                    orientation == HorizontalBottom;
    bool horizontal = !vertical;
    int     dim = w < h ? w : h;
    int     colspec = 0x0000;     // color specification array

    if(dim < 2)                   // too small arrow
        return;

    // adjust size and center(to fix rotation below)
    if(w >  dim)
    {
        _x += (w-dim)/2;
        w = dim;
    }
    if(h > dim)
    {
        _y += (h-dim)/2;
        h = dim;
    }

    if(dim > 3)
    {
        if(dim > 6)
            bFill.resize(dim & 1 ? 3 : 4);
        bTop.resize((dim/2)*2);
        bBot.resize(dim & 1 ? dim + 1 : dim);
        bLeft.resize(dim > 4 ? 4 : 2);
        bLeft.putPoints(0, 2, 0,0, 0,dim-1);

        if(dim > 4)
            bLeft.putPoints(2, 2, 1,2, 1,dim-3);
        bTop.putPoints(0, 4, 1,0, 1,1, 2,1, 3,1);
        bBot.putPoints(0, 4, 1,dim-1, 1,dim-2, 2,dim-2, 3,dim-2);

        for(int i=0; i<dim/2-2 ; i++)
        {
            bTop.putPoints(i*2+4, 2, 2+i*2,2+i, 5+i*2, 2+i);
            bBot.putPoints(i*2+4, 2, 2+i*2,dim-3-i, 5+i*2,dim-3-i);
        }

        if(dim & 1)                // odd number size: extra line
            bBot.putPoints(dim-1, 2, dim-3,dim/2, dim-1,dim/2);
        if(dim > 6)
        {            // dim>6: must fill interior
            bFill.putPoints(0, 2, 1,dim-3, 1,2);
            if(dim & 1)            // if size is an odd number
                bFill.setPoint(2, dim - 3, dim / 2);
            else
                bFill.putPoints(2, 2, dim-4,dim/2-1, dim-4,dim/2);
        }
    }
    else
    {
        if(dim == 3)
        {            // 3x3 arrow pattern
            bLeft.setPoints(4, 0,0, 0,2, 1,1, 1,1);
            bTop .setPoints(2, 1,0, 1,0);
            bBot .setPoints(2, 1,2, 2,1);
        }
        else
        {                    // 2x2 arrow pattern
            bLeft.setPoints(2, 0,0, 0,1);
            bTop .setPoints(2, 1,0, 1,0);
            bBot .setPoints(2, 1,1, 1,1);
        }
    }

    if(orientation == HorizontalBottom || orientation == VerticalRight)
    {
        matrix.translate(_x, _y);
        if(vertical)
        {
            matrix.translate(0, h - 1);
            matrix.rotate(-90);
        }
        else
        {
            matrix.translate(w - 1, h - 1);
            matrix.rotate(180);
        }

        if(down)
            colspec = horizontal ? 0x2334 : 0x2343;
        else
            colspec = horizontal ? 0x1443 : 0x1434;
    }
    else
    {
        matrix.translate(_x, _y);
        if(vertical)
        {
            matrix.translate(w-1, 0);
            matrix.rotate(90);
        }
        if(down)
            colspec = horizontal ? 0x2443 : 0x2434;
        else
            colspec = horizontal ? 0x1334 : 0x1343;
    }

    QColor *cols[5];
    cols[0] = 0;
    cols[1] = (QColor *)&g.button();
    cols[2] = (QColor *)&g.mid();
    cols[3] = (QColor *)&g.light();
    cols[4] = (QColor *)&g.dark();

#define CMID    *cols[(colspec>>12) & 0xf ]
#define CLEFT   *cols[(colspec>>8) & 0xf ]
#define CTOP    *cols[(colspec>>4) & 0xf ]
#define CBOT    *cols[ colspec & 0xf ]

    QPen     savePen   = p->pen();     // save current pen
    QBrush   saveBrush = p->brush();   // save current brush
    QMatrix wxm = p->worldMatrix();
    QPen     pen(Qt::NoPen);
#ifdef Q_WS_MACX
    QBrush   brush(g.brush(QPalette::Background));
#else
    QBrush   brush(g.brush(QPalette::Button));
#endif

    p->setPen(pen);
    p->setBrush(brush);
    p->setWorldMatrix(matrix, TRUE);   // set transformation matrix
    p->drawPolygon(bFill);             // fill arrow
    p->setBrush(Qt::NoBrush);              // don't fill

    p->setPen(CLEFT);
    p->drawLines(bLeft.constData(),bLeft.size());
    p->setPen(CTOP);
    p->drawLines(bTop.constData(),bTop.size());
    p->setPen(CBOT);
    p->drawLines(bBot.constData(),bBot.size());

    p->setWorldMatrix(wxm);
    p->setBrush(saveBrush);            // restore brush
    p->setPen(savePen);                // restore pen

#undef CMID
#undef CLEFT
#undef CTOP
#undef CBOT
}

// ****************************************************************************
// Method: QvisSpectrumBar::drawSpectrum
//
// Purpose:
//   Draws the color spectrum into an internal pixmap.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:19:06 PDT 2001
//
// Modifications:
//   Brad Whitlock, Tue Mar 12 13:59:45 PST 2002
//   Removed all dependencies on style objects and made it use a single
//   pixmap.
//
//   Brad Whitlock, Thu Aug 21 15:42:50 PST 2003
//   I changed how the brush is selected so the widget looks better on MacOS X.
//
// ****************************************************************************

void
QvisSpectrumBar::drawSpectrum()
{
    // If the pixmap is not allocated, allocate it.
    bool totalFill = false;
    if(pixmap == 0)
    {
        pixmap = new QPixmap(width(), height());
        totalFill = true;
    }

#ifdef Q_WS_MACX
    QBrush brush(palette().brush(QPalette::Background));
#else
    QBrush brush(palette().brush(QPalette::Button));
#endif

    // Create a painter and fill in the entire area with the background color.
    QPainter paint(pixmap);
    if(totalFill)
        paint.fillRect(0, 0, width(), height(), brush);

    // Get the area that we can draw the spectrum in.
    QRect area(spectrumArea.x() + 2, spectrumArea.y() + 2,
               spectrumArea.width() - 4, spectrumArea.height() - 4);

    // Figure out the range based on the orientation of the widget.
    int range;
    if(orientation == HorizontalTop || orientation == HorizontalBottom)
        range = area.width();
    else
        range = area.height();

    // Get an array containing the interpolated color spectrum in a form we can use.
    unsigned char *interpolatedColors = getRawColors(range);

    // Draw the spectrum.
    if(interpolatedColors)
    {
        unsigned char *cptr = interpolatedColors;

        // Draw the spectrum based on the orientation of the widget.
        if(orientation == HorizontalTop || orientation == HorizontalBottom)
        {
            // Draw vertical lines.
            for(int i = 0; i < range; ++i, cptr += 3)
            {
                paint.setPen(QPen(QColor((int)cptr[0],(int)cptr[1],(int)cptr[2])));
                paint.drawLine(i + area.x(), area.y(),
                               i + area.x(), area.y() + area.height() + 1);
            }
        }
        else
        {
            // Draw horizontal lines.
            for(int i = range - 1; i >= 0; --i, cptr += 3)
            {
                paint.setPen(QPen(QColor((int)cptr[0],(int)cptr[1],(int)cptr[2])));
                paint.drawLine(area.x(), i + area.y(),
                               area.x() + area.width() + 1, i + area.y());
            }
        }

        // Draw the sunken bevel around the spectrum.
        drawBox(&paint, spectrumArea, palette().dark().color(), palette().light().color());

        // Delete the color array.
        delete [] interpolatedColors;
    }

    // Make pixmap the background pixmap.
//    setBackgroundPixmap(*pixmap);
     QPalette _palette;
     _palette.setBrush(this->backgroundRole(), QBrush(*pixmap));
     this->setPalette(_palette);
}

// ****************************************************************************
// Method: QvisSpectrumBar::getRawColors
//
// Purpose:
//   Figures out the raw colors that will be used to draw the spectrum.
//
// Arguments:
//     w     : The widget for which we're getting raw colors.
//     range : The number of available color slots.
//
// Returns:
//     This function returns a block of memory range*3 bytes long.
//     Colors are arranged in the memory as rgbrgbrgb... where each
//     color channel is one byte.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:19:33 PDT 2001
//
// Modifications:
//   Brad Whitlock, Thu Oct 24 11:06:22 PDT 2002
//   I removed the restriction that the endpoints lie at 0 and 1.
//
//   Brad Whitlock, Mon Jul 14 14:43:04 PST 2003
//   I added a little range checking code.
//
// ****************************************************************************

unsigned char *
QvisSpectrumBar::getRawColors(int range)
{
    unsigned char *row = NULL;
    int i, ci, npoints, c = 0;
    ControlPoint *oldpts = NULL, *newpts = NULL;
    ControlPoint *c1 = NULL, *c2 = NULL;

    // Return early if the range is bad.
    if(range < 1)
        return 0;

    // Allocate memory for the array to be returned.
    int arrayLength = range * 3;
    row = new unsigned char[arrayLength];

    /*******************************************
     * Phase I -- If the widget is non-editable
     *            then it has no control points.
     *            We must do colors differently.
     ******************************************/
    if(!this->controlPoints->CanBeEdited())
    {
        for(i = 0; i < range; ++i)
        {
            /* The desired index into the colors we have. */
            int src_index = (int)(((float)i /(float)(range - 1)) *
               (this->controlPoints->NumColorValues() - 1)) * 3;

            row[i * 3]     = (unsigned char)(controlPoints->ColorValue(src_index) * 255.);
            row[i * 3 + 1] = (unsigned char)(controlPoints->ColorValue(src_index+1) * 255.);
            row[i * 3 + 2] = (unsigned char)(controlPoints->ColorValue(src_index+2) * 255.);
        }

        return row;
    }

    /*******************************************
     * Phase II -- Determine the number of
     *             control points needed and
     *             allocate storage.
     ******************************************/
    npoints = this->controlPoints->NumControlPoints();
    if(equalSpacing() || !smoothing())
        oldpts = new ControlPoint[npoints + 1];
    else
        oldpts = new ControlPoint[npoints];

    // Sort the color control points.
    for(i = 0; i < this->controlPoints->NumControlPoints(); ++i)
        oldpts[i] = this->controlPoints->operator[](i);
    qsort((void *)oldpts, npoints, sizeof(ControlPoint), ControlPointCompare);

    /*******************************************
     * Phase III -- Process the control points.
     ******************************************/
    if(equalSpacing() || !smoothing())
    {
        ++npoints;
        newpts = new ControlPoint[npoints];

        if(equalSpacing())
        {
            // Do the equal spacing case.
            for(i = 0; i < npoints; ++i)
            {
                ci = (i <(npoints - 2)) ? i :(npoints - 2);
                newpts[i].position = (float)i /(float)(npoints - 1);

                if(!smoothing())
                {
                    memcpy((char *)newpts[i].color,
                          (char *)oldpts[ci].color, 3 * sizeof(float));
                }
                else
                {
                    if(i < 1 || i >= npoints - 1)
                    {
                        memcpy((char *)newpts[i].color,
                              (char *)oldpts[ci].color,
                               3 * sizeof(float));
                    }
                    else
                    {
                        newpts[i].color[0] = (oldpts[i].color[0] +
                            oldpts[i-1].color[0])*0.5;
                        newpts[i].color[1] = (oldpts[i].color[1] +
                            oldpts[i-1].color[1])*0.5;
                        newpts[i].color[2] = (oldpts[i].color[2] +
                            oldpts[i-1].color[2])*0.5;
                    }
                }
            } // end for
        } // end if equal spacing
        else
        {
            // Do non-equal, non-smooth case.
            memcpy((char *)newpts,(char *)oldpts, sizeof(ControlPoint));
            for(i = 1; i < npoints - 1; i++)
            {
                newpts[i].position = oldpts[i-1].position +
                   ((oldpts[i].position - oldpts[i-1].position) * 0.5);
                memcpy((char *)newpts[i].color,
                      (char *)oldpts[i].color,
                       3 * sizeof(float));
            }
            memcpy((char *)&newpts[npoints-1],(char *)&oldpts[npoints-2],
                   sizeof(ControlPoint));
        }
        c1 = newpts;
    }
    else
        c1 = oldpts;

    /********************************************
     * Phase IV -- Figure the colors for a row.
     ********************************************/
    c2 = c1;
    int consecutiveZeroLengthRanges = 0;
    for(ci = 0; ci < npoints - 1; ci++)
    {
        float delta_r, delta_g, delta_b;
        float r_sum, g_sum, b_sum;
        int   color_start_i, color_end_i, color_range;

        // Initialize some variables.
        c2++;
        color_start_i = int(c1->position * range);
        color_end_i = int(c2->position * range);
        color_range = color_end_i - color_start_i;

        if(color_range > 1)
        {
            consecutiveZeroLengthRanges = 0;
            if(ci == 0 && color_start_i != 0)
            {
                for(i = 0; i < color_start_i; i++)
                {
                    if(c < arrayLength)
                    {
                        row[c++] = (unsigned char)(c1->color[0] * 255);
                        row[c++] = (unsigned char)(c1->color[1] * 255);
                        row[c++] = (unsigned char)(c1->color[2] * 255);
                    }
                }
            }

            // Figure out some deltas.
            if(smoothing())
            {
                delta_r = (float)(c2->color[0]-c1->color[0])/(float)(color_range-1);
                delta_g = (float)(c2->color[1]-c1->color[1])/(float)(color_range-1);
                delta_b = (float)(c2->color[2]-c1->color[2])/(float)(color_range-1);
            }
            else
                delta_r = delta_g = delta_b = 0.;

            // Initialize sums.
            r_sum = c1->color[0]; g_sum = c1->color[1]; b_sum = c1->color[2];

            // Interpolate color1 to color2.
            for(i = color_start_i; i < color_end_i; i++)
            {
                // Store the colors as 24 bit rgb.
                if(c < arrayLength)
                {
                    row[c++] = (unsigned char)(r_sum * 255);
                    row[c++] = (unsigned char)(g_sum * 255);
                    row[c++] = (unsigned char)(b_sum * 255);
                }

                // Add the color deltas.
                r_sum += delta_r; g_sum += delta_g; b_sum += delta_b;
            }

            if(ci == npoints - 2 && color_end_i != range)
            {
                for(i = color_end_i; i < range; i++)
                {
                    if(c < arrayLength)
                    {
                        row[c++] = (unsigned char)(c2->color[0] * 255);
                        row[c++] = (unsigned char)(c2->color[1] * 255);
                        row[c++] = (unsigned char)(c2->color[2] * 255);
                    }
                }
            }
        }
        else if(c < arrayLength)
        {
            row[c++] = (unsigned char)(c1->color[0] * 255);
            row[c++] = (unsigned char)(c1->color[1] * 255);
            row[c++] = (unsigned char)(c1->color[2] * 255);

            // If this is the second zero length range in a row, back up.
            if(++consecutiveZeroLengthRanges > 1)
                c -= 3;
        }

        c1++;
    }

    // Free unneeded memory.
    delete [] oldpts;
    delete [] newpts;

    // Return some color information.
    return row;
}

// ****************************************************************************
// Method: QvisSpectrumBar::paintEvent
//
// Purpose:
//   Handles paint events for the widget.
//
// Arguments:
//   e : The paint event.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:10:01 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 10:22:05 PDT 2002
//   Made it use a single pixmap and added a focus rectangle.
//
// ****************************************************************************

void
QvisSpectrumBar::paintEvent(QPaintEvent* /*e*/)
{
    // bool clipByRegion = true;
/*
    // If the spectrum pixmap does not exist, generate it.
    if(pixmap == 0)
    {
        drawControls();
        drawSpectrum();
        clipByRegion = false;
    }

    // Create a painter to draw on this widget. If the pixmaps that we're
    // blitting did not have to be created, pay attention to the clipping
    // region stored in the paint event.
    QPainter paint(this);
    if(clipByRegion && !e->region().isEmpty())
        paint.setClipRegion(e->region());

    // Blit the pixmaps to the screen.
    paint.drawPixmap(QPoint(0,0), *pixmap);

    // If this widget has the focus then draw the focus rectangle.
    if(hasFocus())
    {

        QStyleOptionFocusRect option;
        option.init(this);
        option.backgroundColor = palette().color(QPalette::Background);
        style()->drawPrimitive(QStyle::PE_FrameFocusRect, &option, &paint, this);
    }
*/
}

// ****************************************************************************
// Method: QvisSpectrumBar::resizeEvent
//
// Purpose:
//   Handles resize events for the widget.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:10:38 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap.
//
// ****************************************************************************

void
QvisSpectrumBar::resizeEvent(QResizeEvent *)
{
    // Recalculate the size of the spectrum area and the controls area.
    if(orientation == HorizontalTop)
    {
        int controlHeight = int(height() * 0.4) - margin;
        if(controlHeight > 60)
            controlHeight = 60;

        controlsArea.setHeight(controlHeight);
        slider.setWidth((int)(controlsArea.height() / 1.6));
        slider.setHeight(controlsArea.height());

        controlsArea.setX(margin);
        controlsArea.setY(margin);
        controlsArea.setWidth(width() -(margin << 1));

        // These are not important since the slider never moves.
        slider.setX(0);
        slider.setY(controlsArea.y());

        // Figure the area for the spectrum area.
        spectrumArea.setX(margin +(slider.width() >> 1));
        spectrumArea.setY(margin + controlsArea.height());
        spectrumArea.setWidth(width() -(spectrumArea.x() << 1));
        spectrumArea.setHeight(height() - spectrumArea.y() - margin);
    }
    else
        qDebug("This orientation is not supported yet!");

    // Update the whole widget.
    deletePixmap();
    update();
}

// ****************************************************************************
// Method: QvisSpectrumBar::paletteChange
//
// Purpose:
//   This method forces the widget to redraw when the palette is changed.
//
// Programmer: Brad Whitlock
// Creation:   Fri Sep 7 12:48:19 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap.
//
// ****************************************************************************

void
QvisSpectrumBar::paletteChange(const QPalette &)
{
    // Update the whole widget.
    deletePixmap();
    update();
}

// ****************************************************************************
// Method: QvisSpectrumBar::keyPressEvent
//
// Purpose:
//   Handles keypress events for the widgets.
//
// Arguments:
//   e : The key press event.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:21:56 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 12:05:25 PDT 2002
//   Made key support better.
//
// ****************************************************************************

void
QvisSpectrumBar::keyPressEvent(QKeyEvent *e)
{
    // Figure the currently selected point.
    int current_sel = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
    int new_sel;

    if(equalSpacing())
    {
        switch(e->key())
        {
        case Qt::Key_Left:
            if(current_sel > 0)
                new_sel = current_sel - 1;
            else
                new_sel = controlPoints->NumControlPoints() - 1;
            controlPoints->GiveHighestRank(new_sel);
            updateControlPoints();
            break;
        case Qt::Key_Right:
            if(current_sel < controlPoints->NumControlPoints() - 1)
                new_sel = current_sel + 1;
            else
                new_sel = 0;
            controlPoints->GiveHighestRank(new_sel);
            updateControlPoints();
            break;
        case Qt::Key_Up:
        case Qt::Key_Return:
            colorSelected(current_sel);
        }
    }
    else
    {
        switch(e->key())
        {
        case Qt::Key_Shift:
            shiftApplied = true;
            break;
        case Qt::Key_Left:
            if(shiftApplied)
                moveControlPoint(PAGE_DECREMENT);
            else
                moveControlPoint(DECREMENT);
            break;
        case Qt::Key_Up:
        case Qt::Key_Return:
            colorSelected(current_sel);
            break;
        case Qt::Key_Right:
            if(shiftApplied)
                moveControlPoint(PAGE_INCREMENT);
            else
                moveControlPoint(INCREMENT);
            break;
        case Qt::Key_PageUp:
            moveControlPoint(PAGE_INCREMENT);
            break;
        case Qt::Key_PageDown:
            moveControlPoint(PAGE_DECREMENT);
            break;
        case Qt::Key_Home:
            moveControlPoint(PAGE_HOME);
            break;
        case Qt::Key_End:
            moveControlPoint(PAGE_END);
            break;
        case Qt::Key_Backspace:
        case Qt::Key_Space:
            // Find the index of the control point with the lowest rank.
            new_sel = controlPoints->Rank(0);
            // Give the control point the highest rank and redraw the controls.
            controlPoints->GiveHighestRank(new_sel);
            updateControlPoints();
            break;
        }
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::colorSelected
//
// Purpose:
//   This routine is called when we want to select a color. It emits signals
//   that could be used for a popup menu.
//
// Arguments:
//   index : The index of the control point that we're selecting.
//
// Programmer: Brad Whitlock
// Creation:   Wed Mar 13 17:55:29 PST 2002
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::colorSelected(int index)
{
    // Get the location of the current control point.
    QPoint p(controlPointLocation(index));
    // Figure out the center of the control point.
    QPoint pc(p.x() + slider.width() / 2, p.y() + slider.height() / 2);

    emit selectColor(index);
    emit selectColor(index, mapToGlobal(pc));
}

// ****************************************************************************
// Method: QvisSpectrumBar::keyReleaseEvent
//
// Purpose:
//   Handles key release events for the widget. This is mainly to determine
//   when the shift key has been released.
//
// Arguments:
//   e : The key event.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:22:28 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::keyReleaseEvent(QKeyEvent *e)
{
    if(e->key() == Qt::Key_Shift)
        shiftApplied = false;
}

// ****************************************************************************
// Method: QvisSpectrumBar::mousePressEvent
//
// Purpose:
//   Handles mouse press events for the widget.
//
// Arguments:
//   e : The mouse press event.
//
// Notes:      Emits activeControlPointChanged signal.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:23:13 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 11:01:03 PDT 2002
//   Modified the clip region.
//
// ****************************************************************************

void
QvisSpectrumBar::mousePressEvent(QMouseEvent * /*e*/)
{
/*
    // Check to see if a slider was clicked. If so, we need to
    // set the active slider and update the controls.
    int   new_sel, current_sel;
    float fpos, fwidth;

    // Figure the currently selected point.
    current_sel = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

    // Figure the position of the event and the width of the
    // slider as numbers in the range [0.,1.].
    if(orientation == HorizontalTop || orientation == HorizontalBottom)
    {
        fpos = ((float)(e->x() - controlsArea.x() - slider.width()/2) /
               (float)(controlsArea.width() - slider.width()));
        fwidth = (float)slider.width() /
                (float)(controlsArea.width() - slider.width());
    }
    else
    {
        fpos = ((float)(e->y() - controlsArea.y()) /
               (float)controlsArea.height());
        fwidth = (float)slider.height() /
                (float)controlsArea.height();
    }

    // See if we selected a new control point.
    new_sel = controlPoints->ChangeSelectedIndex(fpos, fwidth, equalSpacing());

    // If we did select a point, set its location into the fields
    // that are used to track the slider's location.
    if(new_sel != -1 && new_sel != current_sel)
    {
        // Sort the control points and get the new index.
        controlPoints->Sort();
        new_sel = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

        // Redraw the controls.
        drawControls();

        // Figure out the regions covered by the old and new control points
        // and only repaint those regions.
        QPoint newLocation(controlPointLocation(new_sel));
        QPoint oldLocation(controlPointLocation(current_sel));

        QRegion r(newLocation.x(), newLocation.y(),
                  slider.width(), slider.height());
        QRegion r2(oldLocation.x(), oldLocation.y(),
                   slider.width(), slider.height());

        // Update the region covered by the new and old control points.
        repaint(r + r2);

        // Emit a signal containing the index of the new control point.
        emit activeControlPointChanged(new_sel);
    }
    else if(new_sel == -1 && controlPoints->CanBeEdited() && !b_equalSpacing)
    {
        // The trough: check to see what side of the current control point we're on.
        new_sel = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

        // If we're on the left of the current control point, decrement.
        bool page_decrement = (fpos < controlPoints->operator[](new_sel).position);

        if(page_decrement)
        {
            moveControlPoint(PAGE_DECREMENT);
            paging_mode = PAGE_DECREMENT;
            timer->start(150);
        }
        else
        {
            moveControlPoint(PAGE_INCREMENT);
            paging_mode = PAGE_INCREMENT;
            timer->start(150);
        }
    }

    // If a control point was clicked with the right mouse button, emit
    // a signal that will tell external customers that the control point
    // wants a new color.
    if(new_sel != -1 && e->button() == Qt::RightButton)
        colorSelected(new_sel);
*/
}

// ****************************************************************************
// Method: QvisSpectrumBar::mouseMoveEvent
//
// Purpose:
//   Handles mouse move events for the widget.
//
// Arguments:
//   e : The mouse move event.
//
// Notes:      Emits controlPointMoved signal.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:24:02 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 11:44:06 PDT 2002
//   Added code to return if we have equal spacing.
//
//   Brad Whitlock, Thu Mar 14 08:26:53 PDT 2002
//   Added code to make sure the control points are sorted if we are in
//   continuous update mode.
//
// ****************************************************************************

void
QvisSpectrumBar::mouseMoveEvent(QMouseEvent *e)
{
    // If the widget is doing paging events, ignore mouse motion.
    if(paging_mode != NO_PAGING || !controlPoints->CanBeEdited() || b_equalSpacing)
        return;

    // Indicate that the mouse is sliding a control point.
    b_sliding = true;

    int   current_sel;
    float fpos;

    // Figure the currently selected point.
    current_sel = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

    // Figure the position of the event as a number in the range [0.,1.].
    if(orientation == HorizontalTop || orientation == HorizontalBottom)
    {
        fpos = ((float)(e->x() - controlsArea.x() - slider.width()/2) /
               (float)(controlsArea.width() - slider.width()));
    }
    else
    {
        fpos = ((float)(e->y() - controlsArea.y()) /
               (float)controlsArea.height());
    }

    // Clamp the position to [0, 1.]
    if(fpos < 0.) fpos = 0.;
    if(fpos > 1.) fpos = 1.;

    // If the position is different than the current position, udpate the
    // control area and emit a signal.
    if(fpos != controlPoints->Position(current_sel))
    {
        // Move the control point to its new location and redraw.
        moveControlPointRedraw(current_sel, fpos, continuousUpdate());

        // If we're emitting signals with each mouse movement, emit the
        // position of the current control point.
        if(continuousUpdate())
        {
            controlPoints->Sort();
            current_sel = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
            emit controlPointMoved(current_sel, fpos);
        }
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::mouseReleaseEvent
//
// Purpose:
//   Handles mouse release events for the widget.
//
// Note:       Emits controlPointMoved signal.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:24:28 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::mouseReleaseEvent(QMouseEvent *)
{
    // If we're doing some paging, stop the timer.
    if(paging_mode != NO_PAGING)
    {
        timer->stop();
        paging_mode = NO_PAGING;
    }

    if(b_sliding)
    {
        b_sliding = false;

        if(!continuousUpdate())
        {
            drawSpectrum();
            QRegion r(spectrumArea.x(), spectrumArea.y(), spectrumArea.width(),
                      spectrumArea.height());
            repaint(r);

            // Emit a signal containing the index and position of the control
            // point with the highest rank.
            controlPoints->Sort();
            int index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
            emit controlPointMoved(index, controlPoints->Position(index));
        }
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::updateControlPoints
//
// Purpose:
//   Changes the active control point.
//
// Note:       Emits activeControlPointChanged signal.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:26:40 PDT 2001
//
// Modifications:
//   Brad Whitlock, Thu Mar 14 08:30:06 PDT 2002
//   Added code to sort the control points.
//
// ****************************************************************************

void
QvisSpectrumBar::updateControlPoints()
{
    controlPoints->Sort();
    int index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

    if(isVisible())
    {
        drawControls();
        update(controlsArea.x(), controlsArea.y(), controlsArea.width(),
               controlsArea.height());
    }
    else
        deletePixmap();

    // Emit a signal containing the index of the new control point.
    emit activeControlPointChanged(index);
}

// ****************************************************************************
// Method: QvisSpectrumBar::moveControlPointRedraw
//
// Purpose:
//   Moves a control point to a new position and redraws the controls.
//
// Arguments:
//   index          : The index of the control point to move.
//   _pos            : The new control point position.
//   redrawSpectrum : Whether or not to redraw the spectrum.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:27:15 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 28 10:13:00 PDT 2001
//   Added code to suppress updates.
//
//   Brad Whitlock, Wed Mar 13 10:09:38 PDT 2002
//   Made it use a single pixmap.
//
// ****************************************************************************

void
QvisSpectrumBar::moveControlPointRedraw(int index, float _pos, bool redrawSpectrum)
{
    // Get the current location of the control point.
    QPoint oldLocation(controlPointLocation(index));

    // Set the new position and draw the controls.
    controlPoints->SetPosition(index, _pos);

    // If we're suppressing updates, delete the pixmaps and return.
    if(b_suppressUpdates)
    {
        deletePixmap();
        return;
    }

    if(isVisible())
    {
        drawControls();

        // Construct a region that covers the old and new locations of the control point.
        QPoint newLocation(controlPointLocation(index));
        QRegion r(newLocation.x(), newLocation.y(),
                  slider.width(), slider.height());
        QRegion r2(oldLocation.x(), oldLocation.y(),
                   slider.width(), slider.height());
        QRegion final(r + r2);

        // Update the spectrum
        if(redrawSpectrum)
        {
            drawSpectrum();
            QRegion r3(spectrumArea.x(), spectrumArea.y(), spectrumArea.width(),
                       spectrumArea.height());
            final = final + r3;
        }

        // Update the region covered by the new and old control points and
        // maybe the spectrum area. By only repainting those relatively small
        // rectangles, flicker due to repainting is much reduced.
        repaint(final);
    }
    else
        deletePixmap();
}

// ****************************************************************************
// Method: QvisSpectrumBar::moveControlPoint
//
// Purpose:
//   Moves a control point in the specified manner.
//
// Arguments:
//   changeType : How to move the control point.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:28:43 PDT 2001
//
// Modifications:
//   Brad Whitlock, Wed Mar 13 11:49:55 PDT 2002
//   Added code to return if we cannot modify the control point.
//
// ****************************************************************************

void
QvisSpectrumBar::moveControlPoint(int changeType)
{
    int   index;
    float increment, page_increment;

    // Return if we cannot modify the control point.
    if(!controlPoints->CanBeEdited() || b_equalSpacing)
        return;

    // Get the index of the highest ranking control point.
    index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);

    // Figure out the increments and page increments based on the
    // widget's orientation.
    if(orientation == HorizontalTop || orientation == HorizontalBottom)
    {
        increment = 1. /(float)controlsArea.width();
        page_increment = (float)slider.width() * increment;
    }
    else
    {
        increment = 1. /(float)controlsArea.height();
        page_increment = (float)slider.height() * increment;
    }

    // Change the value by the appropriate amount
    float old_position = controlPoints->Position(index);
    float new_position = controlPoints->Position(index);
    switch(changeType)
    {
    case INCREMENT:
        new_position += increment;
        break;
    case DECREMENT:
        new_position -= increment;
        break;
    case PAGE_INCREMENT:
        new_position += page_increment;
        break;
    case PAGE_DECREMENT:
        new_position -= page_increment;
        break;
    case PAGE_HOME:
        new_position = 0.;
        break;
    case PAGE_END:
        new_position = 1.;
        break;
    }

    // Make sure the new position is in [0., 1.]
    if(new_position < 0.) new_position = 0.;
    if(new_position > 1.) new_position = 1.;

    // If the new position differs from the old position, store the new
    // position and update the widget.
    if(new_position != old_position)
    {
        // Move the control point and redraw the spectrum and the controls.
        moveControlPointRedraw(index, new_position, true);

        // Emit a signal containing the index and position of the control
        // point with the highest rank.
        controlPoints->Sort();
        index = controlPoints->Rank(controlPoints->NumControlPoints() - 1);
        emit controlPointMoved(index, new_position);
    }
}

// ****************************************************************************
// Method: QvisSpectrumBar::handlePaging
//
// Purpose:
//   This is a Qt slot function that is called by the widget's timer in order
//   to move the control point by paging.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:29:46 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
QvisSpectrumBar::handlePaging()
{
    moveControlPoint(paging_mode);
}

//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
/// Definitions of internally used classes and structs.
//////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

const int ControlPointList::CPLIST_INCREMENT = 5;
const ControlPoint ControlPointList::defaultControlPoint1 = {1, 0., {1., 0., 0.}};
const ControlPoint ControlPointList::defaultControlPoint2 = {0, 1., {0., 0., 1.}};

// ****************************************************************************
// Method: ControlPointList::ControlPointList
//
// Purpose:
//   Constructor for the ControlPointList class.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:30:38 PDT 2001
//
// Modifications:
//
// ****************************************************************************

ControlPointList::ControlPointList()
{
    editable = true;
    nvals = 0;
    colorvals = NULL;

    nels = 2;
    total_nels = CPLIST_INCREMENT;
    list = new ControlPoint[CPLIST_INCREMENT];

    // Init the control points.
    list[0] = defaultControlPoint1;
    list[1] = defaultControlPoint2;
}

// ****************************************************************************
// Method: ControlPointList::~ControlPointList
//
// Purpose:
//   Destructor for the ControlPointList class.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:31:01 PDT 2001
//
// Modifications:
//
// ****************************************************************************

ControlPointList::~ControlPointList()
{
    Clear();
}

// ****************************************************************************
// Method: ControlPointList::Clear
//
// Purpose:
//   Frees memory used by the class.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:31:20 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::Clear()
{
    nels = 0;
    total_nels = 0;
    delete [] list;
    nvals = 0;
    delete [] colorvals;
}

// ****************************************************************************
// Method: ControlPointList::ColorValue
//
// Purpose:
//   Returns the color value for the specified index.
//
// Arguments:
//   index : The index of the colorval to return.
//
// Returns:    The color value for the specified index.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:31:40 PDT 2001
//
// Modifications:
//
// ****************************************************************************

float
ControlPointList::ColorValue(int index) const
{
    if(nvals == 0 || colorvals == NULL || index < 0 || index >= nvals*3)
        return 0.;
    else
        return colorvals[index];
}

// ****************************************************************************
// Method: ControlPointList::operator []
//
// Purpose:
//   Returns a reference to the specified control point.
//
// Arguments:
//   index : The index of the specified control point.
//
// Returns:    A reference to the specified control point.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:32:24 PDT 2001
//
// Modifications:
//
// ****************************************************************************

const ControlPoint &
ControlPointList::operator [](int index) const
{
    if(nels == 0 || list == NULL || index < 0 || index >= nels)
        return defaultControlPoint1;
    else
        return list[index];
}

// ****************************************************************************
// Method: ControlPointList::Position
//
// Purpose:
//   Returns the position of the specified control point.
//
// Arguments:
//   index : The index of the control point.
//
// Returns:    The position of the specified control point.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:33:12 PDT 2001
//
// Modifications:
//
// ****************************************************************************

float
ControlPointList::Position(int index) const
{
    if(nels == 0 || list == NULL || index < 0 || index >= nels)
        return 0.;
    else
        return list[index].position;
}

// ****************************************************************************
// Method: ControlPointList::SetPosition
//
// Purpose:
//   Sets the position of a control point.
//
// Arguments:
//   index : The index of the control point to change.
//   _pos   : The control point's new position.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:34:02 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::SetPosition(int index, float _pos)
{
    if(nels != 0 && list != NULL && index >= 0 && index < nels)
        list[index].position = _pos;
}

// ****************************************************************************
// Method: ControlPointList::NumControlPoints
//
// Purpose:
//   Returns the number of control points.
//
// Returns:    The number of control points.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:34:49 PDT 2001
//
// Modifications:
//
// ****************************************************************************

int
ControlPointList::NumControlPoints() const
{
    return nels;
}

// ****************************************************************************
// Method: ControlPointList::NumColorValues
//
// Purpose:
//   Returns the number of color values.
//
// Returns:    The number of color values.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:35:25 PDT 2001
//
// Modifications:
//
// ****************************************************************************

int
ControlPointList::NumColorValues() const
{
    return nvals;
}

// ****************************************************************************
// Method: ControlPointList::CanBeEdited
//
// Purpose:
//   Returns whether or not the control point list can be edited.
//
// Returns:    Whether or not the control point list can be edited.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:36:10 PDT 2001
//
// Modifications:
//
// ****************************************************************************

bool
ControlPointList::CanBeEdited() const
{
    return editable;
}

// ****************************************************************************
// Method: ControlPointList::SetEditMode
//
// Purpose:
//   Sets the list's edit mode.
//
// Arguments:
//   val : The new edit mode.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:36:44 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::SetEditMode(bool val)
{
    editable = val;
}

// ****************************************************************************
// Method: ControlPointList::SetColor
//
// Purpose:
//   Sets the color for the specified control point.
//
// Arguments:
//   index : The control point index.
//   r     : The red value [0., 1.]
//   g     : The green value [0., 1.]
//   b     : The blue value [0., 1.]
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:37:12 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::SetColor(int index, float r, float g, float b)
{
    if(nels != 0 && list != NULL && index >= 0 && index < nels)
    {
        list[index].color[0] = r;
        list[index].color[1] = g;
        list[index].color[2] = b;
    }
}

// ****************************************************************************
// Method: ControlPointList::SetColorValues
//
// Purpose:
//   Sets the color values for the list.
//
// Arguments:
//    colors  : The new colors array.
//    ncolors : The number of colors.
//
// Note:       The colors array is owned by this object after this call.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:38:11 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::SetColorValues(const float *colors, int ncolors)
{
    // If there are already raw colors in the list, free them.
    delete [] colorvals;

    // Make this object own the colors array that is passed in.
    nvals = ncolors;
    colorvals = (float *)colors;
}

// ****************************************************************************
// Method: ControlPointList::Add
//
// Purpose: Adds a new control point to the cp_list. The new control point
//          gets the highest rank and becomes the selected point.
//
// Arguments:
//    cpt : The control point being added.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:39:19 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::Add(const ControlPoint *cpt)
{
    ControlPoint *newList = NULL;

    // See if we need to resize.
    if(nels + 1 > total_nels)
    {
        total_nels += CPLIST_INCREMENT;
        newList = new ControlPoint[total_nels];

        // Copy the elements into the new array.
        memcpy((char *)newList,(char *)list, nels * sizeof(ControlPoint));

        // Update the lists.
        delete [] list;
        list = newList;
    }

    // Put the new control point in the list. Ignore any rank it might
    // have. We're going to manage them here.
    memcpy((char *)&list[nels],(char *)cpt, sizeof(ControlPoint));
    list[nels].rank = nels;
    ++nels;

    // Sort the points.
    Sort();
}

// ****************************************************************************
// Method: ControlPointList::Rank
//
// Purpose:
//   Finds the index of the control point with a given rank.
//
// Arguments:
//   rank : The rank we're looking for.
//
// Returns:    The index of the control point with a given rank.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:40:44 PDT 2001
//
// Modifications:
//
// ****************************************************************************

int
ControlPointList::Rank(int rank) const
{
    int i, retval = 0;

    for(i = 0; i < nels; ++i)
        if(rank == list[i].rank)
        {
            retval = i;
            break;
        }

    return retval;
}

// ****************************************************************************
// Method: ControlPointList::DeleteHighestRank
//
// Purpose:
//   Deletes the control point with the highest rank.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:41:29 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::DeleteHighestRank()
{
    int rank = 0;

    if(nels <= 2)
        return;

    // Get the rank of the point we're deleting.
    rank = Rank(nels - 1);

    // Move all of the control points and ranks that were to the
    // right of the point that we want to delete.
    if(rank != nels - 1)
    {
        memcpy((char *)&list[rank],(char *)&list[rank + 1],
              (nels - 1 - rank) * sizeof(ControlPoint));
    }

    // Reduce the length of the list.
    --nels;
}

// ****************************************************************************
// Method: ControlPointList::ChangeSelectedIndex
//
// Purpose:
//   Finds the control point that is being selected.
//
// Arguments:
//    _pos   : A float value in the range [0,1] that represents the center of
//            a control point.
//    width : The width of a control point in terms of a number(0,1).
//    equal : Whether or not the control points are equally spaced.
//
// Returns:    The control point that is being selected or -1 is not control
//             point was at the specified position.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:42:16 PDT 2001
//
// Modifications:
//
// ****************************************************************************

int
ControlPointList::ChangeSelectedIndex(float _pos, float width, int equal)
{
    int   i, index, retval = -1;
    float position, offset, wd2 = width * 0.6;

    if(equal)
        offset = 1. / nels;
    else
        offset = 0.;

    // Descend through the ranks.
    for(i = nels - 1; i >= 0; --i)
    {
        // Find the index with rank i.
        index = Rank(i);

        if(equal)
            position = (index *(1. - offset)) +(offset * 0.5);
        else
            position = list[index].position;

        /* See if the position is in the control point. */
        if((_pos - wd2 <= position) &&(position <= _pos + wd2))
        {
            retval = index;
            break;
        }
    }

    // If we found a control point, adjust the ranks so it
    // has the highest rank.
    if(retval >= 0)
    {
        GiveHighestRank(retval);
    }

    return retval;
}

// ****************************************************************************
// Method: ControlPointList::GiveHighestRank
//
// Purpose:
//   Gives the highest rank to the specified control point.
//
// Arguments:
//   index : The index of control point we want to have the highest rank.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:44:43 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::GiveHighestRank(int index)
{
    for(int i = 0; i < nels; ++i)
    {
        if(list[i].rank > list[index].rank)
            --(list[i].rank);
    }

    // Give the specified point the highest rank.
    list[index].rank = nels - 1;
}

// ****************************************************************************
// Method: ControlPointList::Sort
//
// Purpose:
//   Sorts the list.
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:45:34 PDT 2001
//
// Modifications:
//
// ****************************************************************************

void
ControlPointList::Sort()
{
    qsort((void *)list, nels, sizeof(ControlPoint), ControlPointCompare);
}

// ****************************************************************************
// Function: ControlPointCompare
//
// Purpose:
//   Compare two control_pt variables. This is used as a callback for qsort.
//
// Arguments:
//   c1 : A pointer to the first ControlPoint struct.
//   c2 : A pointer to the second ControlPoint struct.
//
//
// Programmer: Brad Whitlock
// Creation:   Wed Jan 3 11:45:59 PDT 2001
//
// Modifications:
//
// ****************************************************************************

static int
ControlPointCompare(const void *c1, const void *c2)
{
    ControlPoint *cp1 = (ControlPoint *)c1;
    ControlPoint *cp2 = (ControlPoint *)c2;

    if(cp1->position < cp2->position)
        return -1;
    else if(cp1->position == cp2->position)
    {
        if(cp1->rank < cp2->rank)
            return -1;
        else if(cp1->rank == cp2->rank)
            return 0;
        else
            return 1;
    }
    else
        return 1;
}
