/*
 *
 *    soniK digital audio editor
 *    Copyright (C) 2003-2006  Robert Walker <rob@tenfoot.org.uk>
 *
 *    This program is free software; you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation; either version 2 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program; if not, write to the Free Software
 *    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
#include "partwidget.h"

#include "part.h"
#include "wavewidget.h"
#include "display.h"
#include "actionmanager.h"

#include "controls/zoom.h"
#include "controls/timescale.h"
#include "controls/valuescale.h"

#include "sonik_util.h"

#include <kapplication.h>
#include <kcursor.h>
#include <kdebug.h>
#include <klocale.h>

#include <qprinter.h>
#include <qpainter.h>
#include <qlayout.h>
#include <qscrollbar.h>

using Sonik::PartWidget;

namespace
{
  const int kMaxZoom = 10;
  const uint32_t kMinDataScreenWidth = 200;

  const int kTimeScaleSelStartMarker = 0;
  const int kTimeScaleSelEndMarker   = 1;

  // TODO: only record explicit select
  class SelectAction : public Sonik::Action
  {
  public:
    SelectAction(PartWidget& widget, off_t start, size_t length, const QString& name)
      : Sonik::Action(name), mWidget(widget), mStart(start), mLength(length) { }
    virtual void apply() { mWidget.select(mStart, mLength); }

    virtual QString debugPrint(int indent)
    {
      return Action::debugPrint(indent) + QString("Select %1 %2").arg(mStart).arg(mLength);
    }

  private:
    PartWidget& mWidget;
    off_t       mStart;
    size_t      mLength;
  };


  int zoomPowerForWidth(size_t l, int w)
  {
    if (l == 0)
      return -kMaxZoom;
    else
      return -ilogb((double)l / w) - 1;
  }
}

PartWidget::PartWidget(Part& part, ActionManager& actionManager,
                       QWidget* parent, const char* name)
  : QWidget(parent, name),
    mParent(part),
    mActionManager(actionManager),
    mNumDisplays(0),
    mValueFormat(kNormalised),
    mCurrentDisplay(0),
    mSelectionStart(0),
    mSelectionLength(0),
    mDisplaySelectionStart(0),
    mDisplaySelectionLength(0),
    mCursorPos(0),
    mScrollPos(0),
    mZoom(1.0),
    mPlaybackStartPos(0),
    mPlaybackPos(0),
    mSelectMode(kSelectModeNone),
    mSelectState(kSelectNone),
    mSelectRefPoint(0),
    mSelectRefPointScreenOffset(0),
    mSelectDragLength(0)
{
  setBackgroundMode(NoBackground);
  setMouseTracking(true);

  //
  //  +-+---------------+
  //  |S|               |
  //  |c|  Displays     |
  //  |a|               |
  //  |l|               |
  //  |e|               |
  //  +-+---------------+
  //  | |time scale     |
  //  | |scroll bar | z |
  //  +-+---------------+
  //
  mMainLayout = new QGridLayout(this, 2, 2);

  // Top left
  mScaleLayout = new QVBoxLayout();
  mMainLayout->addLayout(mScaleLayout, 0, 0);

  // Top right
  mDisplayLayout = new QVBoxLayout();
  mMainLayout->addLayout(mDisplayLayout, 0, 1);

  // Bottom left
  mMainLayout->addWidget(new QWidget(this, "Corner"), 1, 0);

  // Bottom right
  mBottomLayout = new QGridLayout(2, 2);
  mBottomLayout->setColStretch(0, 1);
  mMainLayout->addLayout(mBottomLayout, 1, 1);
  mTimeScale = new TimeScale(this, "TimeScale");
  mBottomLayout->addMultiCellWidget(mTimeScale, 0, 0, 0, 1);
  mScreenPos = new QScrollBar(0, 1, 10, 100, 0, QScrollBar::Horizontal,
                               this, "ScreenPos");
  mBottomLayout->addWidget(mScreenPos, 1, 0);
  mScreenZoom = new Zoom(-kMaxZoom, kMaxZoom, 0, Qt::Horizontal, true,
                          this, "ScreenZoom");
  mBottomLayout->addWidget(mScreenZoom, 1, 1);

  connect(mScreenPos, SIGNAL(valueChanged(int)),
          mTimeScale, SLOT(posChanged(int)));
  connect(mScreenPos, SIGNAL(valueChanged(int)),
          this, SLOT(scrollPosChanged(int)));
  connect(mScreenZoom, SIGNAL(zoomChanged(float)),
          mTimeScale, SLOT(zoomChanged(float)));
  connect(mScreenZoom, SIGNAL(zoomChanged(float)),
          this, SLOT(zoomChanged(float)));

  dataChannelsChanged(1);

  // TODO: doesn't fully account for value scale
  setMinimumSize(mScreenZoom->width() + 48 + mValueScales.front()->width(),
                 mScreenZoom->height() + mTimeScale->height() + 32);

  // watch for mouse movement to update cursor
  mTimeScale->setMouseTracking(true);

  setFocusPolicy(QWidget::StrongFocus);
}

PartWidget::~PartWidget()
{
}

void PartWidget::print(QPrinter *pPrinter)
{
  QPainter printpainter;
  printpainter.begin(pPrinter);

  // TODO: add your printing code here

  printpainter.end();
}

off_t PartWidget::selectionStart() const
{
  return mSelectionStart;
}

size_t PartWidget::selectionLength() const
{
  return mSelectionLength;
}

void PartWidget::select(off_t start, size_t length)
{
  if (mSelectionLength != 0 || length != 0)
  {
    // ignore repeated cursor movements
    mActionManager.recordAction(
      new SelectAction(*this, mSelectionStart, mSelectionLength, i18n("Select"))
      );
  }

  changeSelection(start, length);

  mSelectionStart  = mDisplaySelectionStart;
  mSelectionLength = mDisplaySelectionLength;

  emit selectionChanged(mSelectionStart, mSelectionLength);
}

void PartWidget::changeSelection(off_t start, size_t length)
{
  if (mParent.data().length() > 0)
    mDisplaySelectionStart =
      Sonik::bound(start, (off_t)0, (off_t)mParent.data().length());
  else
    mDisplaySelectionStart = 0;

  mDisplaySelectionLength = length;
  if (mDisplaySelectionStart + mDisplaySelectionLength > mParent.data().length())
    mDisplaySelectionLength = mParent.data().length() - mDisplaySelectionStart;

  // timescale markers
  if (mDisplaySelectionLength > 0)
  {
    mTimeScale->setMarker(kTimeScaleSelStartMarker,
                          mDisplaySelectionStart,
                          TimeScale::Lower, TimeScale::Triangle);
    mTimeScale->setMarker(kTimeScaleSelEndMarker,
                          mDisplaySelectionStart + mDisplaySelectionLength - 1,
                          TimeScale::Upper, TimeScale::Triangle);
  }
  else
  {
    mTimeScale->setMarker(kTimeScaleSelStartMarker,
                          mDisplaySelectionStart,
                          TimeScale::Middle, TimeScale::Triangle);
    mTimeScale->removeMarker(kTimeScaleSelEndMarker);
  }

  emit displaySelectionChanged(mDisplaySelectionStart, mDisplaySelectionLength);
}

off_t PartWidget::cursorPos() const
{
  return mCursorPos;
}

void PartWidget::setPlaybackPos(off_t pos)
{
  mPlaybackPos = pos;
  emit playbackPosChanged(mPlaybackPos);
}

off_t PartWidget::playbackPos() const
{
  return mPlaybackPos;
}

void PartWidget::setCursorPos(off_t pos)
{
  mCursorPos = pos;
  emit cursorPosChanged(mCursorPos);
}

void PartWidget::scrollPosChanged(int value)
{
  mScrollPos = value;
}

void PartWidget::dataSampleRateChanged(uint32_t sampleRate)
{
  setValueFormat(mValueFormat);
  mTimeScale->setSampleRate(sampleRate);
}

void PartWidget::dataBitsChanged(uint8_t /*bits*/)
{
  setValueFormat(mValueFormat);
}

