/*  Part of XPCE --- The SWI-Prolog GUI toolkit

    Author:        Jan Wielemaker and Anjo Anjewierden
    E-mail:        jan@swi.psy.uva.nl
    WWW:           http://www.swi.psy.uva.nl/projects/xpce/
    Copyright (c)  1985-2002, University of Amsterdam
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:

    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in
       the documentation and/or other materials provided with the
       distribution.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
    FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
    COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
    BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
    CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
    LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
    ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
*/

#include <h/kernel.h>
#include <h/dialog.h>

static void	compute_slider(Slider, int *, int *, int *, int *,
			       int *, int *, int *, int *, int *);
static status	applySlider(Slider, BoolObj);
static status	restoreSlider(Slider s);
static Type	getTypeSlider(Slider s);
static status	displayedValueSlider(Slider s, Any val);
static status	widthSlider(Slider s, Int val);

#define SLIDER_HEIGHT 20
#define VALUE_GAP 20
#define BAR_WIDTH 5

#define OL_BAR_HEIGHT 5
#define OL_BOX_WIDTH 10

static status
initialiseSlider(Slider s, Name name, Any low, Any high, Any def, Message msg)
{ createDialogItem(s, name);

  assign(s, label_width,   DEFAULT);
  assign(s, show_label,    ON);
  assign(s, show_value,    ON);
  assign(s, low,	   low);
  assign(s, high,	   high);
  assign(s, message,	   msg);
  assign(s, width,	   toInt(200));
  assign(s, drag,	   OFF);
  assign(s, format,	   DEFAULT);

  assign(s, default_value, def);
  if ( !restoreSlider(s) )
  { assign(s, selection, s->low);
    displayedValueSlider(s, s->low);
  }

  return requestComputeGraphical(s, DEFAULT);
}


static double
convert_value(Any val)
{ return isInteger(val) ? (double)valInt(val) : valReal(val);
}


static void
format_value(Slider s, char *buf, Any val)
{ int deffmt = isDefault(s->format);

  if ( isInteger(val) )
    sprintf(buf, deffmt ? "%" PRIdPTR : strName(s->format), valInt(val));
  else
    sprintf(buf, deffmt ? "%g"  : strName(s->format), valReal(val));
}


