//=============================================================================
//  Awl
//  Audio Widget Library
//  $Id:$
//
//  Copyright (C) 2002-2006 by Werner Schweer and others
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License version 2.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//=============================================================================

#include "al/al.h"
#include "awl.h"
#include "posedit.h"
#include "al/sig.h"

namespace Awl {

//---------------------------------------------------------
//   PosEdit
//---------------------------------------------------------

PosEdit::PosEdit(QWidget* parent)
   : QAbstractSpinBox(parent)
      {
      initialized = false;
      setReadOnly(false);
      setSmpte(false);
      }

void* PosEdit::operator new(size_t n)
      {
      void* p = new char[n];
      memset(p, 0, n);
      return p;
      }

PosEdit::~PosEdit()
      {
      }

QSize PosEdit::sizeHint() const
	{
      QFontMetrics fm(font());
      int fw = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
      int h  = fm.height() + fw * 2;
      int w = fw * 4 + 10;	// HACK: 10 = spinbox up/down arrows
      if (_smpte)
            w  += 2 + fm.width('9') * 9 + fm.width(':') * 3 + fw * 4;
      else
            w  += 2 + fm.width('9') * 9 + fm.width('.') * 2 + fw * 4;
      return QSize(w, h).expandedTo(QApplication::globalStrut());
	}

//---------------------------------------------------------
//   event
//    filter Tab and Backtab key events
//---------------------------------------------------------

bool PosEdit::event(QEvent* event)
      {
      if (event->type() == QEvent::KeyPress) {
            QKeyEvent* ke = static_cast<QKeyEvent*>(event);
            int segment = curSegment();
            if (ke->key() == Qt::Key_Backtab) {
                  if (_smpte) {
                        if (segment == 3) {
                              lineEdit()->setSelection(7, 2);
                              return true;
                              }
                        else if (segment == 2) {
                              lineEdit()->setSelection(4, 2);
                              return true;
                              }
                        else if (segment == 1) {
                              lineEdit()->setSelection(0, 3);
                              return true;
                              }
                        }
                  else {
                        if (segment == 2) {
                              lineEdit()->setSelection(5, 2);
                              return true;
                              }
                        if (segment == 1) {
                              lineEdit()->setSelection(0, 4);
                              return true;
                              }
                        }
                  }
            if (ke->key() == Qt::Key_Tab) {
                  if (_smpte) {
                        if (segment == 0) {
                              lineEdit()->setSelection(4, 2);
                              return true;
                              }
                        else if (segment == 1) {
                              lineEdit()->setSelection(7, 2);
                              return true;
                              }
                        else if (segment == 2) {
                              lineEdit()->setSelection(10, 2);
                              return true;
                              }
                        }
                  else {
                        if (segment == 0) {
                              lineEdit()->setSelection(5, 2);
                              return true;
                              }
                        if (segment == 1) {
                              lineEdit()->setSelection(8, 3);
                              return true;
                              }
                        }
                  }
            }
      else if (event->type() == QEvent::FocusIn) {
            QFocusEvent* fe = static_cast<QFocusEvent*>(event);
            QAbstractSpinBox::focusInEvent(fe);
            int segment = curSegment();
            switch(segment) {
                  case 0:  lineEdit()->setSelection(0,4); break;
                  case 1:  lineEdit()->setSelection(5,2); break;
                  case 2:  lineEdit()->setSelection(8,3); break;
                  }
            return true;
            }
      return QAbstractSpinBox::event(event);
      }

//---------------------------------------------------------
//   setSmpte
//---------------------------------------------------------

void PosEdit::setSmpte(bool f)
      {
      _smpte = f;
      if (_smpte)
            lineEdit()->setInputMask("999:99:99:99");
      else
            lineEdit()->setInputMask("9999.99.999");
      updateValue();
      }

//---------------------------------------------------------
//   setValue
//---------------------------------------------------------

void PosEdit::setValue(const Pos& time)
      {
      _pos = time;
      updateValue();
      }

void PosEdit::setValue(const QString& s)
      {
      Pos time(s);
      setValue(time);
      }

void PosEdit::setValue(int t)
      {
      Pos time(t);
      setValue(time);
      }

//---------------------------------------------------------
//   updateValue
//---------------------------------------------------------

void PosEdit::updateValue()
      {
      char buffer[64];
      if (_smpte) {
            int minute, sec, frame, subframe;
            _pos.msf(&minute, &sec, &frame, &subframe);
            sprintf(buffer, "%03d:%02d:%02d:%02d", minute, sec, frame, subframe);
            }
      else {
            int bar, beat;
            int tick;
            _pos.mbt(&bar, &beat, &tick);
            sprintf(buffer, "%04d.%02d.%03d", bar+1, beat+1, tick);
            }
      lineEdit()->setText(buffer);
      }

//---------------------------------------------------------
//   stepEnables
//---------------------------------------------------------

QAbstractSpinBox::StepEnabled PosEdit::stepEnabled() const
      {
      int segment = curSegment();
      QAbstractSpinBox::StepEnabled en = QAbstractSpinBox::StepUpEnabled | QAbstractSpinBox::StepDownEnabled;

      if (_smpte) {
             int minute, sec, frame, subframe;
            _pos.msf(&minute, &sec, &frame, &subframe);
            switch(segment) {
                  case 0:
                        if (minute == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        break;
                  case 1:
                        if (sec == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        else if (sec == 59)
                              en &= ~QAbstractSpinBox::StepUpEnabled;
                        break;
                  case 2:
                        if (frame == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        else if (frame == 23)
                              en &= ~QAbstractSpinBox::StepUpEnabled;
                        break;
                  case 3:
                        if (subframe == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        else if (subframe == 99)
                              en &= ~QAbstractSpinBox::StepUpEnabled;
                        break;
                  }
            }
      else {
            int bar, beat;
            unsigned tick;
            sigmap.tickValues(_pos.tick(), &bar, &beat, &tick);
            unsigned tb = sigmap.ticksBeat(_pos.tick());
            unsigned tm = sigmap.ticksMeasure(_pos.tick());
            int bm = tm / tb;

            switch (segment) {
                  case 0:
                        if (bar == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        break;
                  case 1:
                        if (beat == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        else {
                              if (beat >= (bm-1))
                                    en &= ~QAbstractSpinBox::StepUpEnabled;
                              }
                        break;
                  case 2:
                        if (tick == 0)
                              en &= ~QAbstractSpinBox::StepDownEnabled;
                        else {
                              if (tick >= (tb-1))
                                    en &= ~QAbstractSpinBox::StepUpEnabled;
                              }
                        break;
                  }
            }
      return en;
      }

//---------------------------------------------------------
//   fixup
//---------------------------------------------------------

void PosEdit::fixup(QString& input) const
      {
      printf("fixup <%s>\n", input.toLatin1().data());
      }

//---------------------------------------------------------
//   validate
//---------------------------------------------------------

QValidator::State PosEdit::validate(QString&,int&) const
      {
      // TODO
//      printf("validate\n");
      return QValidator::Acceptable;
      }

//---------------------------------------------------------
//   curSegment
//---------------------------------------------------------

int PosEdit::curSegment() const
      {
      QLineEdit* le = lineEdit();
      int pos = le->cursorPosition();
      int segment = -1;

      if (_smpte) {
            if (pos >= 0 && pos <= 3)
                  segment = 0;
            else if (pos >= 4 && pos <= 6)
                  segment = 1;
            else if (pos >= 7 && pos <= 9)
                  segment = 2;
            else if (pos >= 10)
                  segment = 3;
            }
      else {
            if (pos >= 0 && pos <= 4)
                  segment = 0;
            else if (pos >= 5 && pos <= 7)
                  segment = 1;
            else if (pos >= 8)
                  segment = 2;
            else
                  printf("curSegment = -1, pos %d\n", pos);
            }
      return segment;
      }

//---------------------------------------------------------
//   stepBy
//---------------------------------------------------------

void PosEdit::stepBy(int steps)
      {
      int segment = curSegment();
      int selPos;
      int selLen;

      bool changed = false;

      if (_smpte) {
             int minute, sec, frame, subframe;
            _pos.msf(&minute, &sec, &frame, &subframe);
            switch(segment) {
                  case 0:
                        minute += steps;
                        if (minute < 0)
                              minute = 0;
                        selPos = 0;
                        selLen = 3;
                        break;
                  case 1:
                        sec += steps;
                        if (sec < 0)
                              sec = 0;
                        if (sec > 59)
                              sec = 59;
                        selPos = 4;
                        selLen = 2;
                        break;
                  case 2:
                        frame += steps;
                        if (frame < 0)
                              frame = 0;
                        if (frame > 24)         //TD frame type?
                              frame = 24;
                        selPos = 7;
                        selLen = 2;
                        break;
                  case 3:
                        subframe += steps;
                        if (subframe < 0)
                              subframe = 0;
                        if (subframe > 99)
                              subframe = 99;
                        selPos = 10;
                        selLen = 2;
                        break;
                  default:
                        return;
                  }
            Pos newPos(minute, sec, frame, subframe);
            if (!(newPos == _pos)) {
                  changed = true;
                  _pos = newPos;
                  }
            }
      else {
            int bar, beat, tick;
            _pos.mbt(&bar, &beat, &tick);

            int tb = sigmap.ticksBeat(_pos.tick());
            unsigned tm = sigmap.ticksMeasure(_pos.tick());
            int bm = tm / tb;

            switch(segment) {
                  case 0:
                        bar += steps;
                        if (bar < 0)
                              bar = 0;
                        selPos = 0;
                        selLen = 4;
                        break;
                  case 1:
                        beat += steps;
                        if (beat < 0)
                              beat = 0;
                        else if (beat >= bm)
                              beat = bm - 1;
                        selPos = 5;
                        selLen = 2;
                        break;
                  case 2:
                        tick += steps;
                        if (tick < 0)
                              tick = 0;
                        else if (tick >= tb)
                              tick = tb -1;
                        selPos = 8;
                        selLen = 3;
                        break;
                  default:
                        return;
                  }
            Pos newPos(bar, beat, tick);
            if (!(newPos == _pos)) {
                  changed = true;
                  _pos = newPos;
                  }
            }
      if (changed) {
            updateValue();
            emit valueChanged(_pos);
            }
      lineEdit()->setSelection(selPos, selLen);
      }

      void PosEdit::paintEvent(QPaintEvent* event) {
            if (!initialized)
                  updateValue();
            initialized = true;
            QAbstractSpinBox::paintEvent(event);
            }
}