// TODO create displays as needed
void PartWidget::dataChannelsChanged(uint8_t channels)
{
  if (channels > mNumDisplays)
  {
    for (uint i = mNumDisplays; i < channels; i++)
    {
      ValueScale* valueScale =
        new ValueScale(this, "RangeScale" + QString::number(i));
      valueScale->show();
      mValueScales.append(valueScale);
      mScaleLayout->addWidget(valueScale);

      makeWaveWidget(i);
    }

    setValueFormat(mValueFormat);
  }
  else if (channels < mNumDisplays)
  {
    for (uint i = channels; i < mNumDisplays; i++)
    {
      delete mValueScales.back();
      mValueScales.pop_back();

      WaveWidget* waveWidget = mWaveWidgets.back();
      delete waveWidget;
      mWaveWidgets.pop_back();
    }
  }

  mNumDisplays = channels;

  updateValueScaleLabels();
}

void PartWidget::dataLengthChanged(size_t length)
{
  setMinZoom();
  setScreenPosRange();
  if (length == 0)
    mTimeScale->setMaxTime(0);
  else
    mTimeScale->setMaxTime(length - 1);

  QValueVector<WaveWidget*>::iterator w = mWaveWidgets.begin();
  for (; w != mWaveWidgets.end(); ++w)
    (*w)->lengthChanged(length);
}