static status
RedrawAreaSlider(Slider s, Area a)
{ int x, y, w, h;
  int ny, vx, vy, lx, ly, sx, sy, hx, hy;
  int vv;
  int bw = (s->look == NAME_x ? BAR_WIDTH : OL_BOX_WIDTH);
  float lv = convert_value(s->low);
  float hv = convert_value(s->high);
  float dv = convert_value(s->displayed_value);
  int lflags = (s->active == ON ? 0 : LABEL_INACTIVE);

  if ( dv < lv )
    dv = lv;
  else if ( dv > hv )
    dv = hv;

  if ( hv > lv )
    vv = rfloat(((float) (valInt(s->width) - bw) * (dv - lv)) / (hv - lv));
  else
    vv = 0;

  initialiseDeviceGraphical(s, &x, &y, &w, &h);
  NormaliseArea(x, y, w, h);
  r_thickness(valInt(s->pen));
  r_dash(s->texture);

  compute_slider(s, &ny, &vx, &vy, &lx, &ly, &sx, &sy, &hx, &hy);
  r_clear(x, y, w, h);

  if ( s->show_label == ON )
  { int ex = valInt(getExFont(s->label_font));

    RedrawLabelDialogItem(s,
			  accelerator_code(s->accelerator),
			  x, y+ny, vx-ex, 0,
			  s->label_format, NAME_top,
			  lflags);
  }

  if ( s->look == NAME_motif )
  { int by = y+sy+(SLIDER_HEIGHT-OL_BAR_HEIGHT)/2;
    int ex  = x + sx + valInt(s->width);
    Elevation z = getClassVariableValueObject(s, NAME_elevation);

    r_3d_box(x+sx, by, vv, OL_BAR_HEIGHT, 0, z, FALSE);
    r_3d_box(x+sx+vv+bw, by, ex-(x+sx+vv+bw), OL_BAR_HEIGHT, 0, z, FALSE);
    r_3d_box(x+sx+vv, y+sy, bw, SLIDER_HEIGHT, 0, z, TRUE);
  } else if ( s->look == NAME_openLook )
  { int by = y+sy+(SLIDER_HEIGHT-OL_BAR_HEIGHT)/2;
    int ly2 = by+OL_BAR_HEIGHT-1;
    int ex  = x + sx + valInt(s->width);

    r_fill(x+sx, by+1, 1, OL_BAR_HEIGHT-2, BLACK_IMAGE);
    r_fill(x+sx+1, by, vv-2, OL_BAR_HEIGHT, BLACK_IMAGE);
    r_line(x+sx+1+vv+bw, by, ex-2, by);
    r_line(x+sx+1+vv+bw, ly2, ex-2, ly2);
    r_line(ex-1, by+1, ex-1, ly2-1);
    r_shadow_box(x+sx+vv, y+sy, bw, SLIDER_HEIGHT, 0, 1, NIL);
  } else
  { r_fill(x+sx, y+sy, vv, SLIDER_HEIGHT, GREY50_IMAGE);
    r_box(x+sx, y+sy, valInt(s->width), SLIDER_HEIGHT, 0, NIL);
    r_fill(x+sx+vv, y+sy, bw, SLIDER_HEIGHT, BLACK_IMAGE);
  }

  if ( s->show_value == ON )
  { char buf[100];
    string str;

    buf[0] = '[';
    format_value(s, &buf[1], s->displayed_value);
    strcat(buf, "]");
    str_set_ascii(&str, buf);
    str_label(&str, 0, s->value_font,
	      x+vx, y+vy, 0, 0, NAME_left, NAME_top, lflags);
    format_value(s, buf, s->low);
    str_set_ascii(&str, buf);
    str_label(&str, 0, s->value_font,
	      x+lx, y+ly, 0, 0, NAME_left, NAME_top, lflags);
    format_value(s, buf, s->high);
    str_set_ascii(&str, buf);
    str_label(&str, 0, s->value_font,
	      x+hx, y+hy, 0, 0, NAME_left, NAME_top, lflags);
  }

  return RedrawAreaGraphical(s, a);
}


static void
compute_label_slider(Slider s, int *lw, int *lh)
{ if ( s->show_label == ON )
  { if ( isDefault(s->label_font) )
      obtainClassVariablesObject(s);

    dia_label_size(s, lw, lh, NULL);
    *lw += valInt(getExFont(s->label_font));
    if ( notDefault(s->label_width) )
      *lw = max(valInt(s->label_width), *lw);
  } else
  { *lw = *lh = 0;
  }
}


static void
compute_slider(Slider s, int *ny, int *vx, int *vy, int *lx, int *ly, int *sx, int *sy, int *hx, int *hy)
{ int hv = (s->show_value == ON ? valInt(getHeightFont(s->value_font)) : 0);
  int hl, hm;

  compute_label_slider(s, vx, &hl);
  hm = max(hl, max(hv, SLIDER_HEIGHT));

  *ny = (hm - hl) / 2;
  *sy = (hm - SLIDER_HEIGHT) / 2;
  *vy = *ly = *hy = (hm - hv) / 2;

  if ( s->show_value == ON )
  { int shw, slw, sh, cw = 4, tw;
    char buf[100];
    string str;

    buf[0] = '[';
    format_value(s, &buf[1], s->high);
    strcat(buf, "]");
    str_set_ascii(&str, buf);
    str_size(&str, s->value_font, &shw, &sh);
    format_value(s, buf, s->low);
    str_set_ascii(&str, buf);
    str_size(&str, s->value_font, &slw, &sh);
    if ( convert_value(s->low) < 0.0 &&
	 (tw = (c_width('-', s->value_font) + slw)) > shw )
      shw = tw;

    *lx = *vx + shw + cw;
    *sx = *lx + slw + cw;
    *hx = *sx + valInt(s->width) + cw;
  } else
  { *lx = *sx = *vx;
    *hx = *sx + valInt(s->width);
  }
}


