/* slider.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2005-2019 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * 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 St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "slider.h"
#include <stdlib.h>
#include "awindow.h"
#include "guielement.h"

const char *Slider::type = "Slider";

Slider::Slider( AGUIX *caguix, int cx, int cy, int cwidth, int cheight, bool cvertical, int cdata ) : GUIElement( caguix )
{
  max_len = 0;
  offset = 0;
  maxdisplay = 0;
  vertical = cvertical;

  old_offset = -1;

  this->data = cdata;
  _x = cx;
  _y = cy;
  if ( cwidth > 6 )
    _w = cwidth;
  else
    _w = 6;
  if ( cheight > 6 )
    _h = cheight;
  else
    _h = 6;
  bar_dir = 0;
  bar_pressed = false;

  displayFocus = false;
  setCanHandleFocus();
  setAcceptFocus( true );

  barMode = BAR_IDLE;
  grabs = 0;
  scrollDelta = 0;
  
  _init_timer_wait = 0;
  setAllowTimerEventBreak( true );

  m_bg = -1;

  m_show_arrow_buttons = true;
}

Slider::~Slider()
{
}

void Slider::resize( int tw, int th )
{
  if ( ( tw < 6 ) || ( th < 6 ) ) return;
  _w = tw;
  _h = th;
  if ( isCreated() == true ) {
    _parent->resizeSubWin( win, tw, th );
    redraw();
  }
}

int Slider::getData() const
{
  return data;
}

void Slider::setData( int tdata )
{
  this->data = tdata;
}

void Slider::flush()
{
}

void Slider::handleExpose( Window msgwin, int ex, int ey, int ew, int eh )
{
  if ( isCreated() == false ) return;
  if ( msgwin == win ) {
    redraw();
  }
}

bool Slider::handleMessage( XEvent *E, Message *msg )
{
  bool returnvalue;

  if ( isCreated() == false ) return false;

  returnvalue = false;
  if ( msg->type == Expose ) {
    handleExpose( msg->window,
		  msg->x,
		  msg->y,
		  msg->width,
		  msg->height );
  }
  if ( barMode != BAR_IDLE ) handleBar( msg );
  else {
    if ( msg->type == ButtonPress ) {
      // jetzt muessen wir was machen
      bool isc = false;
      if ( msg->window == win ) {
	takeFocus();
	handleBar( msg );
	isc = true;
      }
      if ( isc == true ) {
	AGMessage *agmsg = AGUIX_allocAGMessage();
	agmsg->slider.slider = this;
	agmsg->slider.offset = offset;
	agmsg->slider.time = msg->time;
	agmsg->slider.mouse = true;
	agmsg->type = AG_SLIDER_PRESSED;
        msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
      }
    } else if ( msg->type == KeyPress ) {
      if ( ( getAcceptFocus() == true ) && ( getHasFocus() == true ) ) {
	if ( isVisible() == true ) {
	  if ( _parent->isTopParent( msg->window ) == true ) {
	    // we have the focus so let's react to some keys
	    // lets call an extra handler so it can be overwritten
	    returnvalue = handleKeys( msg );
	  }
	}
      }
    }
  }
  if ( msg->lockElement == this ) msg->ack = true;
  return returnvalue;
}

void Slider::redraw()
{
  if ( isCreated() == false ) return;
  int tx, ty, tw, th;
  int maxlen = getMaxLen();
  struct point { int x, y; };
  struct point p[3];
  XPoint arrow1[3], arrow2[3];
  int bw, bgbw;
  AGUIXColor bg_to_use( 0 );

  bgbw = 1;

  tw = _w - ( ( displayFocus == true ) ? 2 * bgbw: 0 );
  th = _h - ( ( displayFocus == true ) ? 2 * bgbw: 0 );
  ty = ( displayFocus == true ) ? bgbw : 0;
  tx = ( displayFocus == true ) ? bgbw : 0;

  bw = 2;

  if ( m_bg < 0 ) {
      bg_to_use = _aguix->getFaces().getAGUIXColor( "slider-bg" );
  } else {
      bg_to_use = AGUIXColor( m_bg );
  }
  _aguix->SetWindowBG( win, bg_to_use );
  _aguix->ClearWin( win );

  // draw background
  if ( displayFocus == true ) {
    if ( getHasFocus() == true ) {
      _aguix->setFG( _aguix->getFaceCol_3d_dark() );
    } else {
      _aguix->setFG( _aguix->getFaceCol_3d_bright() );
    }
    p[0].x = 0;
    p[0].y = _h - 1;
    p[1].x = 0;
    p[1].y = 0;
    p[2].x = _w - 1;
    p[2].y = 0;
    _aguix->DrawLine( win, p[0].x, p[0].y, p[1].x, p[1].y );
    _aguix->DrawLine( win, p[1].x, p[1].y, p[2].x, p[2].y );
    
    if ( getHasFocus() == true ) {
      _aguix->setFG( _aguix->getFaceCol_3d_bright() );
    } else {
      _aguix->setFG( _aguix->getFaceCol_3d_dark() );
    }
    p[0].x = 1;
    p[0].y = _h - 1;
    p[1].x = _w - 1;
    p[1].y = _h - 1;
    p[2].x = _w - 1;
    p[2].y = 1;
    _aguix->DrawLine( win, p[0].x, p[0].y, p[1].x, p[1].y );
    _aguix->DrawLine( win, p[1].x, p[1].y, p[2].x, p[2].y );
  }

  if ( vertical == true ) {
    int dw = ( tw - 1 ) & ~1;
    dw = ( dw - 2 * bw ) / 2;
    
    arrow1[0].x = tx + ( tw - 1 ) / 2 - dw;
    arrow1[0].y = ty + tw - bw - 1;
    arrow1[1].x = tx + ( tw - 1 ) / 2;
    arrow1[1].y = ty + bw;
    arrow1[2].x = tx + ( tw - 1 )  / 2 + dw;
    arrow1[2].y = ty + tw - bw - 1;
    
    arrow2[0].x = tx + ( tw - 1 ) / 2 - dw;
    arrow2[0].y = ty + th - tw + bw;
    arrow2[1].x = tx + ( tw - 1 ) / 2;
    arrow2[1].y = ty + th - bw - 1;
    arrow2[2].x = tx + ( tw - 1 )  / 2 + dw;
    arrow2[2].y = ty + th - tw + bw;
  } else {
    int dh = ( th - 1 ) & ~1;
    dh = ( dh - 2 * bw ) / 2;
    
    arrow1[0].x = tx + th - bw - 1;
    arrow1[0].y = ty + ( th - 1 ) / 2 - dh;
    arrow1[1].x = tx + bw;
    arrow1[1].y = ty + ( th - 1 )  / 2;
    arrow1[2].x = tx + th - bw - 1;
    arrow1[2].y = ty + ( th - 1 ) / 2 + dh;

    arrow2[0].x = tx + tw - th + bw;
    arrow2[0].y = ty + ( th - 1 ) / 2 - dh;
    arrow2[1].x = tx + tw - bw - 1;
    arrow2[1].y = ty + ( th - 1 )  / 2;
    arrow2[2].x = tx + tw - th + bw;
    arrow2[2].y = ty + ( th - 1 ) / 2 + dh;
  }

  if ( m_show_arrow_buttons ) {
      // left/top arrow box
      _aguix->drawBorder( win, ( bar_dir == 1 ) ? true : false, tx, ty,  ( vertical == true ) ? tw : th, ( vertical == true ) ? tw : th, 0 );

      // Left/Up-Arrow
      _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-arrow" ) );
      _aguix->DrawTriangleFilled( win,
                                  arrow1[0].x, arrow1[0].y,
                                  arrow1[1].x, arrow1[1].y,
                                  arrow1[2].x, arrow1[2].y );
 }

  // middle box

  // dark grey
  _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-bg2" ) );
  if ( m_show_arrow_buttons ) {
      _aguix->FillRectangle( win, ( vertical == true ) ? tx : tx + th, ( vertical == true ) ? ty + tw : ty,
                             ( vertical == true ) ? tw : tw -  2 * th, ( vertical == true ) ? th -  2 * tw : th );
  } else {
      _aguix->FillRectangle( win, tx, ty,
                             tw, th );
  }

  _aguix->setFG( _aguix->getFaceCol_3d_dark() );
  if ( m_show_arrow_buttons ) {
      p[0].x = ( vertical == true ) ? tx + tw - 1: tx + th;
      p[0].y = ( vertical == true ) ? ty + tw : ty + th - 1;
      p[1].x = ( vertical == true ) ? tx + tw - 1: tx + tw - th - 1;
      p[1].y = ( vertical == true ) ? ty + th - tw - 1: ty + th - 1;
      _aguix->DrawLine( win, p[0].x, p[0].y, p[1].x, p[1].y );
  } else {
      p[0].x = tx + 1;
      p[0].y = ty + th - 1;
      p[1].x = tx + tw - 1;
      p[1].y = ty + th - 1;
      p[2].x = tx + tw - 1;
      p[2].y = ty;

      _aguix->DrawLine( win, p[0].x, p[0].y, p[1].x, p[1].y );
      _aguix->DrawLine( win, p[1].x, p[1].y, p[2].x, p[2].y );
  }
  _aguix->setFG( _aguix->getFaceCol_3d_bright() );
  if ( m_show_arrow_buttons ) {
      p[0].x = ( vertical == true ) ? tx : tx + th;
      p[0].y = ( vertical == true ) ? ty + tw : ty;
      p[1].x = ( vertical == true ) ? tx : tx + tw - th - 1;
      p[1].y = ( vertical == true ) ? ty + th - tw - 1 : ty;
      _aguix->DrawLine( win, p[0].x, p[0].y, p[1].x, p[1].y );
  } else {
      p[0].x = tx;
      p[0].y = ty + th - 1;
      p[1].x = tx;
      p[1].y = ty;
      p[2].x = tx + tw - 2;
      p[2].y = ty;

      _aguix->DrawLine( win, p[0].x, p[0].y, p[1].x, p[1].y );
      _aguix->DrawLine( win, p[1].x, p[1].y, p[2].x, p[2].y );
  }

  if ( m_show_arrow_buttons ) {
      // right/bottom arrow box
      p[0].x = ( vertical == true ) ? tx : tx + tw - th;
      p[0].y = ( vertical == true ) ? ty + th - tw : ty;
      _aguix->drawBorder( win, ( bar_dir == 2 ) ? true : false, p[0].x, p[0].y, ( vertical == true ) ? tw : th, ( vertical == true ) ? tw : th, 0 );

      // Right-Arrow
      _aguix->setFG( _aguix->getFaces().getAGUIXColor( "slider-arrow" ) );
      _aguix->DrawTriangleFilled( win,
                                  arrow2[0].x, arrow2[0].y,
                                  arrow2[1].x, arrow2[1].y,
                                  arrow2[2].x, arrow2[2].y );
  }

  // jetzt noch die eigentliche Leiste
  if ( vertical == true ) {
    tx++;

    if ( m_show_arrow_buttons ) {
        ty += tw;
        th -= 2 * tw;  // Hoehe wenn alles anzeigbar ist
    }

    tw -= 2;
  } else {
    ty++;

    if ( m_show_arrow_buttons ) {
        tx += th;
        tw -= 2 * th;  // Breite wenn alles anzeigbar ist
    }

    th -= 2;
  }
  if ( maxlen > maxdisplay ) {
    int d = ( ( vertical == true ) ? th : tw ) * maxdisplay;
    d /= maxlen;

    if ( d < 10 ) d = 10;

    int a = maxlen - maxdisplay;
    double b = ( ( vertical == true ) ? th : tw ) - d;  // das was uebrig bleibt
    b /= a;            // verteilt sich auch die nichtsichtbaren Zeichen
    if ( vertical == true ) {
      ty += (int)( offset * b );
      th = d;
    } else {
      tx += (int)( offset * b );
      tw = d;
    }
  }

  _aguix->setFG( bg_to_use );
  _aguix->FillRectangle( win, tx, ty,
			 tw, th );

  _aguix->drawBorder( win, bar_pressed, tx, ty, tw, th, 0 );
}

int Slider::getOffset() const
{
  return offset;
}

void Slider::setOffset( int nv )
{
  offset = nv;
  checkValues();
  redraw();
}

int Slider::getMaxLen() const
{
  return max_len;
}

void Slider::setMaxLen( int nv )
{
  max_len = nv;
  checkValues();
  redraw();
}

int Slider::getMaxDisplay() const
{
  return maxdisplay;
}

void Slider::setMaxDisplay( int nv )
{
  maxdisplay = nv;
  checkValues();
  redraw();
}

void Slider::handleBar( Message *msg )
{
  int mx, my;
  int dir;
  int hx, hy, hw, hh;
  int tx, ty, tw, th;
  double b = 1.0;
  int bgbw;

  if ( isCreated() == false ) return;

  int maxlen = getMaxLen();

  mx = msg->mousex;
  my = msg->mousey;

  bgbw = 1;

  hw = _w - ( ( displayFocus == true ) ? 2 * bgbw : 0 );
  hh = _h - ( ( displayFocus == true ) ? 2 * bgbw : 0 );
  hx = ( displayFocus == true ) ? bgbw : 0;
  hy = ( displayFocus == true ) ? bgbw : 0;

  tw = hw;
  th = hh;
  tx = hx;
  ty = hy;

  if ( vertical == true ) {
      tx++;
      if ( m_show_arrow_buttons ) {
          ty += tw;
          th -= 2 * tw;  // Hoehe wenn alles anzeigbar ist
      }
      tw -= 2;
  } else {
      ty++;
      if ( m_show_arrow_buttons ) {
          tx += th;
          tw -= 2 * th;  // Breite wenn alles anzeigbar ist
      }
      th -= 2;
  }

  if ( maxlen > maxdisplay ) {
    int d = ( ( vertical == true ) ? th : tw ) * maxdisplay;
    d /= maxlen;

    if ( d < 10 ) d = 10;

    int a = maxlen - maxdisplay;
    b = ( ( vertical == true ) ? th : tw ) - d;  // das was uebrig bleibt
    b /= a;            // verteilt sich auch die nichtsichtbaren Zeichen
    if ( vertical == true ) {
      ty += (int)( offset * b );
      th = d;
    } else {
      tx += (int)( offset * b );
      tw = d;
    }
  }
  
  if ( msg->type == ButtonPress ) {
    if ( barMode != BAR_IDLE ) return;
    if ( msg->window == win ) {
      dir = 0;

      if ( m_show_arrow_buttons ) {
          if ( msg->button == Button1 ) {
              if ( ( ( vertical == false ) && ( ( mx > hx ) && ( mx <= ( hx + hh ) ) ) ) ||
                   ( ( vertical == true ) && ( ( my > hy ) && ( my <= ( hy + hw ) ) ) ) ) {
                  // left
                  dir = -1;
                  bar_dir = 1;
                  barMode = BAR_SCROLL_LEFT;
              }
              if ( ( ( vertical == false ) && ( ( mx > ( hx + hw - hh ) ) && ( mx <= ( hx + hw ) ) ) ) ||
                   ( ( vertical == true ) && ( ( my > ( hy + hh - hw ) ) && ( my <= ( hy + hh ) ) ) ) ) {
                  // right
                  dir = 1;
                  bar_dir = 2;
                  barMode = BAR_SCROLL_RIGHT;
              }
          }
      }
      
      if ( dir != 0 ) {
	setOffset( offset + dir );
	_aguix->Flush();
	_aguix->msgLock( this );
	enableTimer();
      } else if ( msg->button == Button4 ) {
	setOffset( offset - a_max( 1, ( maxdisplay - 1 ) / 4 ) );
	_aguix->Flush();
      } else if ( msg->button == Button5 ) {
	setOffset( offset + a_max( 1, ( maxdisplay - 1 ) / 4 ) );
	_aguix->Flush();
      } else if ( ( msg->button == Button1 ) &&
		  ( ( ( vertical == false ) && ( mx >= tx ) && ( mx <= ( tx + tw ) ) )
		    ||
		    ( ( vertical == true ) && ( my >= ty ) && ( my <= ( ty + th ) ) )
		  )
		) {
	// the bar-scroller is pressed
	if ( maxlen > maxdisplay ) {
	  grabs = XGrabPointer( _aguix->getDisplay(),
				win,
				False,
				Button1MotionMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask,
				GrabModeAsync,
				GrabModeAsync,
				None,
				None,
				CurrentTime );
	  _aguix->setCursor( win, ( vertical == true ) ? AGUIX::SCROLLV_CURSOR : AGUIX::SCROLLH_CURSOR );
	  scrollDelta = ( vertical == true ) ? my - ty : mx - tx;
	  bar_pressed = true;
	  redraw();
	  _aguix->Flush();
	  barMode = BAR_SCROLL;
	  _aguix->msgLock( this );
	}
      } else {
	if ( msg->button == Button1 ) {
	  if ( ( ( vertical == false ) && ( mx < tx ) ) ||
	       ( ( vertical == true ) && ( my < ty ) ) ) {
	    setOffset( offset - a_max( 1, maxdisplay - 1 ) );
	  } else {
	    setOffset( offset + a_max( 1, maxdisplay - 1 ) );
	  }
	  _aguix->Flush();
	}
      }
    }
  } else if ( msg->type == MotionNotify ) {
    if ( ( msg->window == win ) &&
	 ( barMode == BAR_SCROLL ) ) {
      int tmx;
      double f1;

      _aguix->queryPointer( win, &mx, &my );
      tmx = ( vertical == true ) ? my : mx;

      if ( m_show_arrow_buttons ) {
          tmx -= ( vertical == true ) ? hy + hw : hx + hh;
      } else {
          tmx -= ( vertical == true ) ? hy : hx;
      }

      tmx -= scrollDelta;
      f1 = tmx / b;
      setOffset( (int)f1 );
      _aguix->Flush();
    }
  } else if ( msg->type == ButtonRelease ) {
    if ( ( msg->window == win ) &&
	 ( msg->button == Button1 ) ) {
      switch ( barMode ) {
	case BAR_SCROLL_LEFT:
	case BAR_SCROLL_RIGHT:
	  bar_dir = 0;
	  redraw();
	  _aguix->Flush();
	  _aguix->disableTimer();
	  _aguix->msgUnlock( this );
	  break;
	case BAR_SCROLL:
	  _aguix->unsetCursor( win );
	  if ( grabs == GrabSuccess ) XUngrabPointer( _aguix->getDisplay(), CurrentTime );
	  bar_pressed=false;
	  redraw();
	  _aguix->Flush();
	  _aguix->msgUnlock( this );
	  break;
	default:
	  break;
      }
      barMode = BAR_IDLE;
    }
  } else if ( msg->type == ClientMessage ) {
    if ( ( msg->specialType == Message::TIMEREVENT ) &&
	 ( barMode != BAR_IDLE ) ) {
      if ( ( msg->time % 2 ) == 0 ) { //HARDCODED
	if ( _init_timer_wait < 1 ) {
	  switch ( barMode ) {
	    case BAR_SCROLL_LEFT:
	      setOffset( offset - 1 );
	      break;
	    case BAR_SCROLL_RIGHT:
	      setOffset( offset + 1 );
	      break;
	    default:
	      break;
	  }
	  _aguix->Flush();
	} else {
	  _init_timer_wait--;
	}
      }
    }
  }
  msgAndCallback();
  return;
}

const char *Slider::getType() const
{
  return type;
}

bool Slider::isType(const char *qtype) const
{
  if(strcmp(type,qtype)==0) return true;
  return false;
}

bool Slider::isParent(Window child) const
{
  if ( isCreated() == true ) {
    if ( child == win ) return true;
  }
  return false;
}

bool Slider::handleKeys(Message *msg)
{
  bool returnvalue=false;

  if ( isCreated() == false ) return false;
  if ( msg != NULL ) {
    if ( msg->type == KeyPress ) {
      if ( ( msg->key == XK_Up ) ||
	   ( msg->key == XK_Down ) ||
	   ( msg->key == XK_Left ) ||
	   ( msg->key == XK_Right ) ||
	   ( msg->key == XK_Home ) ||
	   ( msg->key == XK_End ) ||
	   ( msg->key == XK_Prior ) ||
	   ( msg->key == XK_Next ) ) {
	int noffset = offset;

	switch ( msg->key ) {
	  case XK_Up:
	  case XK_Left:
	    noffset--;
	    break;
	  case XK_Down:
	  case XK_Right:
	    noffset++;
	    break;
	  case XK_Home:
	    noffset = 0;
	    break;
	  case XK_End:
	    noffset = max_len - 1;
	    break;
	  case XK_Prior:
	    noffset -= a_max( 1, maxdisplay - 1 );
	    break;
	  case XK_Next:
	    noffset += a_max( 1, maxdisplay - 1 );
	    break;
	  default:
	    break;
	}
	setOffset( noffset );
	msgAndCallback();
      }
    }
  }
  return returnvalue;
}

void Slider::setDisplayFocus( bool nv )
{
  displayFocus = nv;
  redraw();
}

bool Slider::getDisplayFocus() const
{
  return displayFocus;
}

void Slider::checkValues()
{
  if ( max_len < 0 ) max_len = 0;
  if ( maxdisplay < 0 ) maxdisplay = 0;

  if ( ( offset + maxdisplay ) >= max_len ) offset = max_len - maxdisplay;
  if ( offset < 0 ) offset = 0;
}

void Slider::msgAndCallback()
{
  if ( offset != old_offset ) {
    AGMessage *agmsg = AGUIX_allocAGMessage();
    agmsg->slider.slider = this;
    agmsg->slider.offset = offset;
    agmsg->slider.time = time( NULL );
    agmsg->slider.mouse = false;
    agmsg->type = AG_SLIDER_CHANGED;
    msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
    
    callback( offset );
    
    old_offset = offset;
  }
}

void Slider::enableTimer()
{
  _aguix->enableTimer();
  _init_timer_wait = 3;
}

int Slider::getBG() const
{
    return m_bg;
}

void Slider::setBG( int v )
{
    m_bg = v;
    
    if ( m_bg < 0 )
        m_bg = -1;

    redraw();
}

void Slider::setShowArrowButtons( bool nv )
{
    m_show_arrow_buttons = nv;

    redraw();
}

bool Slider::getShowArrowButtons() const
{
    return m_show_arrow_buttons;
}