void PartWidget::dataDataChanged(uint8_t channel, off_t start, size_t length)
{
  assert(channel < mWaveWidgets.size());

  mWaveWidgets[channel]->dataChanged(start, length);
}

void PartWidget::updateMouseSelection(int pos, int xpos, bool finish)
{
  uint selStart = mDisplaySelectionStart, selLength = mDisplaySelectionLength;
  uint origStart = selStart, origLength = selLength;

  pos = Sonik::bound(pos, 0, (int)mParent.data().length());

  int sampleRelativePos = screenOffsetToSample(xpos);

  if (mSelectState == kSelectDragAll)
  {
    selStart += pos - mSelectRefPoint;
    mSelectRefPoint = pos;
    if (selStart + selLength > mParent.data().length())
      selStart = mParent.data().length() - selLength;
  }
  else
  {
    if (pos > mSelectRefPoint)
    {
      selStart = mSelectRefPoint;
      selLength = pos - selStart + (sampleRelativePos <= 0 ? 0 : 1);
    }
    else if (pos < mSelectRefPoint)
    {
      selStart = pos + (sampleRelativePos <= 0 ? 0 : 1);
      selLength = mSelectRefPoint - selStart + 1;
    }
    else
    {
      if ((sampleRelativePos < 0 && mSelectRefPointScreenOffset >= 0) ||
          (sampleRelativePos >=0 && mSelectRefPointScreenOffset < 0))
      {
        //select 1 sample
        selStart = mSelectRefPoint;
        selLength = 1;
      }
      else
      {
        //place cursor
        selStart = pos;
        selLength = 0;
      }
    }
  }

  if (finish)
    select(selStart, selLength);
  else if (selStart != origStart || selLength != origLength)
    changeSelection(selStart, selLength);
}

void PartWidget::mousePressEvent(QMouseEvent *e)
{
  QWidget *child = childAt(e->pos());

  if (e->button() == Qt::LeftButton)
  {
    if (mSelectMode == kSelectModeNone &&
        (isWaveWidget(child) || (child == mTimeScale)))
    {
      int xpos = e->x() - mWaveWidgets.front()->x();
      int t = Sonik::bound(Sonik::screenToTime(xpos, mScrollPos, mZoom),
                           0, (int)mParent.data().length());

      mSelectRefPointScreenOffset = screenOffsetToSample(xpos);

      int selStartPos = selectionScreenLeft(), selEndPos = selectionScreenRight();

      // change the selection state:
      //  - move left edge if click within 1px of start
      //  - move right edge if click within 1px of end
      //  - move all if click within and shift pressed
      //  - otherwise start new selection
      if (xpos >= selStartPos-1 && xpos <= selStartPos+1)
      {
        mSelectState = kSelectDragStart;
        mSelectRefPoint = mDisplaySelectionStart + mDisplaySelectionLength - 1;
      }
      else if (xpos >= selEndPos-1 && xpos <= selEndPos+1)
      {
        mSelectState = kSelectDragEnd;
        mSelectRefPoint = mDisplaySelectionStart;
      }
      else if (xpos >= selStartPos && xpos <= selEndPos &&
               e->state() & Qt::ShiftButton)
      {
        mSelectState = kSelectDragAll;
        mSelectRefPoint = t;
      }
      else
      {
        mSelectState = kSelectDragNew;
        mSelectRefPoint = t;
      }

      mSelectMode = kSelectModeMouse;

      setCursor(KCursor::sizeHorCursor());
      updateMouseSelection(t, xpos, false);
    }
  }

  e->accept();
}