static status
computeSlider(Slider s)
{ if ( notNil(s->request_compute) )
  { int ny, vx, vy, lx, ly, sx, sy, hx, hy;
    int w, h;
    int sw, sh;

    obtainClassVariablesObject(s);
    compute_slider(s, &ny, &vx, &vy, &lx, &ly, &sx, &sy, &hx, &hy);
    h = SLIDER_HEIGHT;
    h = max(h, ly+valInt(getHeightFont(s->label_font)));
    h = max(h, vy+valInt(getHeightFont(s->value_font)));

    if ( s->show_value == ON )
    { char buf[100];
      string str;

      sprintf(buf, "%" PRIdPTR, valInt(s->high));
      str_set_ascii(&str, buf);
      str_size(&str, s->value_font, &sw, &sh);
      w = hx + sw;
    } else
      w = hx;

    CHANGING_GRAPHICAL(s,
	  assign(s->area, w, toInt(w));
	  assign(s->area, h, toInt(h)));

    assign(s, request_compute, NIL);
  }

  succeed;
}


static Point
getReferenceSlider(Slider s)
{ Point ref;

  if ( !(ref = getReferenceDialogItem(s)) )
  { int ny, vx, vy, lx, ly, sx, sy, hx, hy;
    int ascent;

    ComputeGraphical(s);
    compute_slider(s, &ny, &vx, &vy, &lx, &ly, &sx, &sy, &hx, &hy);
    ascent = valInt(getAscentFont(s->label_font));

    ref = answerObject(ClassPoint, ZERO, toInt(ascent + ny), EAV);
  }

  answer(ref);
}


static Int
getLabelWidthSlider(Slider s)
{ int lw, lh;

  compute_label_slider(s, &lw, &lh);
  answer(toInt(lw));
}


static status
labelWidthSlider(Slider s, Int w)
{ if ( s->show_label == ON && s->label_width != w )
  { assign(s, label_width, w);
    CHANGING_GRAPHICAL(s,
	requestComputeGraphical(s, DEFAULT));
  }

  succeed;
}

static status
geometrySlider(Slider s, Int x, Int y, Int w, Int h)
{ if ( notDefault(w) )
  { int extra;
    int width;

    ComputeGraphical(s);
    extra = valInt(s->area->w) - valInt(s->width);
    width = valInt(w) - extra;

    if ( width < 20 )
    { w = toInt(valInt(w) + 20 - width);
      width = 20;
    }

    widthSlider(s, toInt(width));
  }

  return geometryGraphical(s, x, y, w, DEFAULT);
}



		/********************************
		*        EVENT HANDLING		*
		********************************/

static status
displayedValueSlider(Slider s, Any val)
{ if ( s->displayed_value != val )
  { assign(s, displayed_value, val);

    changedDialogItem(s);
  }

  succeed;
}


static status
eventSlider(Slider s, EventObj ev)
{ if ( eventDialogItem(s, ev) )
    succeed;

  if ( s->active == OFF )
    fail;

  if ( isAEvent(ev, NAME_msLeftDown) )
    return send(s, NAME_focus, EAV);

  if ( isAEvent(ev, NAME_msLeft) &&
       hasModifierEvent(ev, findGlobal(NAME_ModifierAllUp)) )
  { int ny, vx, vy, lx, ly, sx, sy, hx, hy;
    int se;
    int ex;
    Int X, Y;

    get_xy_event(ev, s, ON, &X, &Y);
    ex = valInt(X);
    compute_slider(s, &ny, &vx, &vy, &lx, &ly, &sx, &sy, &hx, &hy);
    se = sx + valInt(s->width);

/*  if ( ex > sx - 10 && ex < se + 10 ) */
    { Any val;

      if ( ex < sx ) ex = sx;
      if ( ex > se ) ex = se;

      if ( isInteger(s->low) && isInteger(s->high) )
      { val = toInt(((ex - sx) * (valInt(s->high) - valInt(s->low)) /
		     (se - sx)) + valInt(s->low));
      } else
      { float l = convert_value(s->low);
	float h = convert_value(s->high);

	val = CtoReal(((float)(ex - sx) * (h - l) / (float) (se - sx)) + l);
      }

      send(s, NAME_displayedValue, val, EAV);
      if ( isUpEvent(ev) && !send(s->device, NAME_modifiedItem, s, ON, EAV) )
	applySlider(s, ON);		/* TBD: or ->modified_item! */
      else if ( s->drag == ON && instanceOfObject(s->message, ClassCode) )
	forwardReceiverCode(s->message, s, s->displayed_value, EAV);
    }
  } else if ( isAEvent(ev, NAME_areaCancel) )
    sendv(s, NAME_displayedValue, 1, (Any *) &s->selection);

  fail;
}


		/********************************
		*          ATTRIBUTES		*
		********************************/


