/* cyclebutton.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2001-2014 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 "cyclebutton.h"
#include "awindow.h"
#include "guielement.h"
#include "drawablecont.hh"

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

CycleButton::~CycleButton()
{
    options.clear();
}

CycleButton::CycleButton(AGUIX *taguix,int tx,int ty,int width,
                         int tdata):GUIElement(taguix)
{
  int bw;

  bw = 2;

  _x=tx;
  _y=ty;
  if ( width > 0 ) _w = width;
  _h = taguix->getCharHeight() + 2 * bw;
  this->data=tdata;
  this->state=0;
  this->instate=0;
  this->fg = _aguix->getFaces().getColor( "button-fg" );
  this->bg = _aguix->getFaces().getColor( "button-bg" );

  font=NULL;
  act_opt=-1;
  setCanHandleFocus();
  setAcceptFocus( true );
}

CycleButton::CycleButton(AGUIX *taguix,int tx,int ty,int width,int height,
                         int tdata):GUIElement(taguix)
{
  _x=tx;
  _y=ty;
  if ( width > 0 ) _w = width;
  if ( height > 0 ) _h = height;
  this->data=tdata;
  this->state=0;
  this->instate=0;
  this->fg = _aguix->getFaces().getColor( "button-fg" );
  this->bg = _aguix->getFaces().getColor( "button-bg" );

  font=NULL;
  act_opt=-1;
  setCanHandleFocus();
  setAcceptFocus( true );
}

const char *CycleButton::getOption( int index2 )
{
    if ( index2 < 0 || index2 >= (int)options.size() ) return NULL;
    return options[index2].c_str();
}

int CycleButton::addOption(const char *new_text)
{
  if ( new_text == NULL ) return -1;

  options.push_back( new_text );
  if(act_opt==-1) {
    act_opt=0;
    redraw();
  }
  return options.size() - 1;
}

int CycleButton::addOption( const std::string &new_text )
{
    return addOption( new_text.c_str() );
}

int CycleButton::setOption( int pos, const char *new_text )
{
    if ( pos < 0 || pos >= (int)options.size() ) return -1;
  if ( new_text == NULL ) return -1;

  options[pos] = new_text;

  redraw();
  return pos;
}

void CycleButton::setOption(int index2)
{
    if ( index2 >= 0 && index2 < (int)options.size() ) {
    act_opt=index2;
    redraw();
  }
}

void CycleButton::setFG(int tfg)
{
    if ( tfg < _aguix->getNumberOfColorsForType( AGUIXColor::USER_COLOR ) && tfg >= 0 ) {
        this->fg = tfg;
    }
    redraw();
}

int CycleButton::getFG() const
{
  return fg;
}

void CycleButton::setBG(int tbg)
{
    if ( tbg < _aguix->getNumberOfColorsForType( AGUIXColor::USER_COLOR ) && tbg >= 0 ) {
        this->bg = tbg;
    }
    redraw();
}

int CycleButton::getBG() const
{
  return bg;
}

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

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

void CycleButton::redraw()
{
  double circle[11][4]={{7.0/8, 6.0/8, 7.0/8, 7.0/8},
			{7.0/8, 7.0/8, 6.0/8, 8.0/8},
			{6.0/8, 8.0/8, 1.0/8, 8.0/8},
			{1.0/8, 8.0/8, 0.0/8, 7.0/8},
			{0.0/8, 7.0/8, 0.0/8, 1.0/8},
			{0.0/8, 1.0/8, 1.0/8, 0.0/8},
			{1.0/8, 0.0/8, 6.0/8, 0.0/8},
			{6.0/8, 0.0/8, 7.0/8, 1.0/8},
			{7.0/8, 1.0/8, 7.0/8, 4.0/8},
			{7.0/8, 4.0/8, 6.0/8, 3.0/8},
			{7.0/8, 4.0/8, 8.0/8, 3.0/8}};

  double x1,x2,ty1,y2;
  int len, strwidth;
  char *tstr;
  int bw;

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

  prepareBG();
  
  _aguix->ClearWin(win);

  _aguix->drawBorder( win, ( state == 0 ) ? false : true, 0, 0, _h, _h, 0 );
  _aguix->drawBorder( win, ( state == 0 ) ? false : true, _h, 0, _w - _h, _h, 0 );
  
  bw = 2;
  
  _aguix->setFG( _aguix->getFaceCol_default_fg() );
  for ( int i = 0; i < 11; i++ ) {
    x1 = circle[i][0];
    x2 = circle[i][2];
    ty1 = circle[i][1];
    y2 = circle[i][3];
    x1 *= _h - 2 * bw - 1;
    x2 *= _h - 2 * bw - 1;
    ty1 *= _h - 2 * bw - 1;
    y2 *= _h - 2 * bw - 1;
    _aguix->DrawLine( win, (int)x1 + bw, (int)ty1 + bw, (int)x2 + bw, (int)y2 + bw );
  }
  
  int ch;
  if(font==NULL) {
    ch=_aguix->getCharHeight();
  } else {
    ch=font->getCharHeight();
  }
  int dy=_h/2-ch/2;

  if ( act_opt >= 0 ) tstr = dupstring( options[act_opt].c_str() );
  else tstr = dupstring( "" );

  len = _aguix->getStrlen4Width( tstr, _w - 2 - _h - 5, &strwidth, font );

  if ( len > 0 ) {
    int dx = _h + 5;

    tstr[len] = '\0';

    DrawableCont dc( _aguix, win );
    _aguix->DrawText( dc, font, tstr, dx, dy, fg );
  }
  _freesafe(tstr);

  if ( ( getAcceptFocus() == true ) && ( getHasFocus() == true ) ) {
    _aguix->setDottedFG( 1 );
    _aguix->DrawDottedRectangle( win, _h, 1, _w - _h - 1, _h - 2 );
  }

  _aguix->Flush();
}

void CycleButton::flush()
{
}

void CycleButton::setState(int tstate)
{
  this->state=tstate;
  redraw();
}

int CycleButton::getState() const
{
  return state;
}

bool CycleButton::isInside(int px,int py) const
{
  if((px>0)&&(px<=_w)) {
    if((py>0)&&(py<=_h)) return true;
  }
  return false;
}

bool CycleButton::handleMessage(XEvent *E,Message *msg)
{
  bool returnvalue;
  
  if ( isCreated() == false ) return false;

  returnvalue = GUIElement::handleMessage( E, msg );

  AGMessage *agmsg;
  if((msg->type==ButtonPress)||(msg->type==ButtonRelease)) {
    if ( msg->window == win ) {
      takeFocus();

      if(msg->type==ButtonPress) {
	if(msg->window==win) {
	  setState(1);
	  instate=1;
	  returnvalue=true;
	}
      } else {
	if((state!=0)&&(instate!=0)) {
	  if ( options.size() > 0 ) {
	    if(msg->button==Button3) {
	      act_opt--;
	      if ( act_opt < 0 ) act_opt = options.size() - 1;
	    } else if(msg->button==Button1) {
	      act_opt++;
	      if ( act_opt >= (int)options.size() ) act_opt = 0;
	    } else if(msg->button==Button4) {
	      if(act_opt>0) act_opt--;
	    } else if(msg->button==Button5) {
                if ( ( act_opt + 1 ) < (int)options.size() ) act_opt++;
	    }
	  }
	  
	  agmsg = AGUIX_allocAGMessage();
	  agmsg->type=AG_CYCLEBUTTONCLICKED;
	  agmsg->cyclebutton.cyclebutton=this;
	  agmsg->cyclebutton.option=act_opt;
          msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
	}
	if(instate!=0) {
	  setState(0);
	  instate=0;
	  returnvalue=true;
	}
      }
    }
  } else if(msg->type==EnterNotify) {
    // alles hier und alles mit instate wird benutzt, damit Button sich anpaßt, wenn
    // Mauszeiger im Button oder außerhalb des Buttons ist
    if ( msg->window == win ) {
      if(instate!=0) {
	if(state!=instate) {
	  setState(instate);
	}
      }
    }
  } else if(msg->type==LeaveNotify) {
    // alles hier und alles mit instate wird benutzt, damit Button sich anpaßt, wenn
    // Mauszeiger im Button oder außerhalb des Buttons ist
    if ( msg->window == win ) {
      if(instate!=0) {
	setState(0);
      }
    }
  } else if(msg->type==Expose) {
    if ( msg->window == win ) {
      redraw();
    }
  } else if ( msg->type == KeyPress ) {
    if ( ( getAcceptFocus() == true ) && ( getHasFocus() == true ) ) {
      if ( options.size() > 0 ) {
	if ( isVisible() == true ) {
	  if ( _parent->isTopParent( msg->window ) == true ) {
	    switch ( msg->key ) {
	    case XK_space:
	      if ( options.size() > 0 ) {
		act_opt++;
		if ( act_opt >= (int)options.size() ) act_opt = 0;
	      }
	      
	      agmsg = AGUIX_allocAGMessage();
	      agmsg->type=AG_CYCLEBUTTONCLICKED;
	      agmsg->cyclebutton.cyclebutton=this;
	      agmsg->cyclebutton.option=act_opt;
              msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
	      if(instate!=0) {
		setState(0);
		instate=0;
		returnvalue=true;
	      } else redraw();  // setState will make this so just in the else-case
	      break;
	    case XK_Up:
	      if ( act_opt > 0 ) {
		act_opt--;
		agmsg = AGUIX_allocAGMessage();
		agmsg->type = AG_CYCLEBUTTONCLICKED;
		agmsg->cyclebutton.cyclebutton = this;
		agmsg->cyclebutton.option = act_opt;
                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
		redraw();
	      }
	      break;
	    case XK_Down:
                if ( ( act_opt + 1 ) < (int)options.size() ) {
		act_opt++;
		agmsg = AGUIX_allocAGMessage();
		agmsg->type = AG_CYCLEBUTTONCLICKED;
		agmsg->cyclebutton.cyclebutton = this;
		agmsg->cyclebutton.option = act_opt;
                msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
		redraw();
	      }
	      break;
	    }
	  }
	}
      }
    }
  }
  if(returnvalue==true) {
    // jetzt noch die Message mit den Werten füllen
    msg->gadget=this;
    msg->gadgettype=BUTTON_GADGET;
  }
//  return returnvalue;
  return false;  // we return false because an other element can take use of
                 // this message (f.e. StringGagdet for deactivating)
}

int CycleButton::setFont( const char *fontname )
{
  font=_aguix->getFont(fontname);
  if(font==NULL) return -1;
  return 0;
}

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

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

int CycleButton::getMaxSize() const
{
    int maxlen = -1;
    
    for ( std::vector<std::string>::const_iterator it1 = options.begin();
          it1 != options.end();
          it1++ ) {
        const char *tstr = (*it1).c_str();
        
        int len = _aguix->getTextWidth( tstr, font );
        if ( len > maxlen ) maxlen = len;
    }
    
    return _h + 10 + maxlen;
}

int
CycleButton::getSelectedOption() const
{
  return act_opt;
}

void CycleButton::prepareBG( bool force )
{
  if ( isCreated() == false ) return;
  if ( win == 0 ) return;

  _aguix->SetWindowBG( win, bg );
}

int CycleButton::getNrOfOptions() const
{
    return options.size();
}

void CycleButton::removeOption( int o )
{
    if ( o < 0 || o >= getNrOfOptions() ) return;
    if ( getNrOfOptions() < 2 ) return;

    int pos = 0;
    for ( std::vector<std::string>::iterator it1 = options.begin();
          it1 != options.end();
          it1++ ) {
        if ( pos == o ) {
            options.erase( it1 );
            break;
        }
        pos++;
    }

    if ( o == getNrOfOptions() ) {
        // last option
        setOption( o - 1 );
    } else {
        // option in the middle
        setOption( o );
    }
}