void PartWidget::mouseReleaseEvent(QMouseEvent *e)
{
  if (mSelectMode == kSelectModeMouse && mSelectState != kSelectNone)
  {
    int xpos = e->x() - mWaveWidgets.front()->x();
    int t = Sonik::bound(Sonik::screenToTime(xpos, mScrollPos, mZoom),
                         0, (int)mParent.data().length());

    updateMouseSelection(t, xpos, true);
    mSelectMode = kSelectModeNone;
    mSelectState = kSelectNone;
    setCursor(KCursor::arrowCursor());
  }

  e->accept();
}

void PartWidget::mouseMoveEvent(QMouseEvent *e)
{
  int xpos = e->x() - mWaveWidgets.front()->x();

  int t = Sonik::bound(Sonik::screenToTime(xpos, mScrollPos, mZoom),
                       0, (int)mParent.data().length());

  setCursorPos(t);

  if (mSelectMode == kSelectModeMouse && mSelectState != kSelectNone)
    updateMouseSelection(t, xpos, false);
  else if (mSelectMode == kSelectModeNone)
  {
    QWidget *child = childAt(e->pos());
    if (isWaveWidget(child) ||
        (child == mTimeScale))
    {
      int selStartPos = selectionScreenLeft(), selEndPos = selectionScreenRight();

      // show resize cursor if over selection edge or selection with shift pressed
      if ((xpos >= selStartPos-1 && xpos <= selStartPos+1) ||
          (xpos >= selEndPos-1 && xpos <= selEndPos+1) ||
          (xpos >= selStartPos && xpos <= selEndPos &&
           e->state() & Qt::ShiftButton))
        setCursor(KCursor::sizeHorCursor());
      else
        setCursor(KCursor::arrowCursor());
    }
    else
      setCursor(KCursor::arrowCursor());
  }

  e->accept();
}

void PartWidget::wheelEvent(QWheelEvent *e)
{
  // ignore wheel events while selecting
  if (mSelectMode != kSelectModeNone)
    return;

  // TODO: update resize cursor

  // delta is in multiples of WHEEL_DELTA=120
  int offset = e->delta()/120;

  if (e->state() & Qt::ShiftButton)
  {
    // Time movement
    int step = QMIN(KApplication::wheelScrollLines()*mScreenPos->lineStep(),
                    mScreenPos->pageStep());
    mScreenPos->setValue(mScreenPos->value() - offset*step);
  }
  else if (e->state() & Qt::ControlButton)
    // TODO: vertical zoom
    ;
  else
    // Time zoom
    mScreenZoom->setCurPower(mScreenZoom->curPower() + offset);

  e->accept();
}

void PartWidget::contextMenuEvent(QContextMenuEvent *e)
{
  QWidget *child = childAt(e->pos());

  if (isValueScale(child))
  {
    mParent.contextMenu(Part::kValueScale, e->globalPos());
  }
  else if (child == mTimeScale)
  {
    mParent.contextMenu(Part::kTimeScale, e->globalPos());
  }
  else if (isWaveWidget(child))
  {
    mParent.contextMenu(Part::kWaveWidget, e->globalPos());
  }
}