static status
valueFontSlider(Slider s, FontObj font)
{ return assignGraphical(s, NAME_valueFont, font);
}


static status
showLabelSlider(Slider s, BoolObj val)
{ return assignGraphical(s, NAME_showLabel, val);
}


static status
showValueSlider(Slider s, BoolObj val)
{ return assignGraphical(s, NAME_showValue, val);
}


static status
formatSlider(Slider s, Name val)
{ return assignGraphical(s, NAME_format, val);
}


static status
lowSlider(Slider s, Any val)
{ return assignGraphical(s, NAME_low, val);
}


static status
selectionSlider(Slider s, Any val)
{ Any v;
  Type t = getTypeSlider(s);

  if ( (v = checkType(val, t, s)) )
  { assign(s, selection, val);
    displayedValueSlider(s, val);

    succeed;
  }

  return errorPce(t, NAME_unexpectedType, val);
}


static status
highSlider(Slider s, Any val)
{ return assignGraphical(s, NAME_high, val);
}


static status
widthSlider(Slider s, Int val)
{ return assignGraphical(s, NAME_width, val);
}


static Int
getWidthSlider(Slider s)
{ answer(s->width);
}


		/********************************
		*         COMMUNICATION		*
		********************************/

static Any
getSelectionSlider(Slider s)
{ assign(s, selection, s->displayed_value);

  answer(s->selection);
}


static BoolObj
getModifiedSlider(Slider s)
{ answer(s->selection == s->displayed_value ? OFF : ON);
}


static status
modifiedSlider(Slider s, BoolObj val)
{ if ( val == OFF )
    displayedValueSlider(s, s->selection);

  succeed;
}


static Type
getTypeSlider(Slider s)
{ if ( isInteger(s->low) && isInteger(s->high) )
    answer(TypeInt);

  answer(TypeReal);
}


static Any
getDefaultSlider(Slider s)
{ answer(checkType(s->default_value, getTypeSlider(s), s));
}


static status
defaultSlider(Slider s, Any val)
{ if ( s->default_value != val )
  { assign(s, default_value, val);

    return restoreSlider(s);
  }

  succeed;
}


static status
restoreSlider(Slider s)
{ Any val;

  if ( (val = getDefaultSlider(s)) )
    return selectionSlider(s, val);

  fail;
}


static status
applySlider(Slider s, BoolObj always)
{ Any val;

  if ( instanceOfObject(s->message, ClassCode) &&
       (always == ON || getModifiedSlider(s) == ON) &&
       (val = getSelectionSlider(s)) )
  { forwardReceiverCode(s->message, s, val, EAV);
    succeed;
  }

  fail;
}


		 /*******************************
		 *	 CLASS DECLARATION	*
		 *******************************/

/* Type declarations */

static char *T_initialise[] =
        { "name", "low=int|real", "high=int|real", "value=int|real|function", "message=[code]*" };
static char *T_geometry[] =
        { "x=[int]", "y=[int]", "width=[int]", "height=[int]" };

/* Instance Variables */

