//=========================================================
//  MusE
//  Linux Music Editor
//    $Id: scrollscale.cpp,v 1.2.2.2 2009/11/04 17:43:25 lunar_shuttle Exp $
//  (C) Copyright 1999 Werner Schweer (ws@seh.de)
//
//  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; 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 <stdio.h>
#include <cmath>

#include <QBoxLayout>
#include <QLabel>
#include <QResizeEvent>
#include <QScrollBar>
#include <QSlider>
#include <QToolButton>
#include <QToolTip>

// #include "globals.h"
#include "scrollscale.h"
#include "icons.h"

namespace MusEGui {

//---------------------------------------------------------
//   setScale
//    "val" - slider value in range 0-convertQuickZoomLevelToMag(zoomLevels-1)
//---------------------------------------------------------

void ScrollScale::setScale ( int val, int pos_offset )
{
        int mag_max = convertQuickZoomLevelToMag(zoomLevels-1);
	if(val < 0)
	  val = 0;
	else if(val > mag_max)
	  val = mag_max;
	int off = offset();
	int old_scale_val = scaleVal;
	if ( invers )
        val = mag_max - val;
	double min, max;
	if ( scaleMin < 0 )
		min = 1.0/ ( -scaleMin );
	else
		min = double ( scaleMin );

	if ( scaleMax < 0 )
		max = 1.0/ ( -scaleMax );
	else
		max = double ( scaleMax );

	double diff = max-min;
    double fkt  = double ( val ) /double(mag_max);
	double v = ( pow ( logbase, fkt )-1 ) / ( logbase-1 );
	double scale;
	if ( invers )
		scale = max - v * diff;
	else
		scale = min + v * diff;

	if ( scale < 1.0 )
		scaleVal = - ( int ( 1.0 / scale ) );
	else
		scaleVal = int ( scale );
	if ( scaleVal == -1 )   // nur so
		scaleVal = 1;

#if 0
	if ( scaleMax > scaleMin )
	{
		if ( scale < scaleMin )
			scale = scaleMin;
		else if ( scale > scaleMax )
			scale = scaleMax;
	}
	else
	{
		if ( scale < scaleMax )
			scale = scaleMax;
		else if ( scale > scaleMin )
			scale = scaleMin;
	}
#endif
    //fprintf(stderr, "scaleMin %d scaleMax %d val=%d emit scaleVal=%d\n", scaleMin, scaleMax, val, scaleVal);
	emit scaleChanged ( scaleVal );
	if ( !noScale )
		setRange ( minVal, maxVal );

  int i = ( scroll->orientation() == Qt::Horizontal ) ? width() : height();
  int pos, pmax;
  if ( scaleVal < 1 )
  {
    pos = ( off-scaleVal/2 ) / ( -scaleVal );
    pmax = ( maxVal-scaleVal-1 ) / ( -scaleVal ) - i;
  }
  else
  {
    pos = off * scaleVal;
    pmax = maxVal * scaleVal - i;
  }

  // Zoom at cursor support...
  if(pos_offset != 0)
  {
    double oscale = old_scale_val;
    double nscale = scaleVal;
    if(old_scale_val < 1)
      oscale = 1.0 / -old_scale_val;
    if(scaleVal < 1)
      nscale = 1.0 / -scaleVal;
    double scale_fact = nscale / oscale;
    int pos_diff = (int)((double)pos_offset * scale_fact - (double)pos_offset + 0.5);  // 0.5 for round-off
    pos += pos_diff;
  }
  
  if(pos > pmax)
    pos = pmax;
  setPos(pos);
}

//---------------------------------------------------------
//   setMag
//---------------------------------------------------------

void ScrollScale::setMag ( int cs, int pos_offset )
{
	scale->blockSignals(true);
	scale->setValue ( cs );
	scale->blockSignals(false);
	setScale ( cs, pos_offset );
}

//---------------------------------------------------------
//   setRange
//    min,max  ticks
//---------------------------------------------------------

void ScrollScale::setRange ( int min, int max )
{
//      if ((min != minVal) && (max != maxVal))
//            return;
	minVal = min;
	maxVal = max;
	int i = ( scroll->orientation() == Qt::Horizontal ) ? width() : height();

	if ( !noScale )
	{
		if ( scaleVal < 1 )
		{
			min = minVal / ( -scaleVal );
			max = ( maxVal-scaleVal-1 ) / ( -scaleVal ) - i;
		}
		else
		{
			min = minVal * scaleVal;
			max = maxVal * scaleVal - i;
		}
	}
	else
		max -= i;
	if ( max < 0 )
		max = 0;
	if ( min < 0 )
		min = 0;
	if ( min > max )
		max = min;
  
  scroll->setRange ( min, max );

	// qt doesn't check this...
	if ( scroll->value() < min )
		scroll->setValue ( min );
	if ( scroll->value() > max )
		scroll->setValue ( max );
        scroll->setSingleStep(20);
        scroll->setPageStep(i);
}

//---------------------------------------------------------
//   setPos
//    pos in pixel
//---------------------------------------------------------

void ScrollScale::setPos ( unsigned pos )
{
	scroll->setValue ( pos );
}

//---------------------------------------------------------
//   setPosNoLimit
//    pos in pixel
//---------------------------------------------------------

void ScrollScale::setPosNoLimit ( unsigned pos )
{
  //printf ( "ScrollScale::setPosNoLimit pos:%d scaleVal:%d offset ticks:%d\n", pos, scaleVal, pos2offset ( pos ) );

  if((int)pos > scroll->maximum())
    scroll->setMaximum(pos);
  scroll->setValue(pos);
}

//---------------------------------------------------------
//   resizeEvent
//---------------------------------------------------------

void ScrollScale::resizeEvent ( QResizeEvent* ev)
{
  QWidget::resizeEvent(ev);
  setScale ( scale->value() );
}

//---------------------------------------------------------
//   ScrollScale
//---------------------------------------------------------

ScrollScale::ScrollScale ( int s1, int s2, int cs, int max_, Qt::Orientation o,
                           QWidget* parent, int min_, bool inv, double bas )
		: QWidget ( parent )
{
	noScale     = false;
	_page        = 0;
	_pages       = 1;
	pageButtons = false;
	showMagFlag = true;
	scaleMin    = s1;
	scaleMax    = s2;
	minVal      = min_;
    maxVal      = max_;
	up          = 0;
	down        = 0;
	logbase     = bas;
	invers      = inv;
        scaleVal    = 0;

	double min, max;
	if ( scaleMin < 0 )
		min = 1.0/ ( -scaleMin );
	else
		min = double ( scaleMin );

	if ( scaleMax < 0 )
		max = 1.0/ ( -scaleMax );
	else
		max = double ( scaleMax );

	double cmag = ( cs < 0 ) ? ( 1.0/ ( -cs ) ) : double ( cs );
	double diff = max-min;

	//
	// search initial value for slider
	//
	int cur   = 512;
	int delta = 256;
	for ( int i = 0; i < 8; ++i )
	{
        int tryVal   = invers ? convertQuickZoomLevelToMag(zoomLevels-1)+1 - cur : cur;
        double fkt   = double ( tryVal ) /double(convertQuickZoomLevelToMag(zoomLevels-1));
		double v     = ( pow ( logbase, fkt )-1 ) / ( logbase-1 );
		double scale = invers ? ( max - v * diff ) : ( min + v * diff );
		if ( scale == cmag ) // not very likely
			break;
 //printf("iteration %d invers:%d soll %f(cur:%d) - ist %f\n", i, invers, scale, cur, cmag);
		int dd = invers ? -delta : delta;
		cur += ( scale < cmag ) ? dd : -dd;
        delta/=2;
	}

	scale  = new QSlider (o);
        // Added by Tim. For some reason focus was on. 
        // It messes up tabbing, and really should have a shortcut instead.
        scale->setFocusPolicy(Qt::NoFocus);  
        scale->setMinimum(0);
        scale->setMaximum(convertQuickZoomLevelToMag(zoomLevels-1));
	scale->setPageStep(1);
	scale->setValue(cur);	

	scroll = new QScrollBar ( o );
        //scroll->setFocusPolicy(Qt::NoFocus);  // Tim.
	setScale ( cur );

	if ( o == Qt::Horizontal )
	{
                box = new QBoxLayout ( QBoxLayout::LeftToRight);
		scale->setMaximumWidth ( 70 );
		scroll->setMinimumWidth ( 50 );
	}
	else
	{
                box = new QBoxLayout ( QBoxLayout::TopToBottom);
		scroll->setMinimumHeight ( 50 );
		scale->setMaximumHeight ( 70 );
	}
        box->setContentsMargins(0, 0, 0, 0);
        box->setSpacing(0);  
	box->addWidget ( scroll, 10 );
	box->addWidget ( scale, 5 );
	setLayout(box);
	connect ( scale, SIGNAL ( valueChanged ( int ) ), SLOT ( setScale ( int ) ) );
	connect ( scroll, SIGNAL ( valueChanged ( int ) ), SIGNAL ( scrollChanged ( int ) ) );
}

//---------------------------------------------------------
//   setPageButtons
//---------------------------------------------------------

void ScrollScale::setPageButtons ( bool flag )
{
	if ( flag == pageButtons )
		return;

	if ( flag )
	{
		if ( up == 0 )
		{
			up = new QToolButton;
			up->setIcon ( QIcon(*upIcon) );
			down = new QToolButton;
			down->setIcon ( QIcon(*downIcon) );
			pageNo = new QLabel;
			QString s;
			s.setNum ( _page+1 );
			pageNo->setText ( s );
                        down->setToolTip(tr ( "next page" ) );
                        up->setToolTip(tr ( "previous page" ) );
                        pageNo->setToolTip(tr ( "current page number" ) );
			box->insertWidget ( 1, up );
			box->insertWidget ( 2, down );
			box->insertSpacing ( 3, 5 );
			box->insertWidget ( 4, pageNo );
			box->insertSpacing ( 5, 5 );
			connect ( up, SIGNAL ( clicked() ), SLOT ( pageUp() ) );
			connect ( down, SIGNAL ( clicked() ), SLOT ( pageDown() ) );
		}
		up->show();
		down->show();
		pageNo->show();
		if ( _page == ( _pages-1 ) )
			down->setEnabled ( false );
		if ( _page == 0 )
			up->setEnabled ( false );
	}
	else
	{
		up->hide();
		down->hide();
	}
	pageButtons = flag;
}

//---------------------------------------------------------
//   showMag
//---------------------------------------------------------

void ScrollScale::showMag ( bool flag )
{
	showMagFlag = flag;
	if ( flag )
		scale->show();
	else
		scale->hide();
	box->activate();
}

//---------------------------------------------------------
//   offset
//---------------------------------------------------------
int ScrollScale::offset()
{
	return pos2offset ( scroll->value() );
}

//---------------------------------------------------------
//   pos2offset
//---------------------------------------------------------
int ScrollScale::pos2offset ( int pos )
{
	if ( scaleVal < 1 )
		return pos * ( -scaleVal ) + scaleVal/2;
	else
		return pos / scaleVal;
}

//---------------------------------------------------------
//   setOffset
//    val in tick
//---------------------------------------------------------

void ScrollScale::setOffset ( int val )
{
	int i = ( scroll->orientation() == Qt::Horizontal ) ? width() : height();
	int pos, max;

	if ( scaleVal < 1 )
	{
		pos = ( val-scaleVal/2 ) / ( -scaleVal );
		max = ( maxVal-scaleVal-1 ) / ( -scaleVal ) - i;
	}
	else
	{
		pos = val * scaleVal;
		max = maxVal * scaleVal - i;
	}
	if ( pos > max )
	{
		int min;
		if ( scaleVal < 1 )
		{
			maxVal  = ( pos + width() ) * ( -scaleVal );
			min = ( minVal-scaleVal/2 ) / ( -scaleVal );
			max     = ( maxVal-scaleVal/2 ) / ( -scaleVal ) - i;
		}
		else
		{
			maxVal  = ( pos + width() + scaleVal/2 ) /scaleVal;
			min = minVal * scaleVal;
			max     = maxVal * scaleVal - i;
		}

		if ( max < 0 )
			max = 0;
		if ( min < 0 )
			min = 0;
		if ( min > max )
			max = min;
		scroll->setRange ( min, max );
	}

	setPos ( pos );
}

//---------------------------------------------------------
//   pageUp
//    goto previous page
//---------------------------------------------------------

void ScrollScale::pageUp()
{
	if ( _page )
	{
		--_page;
		emit newPage ( _page );
		QString s;
		s.setNum ( _page+1 );
		pageNo->setText ( s );
		if ( _page == 0 )
			up->setEnabled ( false );
		if ( _page == ( _pages-2 ) )
			down->setEnabled ( true );
	}
}

//---------------------------------------------------------
//   pageDown
//    goto next page
//---------------------------------------------------------

void ScrollScale::pageDown()
{
	if ( _page + 1 < _pages )
	{
		++_page;
		emit newPage ( _page );
		QString s;
		s.setNum ( _page+1 );
		pageNo->setText ( s );
		if ( _page == ( _pages-1 ) )
			down->setEnabled ( false );
		if ( _page == 1 )
			up->setEnabled ( true );
	}
}

//---------------------------------------------------------
//   setPages
//---------------------------------------------------------

void ScrollScale::setPages ( int n )
{
	_pages = n;
	if ( _page >= _pages )
	{
		_page = _pages-1;
		emit newPage ( _page );
		QString s;
		s.setNum ( _page+1 );
		pageNo->setText ( s );
	}
	up->setEnabled ( _page );
	down->setEnabled ( _page < ( _pages-1 ) );
}

int ScrollScale::pos() const
{
	return scroll->value();
}

int ScrollScale::mag() const
{
	return scale->value();
}

/**
 * Hardcoded hackish function that corresponds to the values used for the scrollscales in PianoRoll and DrumEditor
 * since I couldn't easily create any inverse function from the [0,1024]-range to detect where a zoom actually occurs
 * (mg)
 */
int ScrollScale::getQuickZoomLevel(int mag)
{
      if (mag == 0)
            return 0;

      for (int i=0; i<zoomLevels-1; i++) {
            int val1 = ScrollScale::convertQuickZoomLevelToMag(i);
            int val2 = ScrollScale::convertQuickZoomLevelToMag(i + 1);
            if (mag > val1 && mag <= val2)
                  return i + 1;
            }

     return -1; 

}

/**
 * Function returning the boundary values for a zoom change, hardcoded corresponding to the values used in PianoRoll 
 * and DrumEditor
 */
int ScrollScale::convertQuickZoomLevelToMag(int zoomlevel)
{
      int vals[] = {
            0,   1,   15,  30,  46,  62,  80,  99,  119, 140,
            163, 187, 214, 242, 274, 308, 346, 388, 436, 491,
            555, 631, 726, 849, 1024, 1200, 1300, 1400, 1500, 1600, 1700, 1800,    
            1900, 2100, 2200, 2300, 2400, 2500 };  

      return vals[zoomlevel];
}

} // namespace MusEGui