void PartWidget::keyPressEvent(QKeyEvent* e)
{
  // show size cursor if shift pressed in selected area
  if (e->key() == Qt::Key_Shift)
  {
    QPoint pos = mapFromGlobal(QCursor::pos());
    QWidget *child = childAt(pos);
    if (isWaveWidget(child) ||
        (child == mTimeScale))
    {
      int xpos = pos.x() - mWaveWidgets.front()->x();
      int selStartPos = selectionScreenLeft(), selEndPos = selectionScreenRight();

      if (xpos > selStartPos+1 && xpos < selEndPos-1)
        setCursor(KCursor::sizeHorCursor());
    }
  }
  else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right ||
           e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown ||
           e->key() == Qt::Key_Home || e->key() == Qt::Key_End)
  {
    int step = (mZoom > 1.0) ? 1 : (int)(1 / mZoom);
    if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown)
      step *= 10;
    int delta = (e->key() == Qt::Key_Left || e->key() == Qt::Key_PageUp) ? -step : step;

    switch (e->state() & (Qt::ShiftButton | Qt::ControlButton))
    {
      case 0:
        if (mSelectMode == kSelectModeNone)
        {
          // move selection start
          mSelectMode = kSelectModeKeyboard;
          mSelectState = kSelectDragNew;
          mSelectRefPoint = mSelectionStart + delta;
          mSelectDragLength = 0;
          if (e->key() == Qt::Key_Home)
            changeSelection(0, 0);
          else if (e->key() == Qt::Key_End)
            changeSelection(mParent.data().length(), 0);
          else
            changeSelection(mSelectRefPoint, 0);
        }
        break;

      case Qt::ShiftButton:

        if (mSelectMode != kSelectModeMouse)
        {
          // extend selection
          switch (mSelectState)
          {
            case kSelectNone:
              mSelectMode  = kSelectModeKeyboard;
              mSelectState = kSelectDragNew;
              if (e->key() == Qt::Key_Left ||
                  e->key() == Qt::Key_PageUp ||
                  e->key() == Qt::Key_Home)
                mSelectRefPoint = mSelectionStart + mSelectionLength;
              else
                mSelectRefPoint = mSelectionStart;

              mSelectDragLength = 0;

              // fall through

            case kSelectDragNew:
              if (e->key() == Qt::Key_Home)
                mSelectDragLength = -(mSelectRefPoint + 1);
              else if (e->key() == Qt::Key_End)
                mSelectDragLength = mParent.data().length() - mSelectRefPoint;
              else
                mSelectDragLength += delta;


              off_t selStart;
              size_t selLength;

              if (mSelectDragLength < 0)
              {
                if (-mSelectDragLength > mSelectRefPoint + 1)
                  mSelectDragLength = -(mSelectRefPoint + 1);

                selStart = mSelectRefPoint + mSelectDragLength + 1;
                selLength = -mSelectDragLength;
              }
              else
              {
                if (mSelectRefPoint + mSelectDragLength > (int)mParent.data().length())
                  mSelectDragLength = mParent.data().length() - mSelectRefPoint;

                selStart = mSelectRefPoint;
                selLength = mSelectDragLength;
              }

              changeSelection(selStart, selLength);

              break;

            default:
              // ignore if Control pressed
              break;
          }
        }

        break;

      case Qt::ControlButton:
        // move display
        if (e->key() == Qt::Key_Home)
          mScreenPos->setValue(0);
        else if (e->key() == Qt::Key_End)
          mScreenPos->setValue(mParent.data().length());
        else
          mScreenPos->setValue(mScreenPos->value() + delta * mScreenPos->lineStep());

        break;

      default:
        break;
    }
  }
}

void PartWidget::keyReleaseEvent(QKeyEvent* e)
{
  // show normal cursor if shift released in selected area
  if (e->key() == Qt::Key_Shift)
  {
    if (mSelectMode == kSelectModeNone)
    {
      QPoint pos = mapFromGlobal(QCursor::pos());
      QWidget *child = childAt(pos);
      if (isWaveWidget(child) ||
          (child == mTimeScale))
      {
        int xpos = pos.x() - mWaveWidgets.front()->x();
        int selStartPos = selectionScreenLeft(), selEndPos = selectionScreenRight();

        if (xpos > selStartPos+1 && xpos < selEndPos-1)
          setCursor(KCursor::arrowCursor());
      }
    }
    else if (mSelectMode == kSelectModeKeyboard)
    {
      //  finalize the selection
      select(mDisplaySelectionStart, mDisplaySelectionLength);

      mSelectMode = kSelectModeNone;
      mSelectState = kSelectNone;
    }
  }
  else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right ||
           e->key() == Qt::Key_PageUp || e->key() == Qt::Key_PageDown ||
           e->key() == Qt::Key_Home || e->key() == Qt::Key_End)
  {
    if (mSelectMode == kSelectModeKeyboard)
    {
      if ((e->state() & (Qt::ShiftButton | Qt::ControlButton)) == 0)
      {
        select(mDisplaySelectionStart, 0);

        mSelectMode = kSelectModeNone;
        mSelectState = kSelectNone;
      }
    }
  }
}