static vardecl var_slider[] =
{ SV(NAME_selection, "int|real", IV_GET|IV_STORE, selectionSlider,
     NAME_selection, "Current selection"),
  IV(NAME_default, "int|real|function", IV_NONE,
     NAME_apply, "The default selection or function to get it"),
  SV(NAME_displayedValue, "int|real", IV_GET|IV_STORE, displayedValueSlider,
     NAME_selection, "Currently displayed value"),
  SV(NAME_valueFont, "font", IV_GET|IV_STORE, valueFontSlider,
     NAME_appearance, "Font for current selection"),
  SV(NAME_showLabel, "bool", IV_GET|IV_STORE, showLabelSlider,
     NAME_appearance, "Whether label is shown"),
  SV(NAME_showValue, "bool", IV_GET|IV_STORE, showValueSlider,
     NAME_appearance, "Whether selection is shown"),
  SV(NAME_format, "[name]", IV_GET|IV_STORE, formatSlider,
     NAME_appearance, "Format for the printed values"),
  SV(NAME_low, "int|real", IV_GET|IV_STORE, lowSlider,
     NAME_selection, "Minimum of range"),
  SV(NAME_high, "int|real", IV_GET|IV_STORE, highSlider,
     NAME_selection, "Maximum of range"),
  SV(NAME_width, "int", IV_NONE|IV_STORE, widthSlider,
     NAME_area, "Length of the bar"),
  IV(NAME_drag, "bool", IV_BOTH,
     NAME_event, "Send messages while dragging")
};

/* Send Methods */

static senddecl send_slider[] =
{ SM(NAME_compute, 0, NULL, computeSlider,
     DEFAULT, "Compute desired size"),
  SM(NAME_event, 1, "event", eventSlider,
     DEFAULT, "Process an event"),
  SM(NAME_geometry, 4, T_geometry, geometrySlider,
     DEFAULT, "Adjust <-width"),
  SM(NAME_initialise, 5, T_initialise, initialiseSlider,
     DEFAULT, "Create from label, low, high, default and message"),
  SM(NAME_apply, 1, "always=[bool]", applySlider,
     NAME_apply, "->execute if <-modified or @on"),
  SM(NAME_default, 1, "value=int|real|function", defaultSlider,
     NAME_apply, "Set variable -default and ->selection"),
  SM(NAME_modified, 1, "bool", modifiedSlider,
     NAME_apply, "Reset modified flag"),
  SM(NAME_restore, 0, NULL, restoreSlider,
     NAME_apply, "Set ->selection to <-default"),
  SM(NAME_labelWidth, 1, "pixels=[int]", labelWidthSlider,
     NAME_layout, "Set width of label in pixels")
};

/* Get Methods */

static getdecl get_slider[] =
{ GM(NAME_reference, 0, "point", NULL, getReferenceSlider,
     DEFAULT, "Baseline of label"),
  GM(NAME_default, 0, "int|real", NULL, getDefaultSlider,
     NAME_apply, "Current default value"),
  GM(NAME_modified, 0, "bool", NULL, getModifiedSlider,
     NAME_apply, "If @on, slider has been modified"),
  GM(NAME_width, 0, "int", NULL, getWidthSlider,
     NAME_area, "Get width of bar"),
  GM(NAME_labelWidth, 0, "int", NULL, getLabelWidthSlider,
     NAME_layout, "Get minimal width required for label"),
  GM(NAME_selection, 0, "int|real", NULL, getSelectionSlider,
     NAME_selection, "Current value of the selection")
};

/* Resources */

static classvardecl rc_slider[] =
{ RC(NAME_look, RC_REFINE, "when(@colour_display, motif, open_look)", NULL)

};

/* Class Declaration */

static Name slider_termnames[] = { NAME_label, NAME_low, NAME_high, NAME_selection, NAME_message };

ClassDecl(slider_decls,
          var_slider, send_slider, get_slider, rc_slider,
          5, slider_termnames,
          "$Rev$");

status
makeClassSlider(Class class)
{ declareClass(class, &slider_decls);
  setRedrawFunctionClass(class, RedrawAreaSlider);

  succeed;
}