void PartWidget::resizeEvent(QResizeEvent* /* pEvent */)
{
  setScreenPosRange();
}

void PartWidget::zoomChanged(float value)
{
  if (mZoom != value)
  {
    mZoom = value;
    setScreenPosRange();

    // make selection visible
    size_t pad = 0;

    if (mDisplaySelectionLength < samplesOnScreen())
      pad = (samplesOnScreen() - mDisplaySelectionLength) / 2;
    else
      pad = samplesOnScreen() / 10;

    mScreenPos->setValue(mDisplaySelectionStart - pad);
  }
}

Sonik::TimeFormat PartWidget::timeFormat() const
{
  return mTimeScale->format();
}

void PartWidget::setTimeFormat(TimeFormat format)
{
  mTimeScale->setFormat(format);
}

Sonik::ValueFormat PartWidget::valueFormat() const
{
  return mValueFormat;
}

void PartWidget::setValueFormat(ValueFormat format)
{
  float min, max;
  bool logTick;
  QString tickFmt;

  if (mCurrentDisplay && mCurrentDisplay->verticalAxis() == Sonik::Display::kFreq)
    format = kFreq;

  mValueFormat   = format;

  switch (mValueFormat)
  {
    case kNormalised:
      min     = -1.0f;
      max     = 1.0f;
      logTick = false;
      tickFmt = "%.02f";
      break;

    case kValue:
      min     = -(1 << (mParent.data().bits()-1));
      max     = (1 << (mParent.data().bits()-1));
      logTick = false;
      tickFmt = "%.0f";
      break;

    case kValueDB:
      min     = -1.0f;
      max     = 1.0f;
      logTick = true;
      tickFmt = "%.0f dB";
      break;


    case kFreq:
      min     = 0.0f;
      max     = mParent.data().sampleRate()/2;
      logTick = false;
      tickFmt = "%5.0f Hz";
      break;
  }

  QValueVector<ValueScale*>::iterator it = mValueScales.begin();
  for ( ; it != mValueScales.end(); ++it)
  {
    (*it)->setMin(min);
    (*it)->setMax(max);
    (*it)->setIsLog(logTick);
    (*it)->setTickFormat(tickFmt);
    (*it)->setBorder(mCurrentDisplay ? mCurrentDisplay->verticalBorder() : 0);
  }
}

float PartWidget::zoom() const
{
  return mZoom;
}

void PartWidget::zoomIn()
{
  mScreenZoom->zoomIn();
}

void PartWidget::zoomOut()
{
  mScreenZoom->zoomOut();
}

void PartWidget::updateAll()
{
  QValueVector<WaveWidget*>::iterator it = mWaveWidgets.begin();
  for (; it != mWaveWidgets.end(); ++it)
    (*it)->update();
}

void PartWidget::resetView()
{
  setMinZoom();

  // zoom so that whole file fits in window

  mScreenZoom->setCurPower(
    zoomPowerForWidth(mParent.data().length(), mWaveWidgets.front()->width())
    );
  mZoom = mScreenZoom->curValue();

  setScreenPosRange();
  mScreenPos->setValue(0);

  select(0, 0);
  scrollPosChanged(0);

  mTimeScale->setMaxTime(mParent.data().length());
}

Sonik::Display* PartWidget::display() const
{
  return mCurrentDisplay;
}

void PartWidget::setDisplay(Sonik::Display* display)
{
  mCurrentDisplay = display;

  QValueVector<WaveWidget*>::iterator it = mWaveWidgets.begin();
  for (; it != mWaveWidgets.end(); ++it)
    delete *it;
  mWaveWidgets.clear();

  for (uint i = 0; i < mNumDisplays; i++)
    makeWaveWidget(i);
}

QSize PartWidget::sizeHint() const
{
  return QSize(800, 600);
}

void PartWidget::audioIOPlaying()
{
  mPlaybackStartPos = playbackPos();
}

void PartWidget::audioIORecording()
{
}

void PartWidget::audioIOPaused()
{
  mPlaybackStartPos = playbackPos();
}

void PartWidget::audioIOStopped()
{
  setPlaybackPos(mPlaybackStartPos);
}

void PartWidget::audioIOPosition(off_t pos)
{
  setPlaybackPos(pos);
}

Sonik::WaveWidget* PartWidget::makeWaveWidget(uint8_t channel)
{
  if (mCurrentDisplay == 0)
    return 0;

  // create the widget
  WaveWidget* waveWidget =
    mCurrentDisplay->makeWidget(mParent.data(), channel, this,
                                "WaveDisplay" + QString::number(channel));

  // set to current state
  waveWidget->selectionChanged(mDisplaySelectionStart, mDisplaySelectionLength);
  waveWidget->scrollPosChanged(mScrollPos);
  waveWidget->zoomChanged(mZoom);
  waveWidget->show();

  // connect signals
  // TODO: connect playback
  connect(this, SIGNAL(displaySelectionChanged(off_t, size_t)),
          waveWidget, SLOT(selectionChanged(off_t, size_t)));
  connect(mScreenPos, SIGNAL(valueChanged(int)),
          waveWidget, SLOT(scrollPosChanged(int)));
  connect(mScreenZoom, SIGNAL(zoomChanged(float)),
          waveWidget, SLOT(zoomChanged(float)));

  // add to collections
  mWaveWidgets.append(waveWidget);
  mDisplayLayout->addWidget(waveWidget);

  return waveWidget;
}

bool PartWidget::isWaveWidget(QWidget* w) const
{
  QValueVector<WaveWidget*>::const_iterator it =
    qFind(mWaveWidgets.begin(), mWaveWidgets.end(), static_cast<WaveWidget*>(w));
  return (it != mWaveWidgets.end());
}

bool PartWidget::isValueScale(QWidget* w) const
{
  QValueVector<ValueScale*>::const_iterator it =
    qFind(mValueScales.begin(), mValueScales.end(), static_cast<ValueScale*>(w));
  return (it != mValueScales.end());
}

void PartWidget::setScreenPosRange(void)
{
  mScreenPos->setMaxValue(
    QMAX(0, (int)(mParent.data().length() - samplesOnScreen() - 1)));
  mScreenPos->setSteps(QMAX(1, samplesOnScreen() / 20),
                       QMAX(2, samplesOnScreen() / 2));
}

void PartWidget::setMinZoom()
{
  // set min zoom so that display will be at least kMinDataScreenWidth and
  // less than 2 * kMinDataScreenWidth when fully zoomed out
  mScreenZoom->setMinPower(
    zoomPowerForWidth(mParent.data().length(), kMinDataScreenWidth)
    );
}

void PartWidget::updateValueScaleLabels()
{
  QValueVector<ValueScale*>::iterator scale = mValueScales.begin();
  for (int i = 1; scale != mValueScales.end(); ++scale, ++i)
  {
    QString label;
    if (mNumDisplays == 1)
      label = "";
    if (mNumDisplays == 2)
      label = (i == 1) ? i18n("Left") : i18n("Right");
    else if (mParent.data().channels() > 2)
      label = i18n("Channel %1").arg(i);

    (*scale)->setLabel(label);
  }
}

size_t PartWidget::samplesOnScreen() const
{
  if (mWaveWidgets.empty())
    return 0;
  else
    return (size_t)(mWaveWidgets.front()->width() / mZoom);
}

/*!
 *          Calculate distance to nearest sample
 * @param   screenPos On screen X co-ordinate
 * @return  number of pixels to nearest sample. Negative means sample is to
 *          right of screenPos.
 */
int PartWidget::screenOffsetToSample(int screenPos) const
{
  if (mZoom > 1.0)
  {
    if (screenPos < Sonik::timeToScreenM(mParent.data().length() - 1, mScrollPos, mZoom))
      return (screenPos - (int)(mZoom/2)) % (int)(mZoom) - (int)(mZoom/2);
    else
      return 1;
  }
  else
    return 0;
}


int PartWidget::selectionScreenLeft() const
{
  return Sonik::timeToScreenL(mDisplaySelectionStart, mScrollPos, mZoom);
}


int PartWidget::selectionScreenRight() const
{
  if (mDisplaySelectionStart + mDisplaySelectionLength == mParent.data().length())
    return Sonik::timeToScreenM(mDisplaySelectionStart + mDisplaySelectionLength - 1,
                                mScrollPos, mZoom);
  else
    return Sonik::timeToScreenU(mDisplaySelectionStart + mDisplaySelectionLength - 1,
                                mScrollPos, mZoom);
}


