############################################################################
#
#	File:     vscroll.icn
#
#	Subject:  Procedures for scrollbars
#
#	Authors:  Jon Lipp and Gregg M. Townsend
#
#	Date:     April 1, 1997
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  Vidgets defined in this file:
#	Varrow
#	Vvthumb
#	Vhthumb
#	Vscrollbar_frame
#
#  Utility procedures in this file:
#	Vvert_scrollbar()
#	Vhoriz_scrollbar()
#	reformat_Vhthumb()
#	reformat_Vvthumb()
#	Vreformat_vscrollbar()
#	Vreformat_hscrollbar()
#	VReformat()
#
############################################################################
#
#  Includes: vdefns.icn
#
############################################################################
#
#  Links:  vidgets
#
############################################################################

link vidgets

$include "vdefns.icn"

############################################################################
# Varrow
############################################################################

record Varrow_rec(win, callback, aw, ah, rev, dir, incop, id, ax, ay, r,
   uid, P, V)

procedure Varrow(params[])
local frame, x, y, ins, self, init_proc

   init_proc := init_Varrow
   if ins := Vinsert_check(params) then {
      frame := pop(params); x := pop(params); y:= pop(params)
      }
   self := Varrow_rec ! params[1:7|0]
   self.r := self.aw / 2
   self.uid := Vget_uid()
   self.V := Vstd(event_Varrow, draw_Varrow, 1,
                  resize_Vidget, inrange_Vpane, init_proc,
                  couplerset_Vpane)
   self.P := Vstd_pos()
   self.V.init(self)
   if \ins then VInsert(frame, self, x, y)
   return self
end

procedure event_Varrow(s,e)
local c, prev, new
static delay

   initial delay := proc("delay", 0)	# protect attractive name

   if \s.callback.locked then fail
   if e === (&lpress|&mpress|&rpress) then {
      FillTriangle(s.win, s.ax + s.r, s.ay + s.r, s.r - 2, s.dir)
      BevelTriangle(s.win, s.ax + s.r, s.ay + s.r, s.r, s.dir, -2)
      s.callback.V.set(s.callback, s, prev := press_Varrow(s))
      delay(200)
      while (*Pending(s.win) = 0) |
	    (Event(s.win) === (&ldrag|&mdrag|&rdrag)) do {
         new := press_Varrow(s)
	 if new ~= prev then
            s.callback.V.set(s.callback, s, prev := new)
         delay(40)
         }
      draw_Varrow(s)
      return \(s.callback.value)
      }
end

procedure draw_Varrow(s)
   EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
   BevelTriangle(s.win, s.ax + s.r, s.ay + s.r, s.r, s.dir)
end

procedure press_Varrow(s)
   local v
   v := s.incop(s.callback.value, s.callback.inc)
   if abs(v) < abs(s.callback.inc) / 1000000.0 then	# if close to zero
      v -:= v					# set to zero, preserving type
   return v
end

procedure init_Varrow(s)
   if /s.aw then _Vbomb("must specify a size for a Varrow")
   if (/s.rev & s.dir == !"se") | (\s.rev & s.dir == !"nw") then
      s.incop := proc("+", 2)
   else
      s.incop := proc("-", 2)
   s.ah := s.aw
   s.id := V_ARROW
end

############################################################################
# Vvthumb
############################################################################
record Vthumb_rec (win, callback, id, aw, ah, win_sz, tot_sz, discont,
   sp, sw, tw, th, ws, cv_range, pos, rev, frame, drawn, type,
   ax, ay, uid, P, V)

procedure procs_Vvthumb()
   static procs
   initial procs := Vstd(event_Vvthumb, draw_Vvthumb, 1,
                        resize_Vidget, inrange_Vpane, init_Vvthumb,
                        couplerset_Vvthumb,,,,,set_value_Vvthumb)
   return procs
end

procedure Vvthumb(params[])
local frame, x, y, ins, self

   if ins := Vinsert_check(params) then {
      frame := pop(params); x := pop(params); y:= pop(params)
      }
   self := Vthumb_rec ! params
   self.uid := Vget_uid()
   self.V := procs_Vvthumb()
   self.P := Vstd_pos()
   self.type := 1
   self.V.init(self)
   if \ins then VInsert(frame, self, x, y)
   return self
end

#
#  debugging statement--
#
# write("draw: val ", val, " cv value ", s.callback.value, " cv min ",
# s.callback.min, "  ws ", s.ws, "  cv range ", s.cv_range)
#
procedure draw_Vvthumb(s)
   local val

   s.drawn := 1
   val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
   if \s.rev then
      val := s.ws - val
   s.pos := val
   BevelRectangle(s.win, s.ax, s.ay + val, s.tw, s.th)
end

procedure event_Vvthumb(s, e)
local value, offset

   if \s.callback.locked then fail
   if e === (&lpress|&mpress|&rpress) then {
      offset := (s.th + 1) / 2
      until e === (&lrelease|&mrelease|&rrelease) do {
         value := ((&y - offset - s.ay) / (0 ~= s.ws)) * s.cv_range | 0
         if \s.rev then
            s.callback.V.set(s.callback, s, s.callback.max - value, s.discont)
         else
            s.callback.V.set(s.callback, s, s.callback.min + value, s.discont)
         s.frame.data := s.callback.value
         update_Vvthumb(s, 1)
         e := Event(s.win)
         }
      update_Vvthumb(s)
      if \s.discont then
         s.callback.V.set(s.callback, s, s.callback.value)
      return \(s.callback.value)
      }
end

procedure update_Vvthumb(s, active)
local val, op, tw, th, sw, sp

   val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
   if \s.rev then
      val := s.ws - val

   op := s.pos; tw := s.tw; th := s.th
   sp := s.sp; sw := s.sw
   EraseArea(s.win, s.ax, s.ay + op, tw, th)
   if \active then {
      BevelRectangle(s.win, s.ax, s.ay + val, tw, th, -2)
      FillRectangle(s.win, s.ax + 2, s.ay + val + 2, tw - 4, th - 4)
      }
   else
      BevelRectangle(s.win, s.ax, s.ay + val, tw, th)
   s.pos := val
end

procedure set_value_Vvthumb(s, value)
   couplerset_Vvthumb(s, , value)
end

procedure couplerset_Vvthumb(s, caller, value)
   value := numeric(value) | s.callback.min
   if (\caller).id === V_ARROW then caller := s
   else if value === s.callback.value then fail
   s.frame.data := s.callback.value := value
   if \s.drawn then
      update_Vvthumb(s)
end

procedure init_Vvthumb(s)
   static type

   initial type := proc("type", 0)	# protect attractive name

   if /s.aw | /s.ah then
      _Vbomb("must specify width and height for Vvthumb")
   if /s.callback | type(s.callback) == "procedure" then
      _Vbomb("Vvthumb requires a coupler variable callback")
   s.sw := 3
   s.sp:= (s.aw - s.sw) / 2
   s.tw := s.aw
   \s.win_sz <:= 0
   if /s.win_sz then s.th := s.tw
   else s.th := ( s.tw < 
                integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.ah) ) |
                s.tw
   s.ws := 0 < real(s.ah - s.th) | 0
   s.cv_range := (0 < s.callback.max - s.callback.min | 1.0)

end

############################################################################
# Vhthumb
############################################################################

procedure procs_Vhthumb()
   static procs
   initial procs := Vstd(event_Vhthumb, draw_Vhthumb, 1,
                        resize_Vidget, inrange_Vpane, init_Vhthumb,
                        couplerset_Vhthumb,,,,,set_value_Vhthumb)
   return procs
end

procedure Vhthumb(params[])
local frame, x, y, ins, self

   if ins := Vinsert_check(params) then {
      frame := pop(params); x := pop(params); y:= pop(params)
      }
   self := Vthumb_rec ! params
   self.uid := Vget_uid()
   self.V := procs_Vhthumb()
   self.P := Vstd_pos()
   self.V.init(self)
   if \ins then VInsert(frame, self, x, y)
   return self
end

procedure draw_Vhthumb(s)
   local val

   s.drawn := 1
   val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
   if \s.rev then
      val := s.ws - val
   s.pos := val
   BevelRectangle(s.win, s.ax + val, s.ay, s.tw, s.th)
end

procedure event_Vhthumb(s, e)
local value, offset

   if \s.callback.locked then fail
   if e === (&lpress|&mpress|&rpress) then {
      offset := (s.tw + 1) / 2
      until e === (&lrelease|&mrelease|&rrelease) do {
         value := ((&x - offset - s.ax)/(0 ~= s.ws)) * s.cv_range | 0
         if \s.rev then
            s.callback.V.set(s.callback, s, s.callback.max - value, s.discont)
         else
            s.callback.V.set(s.callback, s, s.callback.min + value, s.discont)
         s.frame.data := s.callback.value
         update_Vhthumb(s, 1)
         e := Event(s.win)
         }
      update_Vhthumb(s)
      if \s.discont then
         s.callback.V.set(s.callback, s, s.callback.value)
      return \(s.callback.value)
      }
end

procedure update_Vhthumb(s, active)
   local val, op, tw, th, sw, sp

   val := integer((s.callback.value - s.callback.min) * s.ws / s.cv_range + 0.5)
   if \s.rev then
      val := s.ws - val

   op := s.pos; tw := s.tw; th := s.th
   sp := s.sp; sw := s.sw
   EraseArea(s.win, s.ax + op, s.ay, tw, th)
   if \active then {
      BevelRectangle(s.win, s.ax + val, s.ay, tw, th, -2)
      FillRectangle(s.win, s.ax + val + 2, s.ay + 2, tw - 4, th - 4)
      }
   else
      BevelRectangle(s.win, s.ax + val, s.ay, tw, th)
   s.pos := val
end

procedure set_value_Vhthumb(s, value)
   couplerset_Vhthumb(s, s, value)
end

procedure couplerset_Vhthumb(s, caller, value)

   value := numeric(value) | s.callback.min
   if (\caller).id === V_ARROW then caller := s
   else if value === s.callback.value then fail
   s.frame.data := s.callback.value := value
   if \s.drawn then
      update_Vhthumb(s)
end

procedure init_Vhthumb(s)
   static type

   initial type := proc("type", 0)	# protect attractive name

   if /s.aw | /s.ah then
      _Vbomb("must specify width and height for Vhthumb")
   if /s.callback | type(s.callback) == "procedure" then
      _Vbomb("Vhthumb requires a coupler variable callback")
   s.sw := 3
   s.sp := (s.ah - s.sw) / 2
   s.th := s.ah
   \s.win_sz <:= 0
   if /s.win_sz then s.tw := s.th
   else s.tw := ( s.th < 
                integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.aw) ) | 
                s.th
   s.ws := 0 < real(s.aw - s.tw) | 0
   s.cv_range := (0 < s.callback.max - s.callback.min | 1.0)
end

############################################################################
# Vscrollbar_frame 
############################################################################

record Vscrollbar_frame_rec(win, callback, id, aw, ah, lookup, draw, uid,
   data, thumb, ax, ay, P, V)

procedure Vscrollbar_frame(params[])
local self, procs

   procs := Vstd(event_Vframe, draw_Vframe, outline_Vscrollbar,
                resize_Vscrollbar, inrange_Vpane, init_Vframe,
                couplerset_Vpane, insert_Vframe, remove_Vframe,
                lookup_Vframe, set_abs_Vframe)
   self := Vscrollbar_frame_rec ! params
   self.uid := Vget_uid()
   self.V := procs
   self.P := Vstd_pos()
   self.V.init(self)
   return self
end

procedure outline_Vscrollbar(self)
   BevelRectangle(self.win, self.ax, self.ay, self.aw, self.ah, -2)
end

procedure resize_Vscrollbar(self, x, y, w, h)

   resize_Vframe(self, x, y, w, h)

   if self.aw > self.ah then {
      if \self.thumb.type then {		# was formerly vertical
         self.thumb.V := procs_Vhthumb()
	 self.thumb.type := &null
	 }
      VReformat(self, self.aw, self.ah)
      }

   else {
      if /self.thumb.type then {		# was formerly horizontal
         self.thumb.V := procs_Vvthumb()
	 self.thumb.type := 1
	 }
      VReformat(self, self.ah, self.aw)
      }
end

#  These are the middle-man procedures between the scrollbar frame 
#  and the thumb.

procedure couplerset_Vhscrollbar(s, caller, value)
   couplerset_Vhthumb(s.thumb, caller, value)
end

procedure set_value_Vhscrollbar(s, value)
   set_value_Vhthumb(s.thumb, value)
   return
end

procedure couplerset_Vvscrollbar(s, caller, value)
   couplerset_Vvthumb(s.thumb, caller, value)
end

procedure set_value_Vvscrollbar(s, value)
   set_value_Vvthumb(s.thumb, value)
   return
end

############################################################################
# Vertical scrollbar 
############################################################################
procedure Vvert_scrollbar(params[])
local frame, x, y, ins, t, self

   if ins := Vinsert_check(params) then {
      frame := pop(params); x := pop(params); y:= pop(params)
      }

   self :=  Vmake_vscrollbar ! params
   self.uid := Vget_uid()
   if \ins then VInsert(frame, self, x, y)
   return self
end

procedure Vmake_vscrollbar(win, callback, id, length, width, 
                           min, max, inc, win_sz, discont)
   local cv, cb, frame, up, down, thumb, tot_sz
   local r, rev, in_max, odd
   static type

   initial type := proc("type", 0)	# protect attractive name

   Vwin_check(win, "Vvert_scrollbar()")
   if (\win_sz, not numeric(win_sz) | win_sz < 0 ) then
      _Vbomb("negative or non-numeric window_size parameter to Vvert_scrollbar()")
   if (\inc, not numeric(inc) | inc  < 0 ) then
      _Vbomb("negative or non-numeric increment parameter to Vvert_scrollbar()")
   if (\length, not numeric(length) ) then
      _Vbomb("invalid length parameter to Vvert_scrollbar()")
   if (\width, not numeric(width) ) then
      _Vbomb("invalid width parameter to Vvert_scrollbar()")

   /width := VSlider_DefWidth
   /length := VSlider_DefLength
   width <:= VSlider_MinWidth
   length <:= VSlider_MinAspect * width
   /min := 0
   /max := 1.0
   rev := 1
   if max < min then { max :=: min; rev := &null }
   in_max := max
   max -:= (\win_sz | 0) 
   max <:= min
   tot_sz := 0 < abs(in_max-min) | 1
   r := (type(min|max) == "real", 1)
   if (not numeric(\inc) ) | /inc then
      inc := 0.1*abs(max-min)
   (/r, inc := integer(inc), inc <:= 1)

   cv := Vrange_coupler(min, max, , inc)
   frame := Vscrollbar_frame(win, cv, id, width, length)
   Varrow(frame, 2, 2, win, cv, width - 4, width - 4, rev, "n")
   odd := width % 2
   thumb := Vvthumb(frame, 2, width - odd, 
      win, cv, id, width - 4, length - 2 * width + 1 + odd,
      win_sz, tot_sz, discont)
   Varrow(frame, 2, length - width + 2, win, cv, width - 4, width - 4, rev, "s")

   thumb.rev := rev
   cv.V.add_client(cv, thumb)
   add_clients_Vinit(cv, callback, thumb)

   thumb.frame := frame
   frame.thumb := thumb
   frame.V.couplerset := couplerset_Vvscrollbar
   frame.V.set_value := set_value_Vvscrollbar

   return frame
end

############################################################################
# Horizontal scrollbar 
############################################################################
procedure Vhoriz_scrollbar(params[])
local frame, x, y, ins, t, self

   if ins := Vinsert_check(params) then {
      frame := pop(params); x := pop(params); y:= pop(params)
      }

   self :=  Vmake_hscrollbar ! params
   self.uid := Vget_uid()
   if \ins then VInsert(frame, self, x, y)
   return self
end

procedure Vmake_hscrollbar(win, callback, id, length, width,
                           min, max, inc, win_sz, discont)
   local cv, cb, frame, up, down, thumb, tot_sz
   local r, rev, in_max, odd
   static type

   initial type := proc("type", 0)	# protect attractive name

   Vwin_check(win, "Vhoriz_scrollbar().")
   if (\win_sz, not numeric(win_sz) | win_sz < 0 ) then
      _Vbomb("negative or non-numeric window_size parameter to Vhoriz_scrollbar()")
   if (\inc, not numeric(inc) | inc  < 0 ) then
      _Vbomb("negative or non-numeric increment parameter to Vhoriz_scrollbar()")
   if (\length, not numeric(length) ) then
      _Vbomb("invalid length parameter to Vhoriz_scrollbar()")
   if (\width, not numeric(width) ) then
      _Vbomb("invalid width parameter to Vhoriz_scrollbar()")

   /width := VSlider_DefWidth
   /length := VSlider_DefLength
   width <:= VSlider_MinWidth
   length <:= VSlider_MinAspect * width
   /min := 0
   /max := 1.0
   if max < min then {max :=: min; rev := 1 }
   in_max := max
   max -:= (\win_sz | 0)
   max <:= min
   tot_sz := 0 < abs(in_max-min) | 1
   r := (type(min|max) == "real", 1)
   if (not numeric(\inc) ) | /inc then
      inc := 0.1*abs(max-min)
   (/r, inc := integer(inc), inc <:= 1)

   cv := Vrange_coupler(min, max, , inc)
   frame := Vscrollbar_frame(win, cv, id, length, width)
   Varrow(frame, 2, 2, win, cv, width - 4, width - 4, rev, "w")
   odd := width % 2
   thumb := Vhthumb(frame, width - odd, 2, 
      win, cv, id, length - 2 * width + 1 + odd, width - 4,
      win_sz, tot_sz, discont)
   Varrow(frame, length - width + 2, 2, win, cv, width-4, width-4, rev, "e")

   thumb.rev := rev
   cv.V.add_client(cv, thumb)
   add_clients_Vinit(cv, callback, thumb)

   thumb.frame := frame
   frame.thumb := thumb
   frame.V.couplerset := couplerset_Vhscrollbar
   frame.V.set_value := set_value_Vhscrollbar

   return frame
end

############################################################################
# reformatting procedures.  Will just reformat width and length.
############################################################################
procedure reformat_Vvthumb(s, length, width)

   s.P.w := s.aw := \width
   s.P.h := s.ah := \length
   s.sp := (s.aw - s.sw) / 2
   s.tw := s.aw
   if /s.win_sz then s.th := s.tw
   else s.th := ( s.tw <
                integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.ah) ) |
                s.tw-1
   s.ws := 0 < real(s.ah - s.th - 2) | 0
end

procedure reformat_Vhthumb(s, length, width)

   s.P.w := s.aw := length
   s.P.h := s.ah := width
   s.sp := (s.ah - s.sw) / 2
   s.th := s.ah
   if /s.win_sz then s.tw := s.th
   else s.tw := ( s.th <
                integer( ((1.0 >= real(s.win_sz)/s.tot_sz) | 1.0)\1 * s.aw) ) |
                s.th-1
   s.ws := 0 < real(s.aw - s.tw - 2) | 0
end

procedure Vreformat_vscrollbar(self, length, width)
   local up, down, thumb

   /width := self.aw
   /length := self.ah
   self.aw := self.P.w := width
   self.ah := self.P.h := length

   up := self.lookup[1]
   thumb := self.lookup[2]
   down := self.lookup[3]

   VRemove(self, up, 1)
   VRemove(self, thumb, 1)
   VRemove(self, down, 1)

   up.dir := "n"
   down.aw := down.ah := up.aw := up.ah :=
      down.P.w := down.P.h := up.P.w := up.P.h := width
   down.r := up.r := (width - 4) / 2
   down.dir := "s"

   reformat_Vvthumb(thumb, length - 2 * width + 2, width - 4)
   VInsert(self, up, 2, 2)
   VInsert(self, thumb, 2, width)
   VInsert(self, down, 2, width + thumb.ah)

end

procedure Vreformat_hscrollbar(self, length, width)
   local left, right, thumb

   /width := self.ah
   /length := self.aw
   self.aw := self.P.w := length
   self.ah := self.P.h := width

   left := self.lookup[1]
   thumb := self.lookup[2]
   right := self.lookup[3]

   VRemove(self, left, 1)
   VRemove(self, thumb, 1)
   VRemove(self, right, 1)

   left.dir := "w"
   left.aw := left.ah := right.aw := right.ah :=
      left.P.w := left.P.h := right.P.w := right.P.h := width
   left.r := right.r := (width - 4) / 2
   right.dir := "e"

   reformat_Vhthumb(thumb, length - 2 * width + 2, width - 4)
   VInsert(self, left, 2, 2)
   VInsert(self, thumb, width, 2)
   VInsert(self, right, width + thumb.aw, 2)
end

############################################################################
#  interface procedure for Vreformat
############################################################################
procedure VReformat(scrollbar, length, width)
   static type

   initial type := proc("type", 0)	# protect attractive name

   if /scrollbar | type(scrollbar) ~== "Vscrollbar_frame_rec" then
      _Vbomb("invalid scrollbar parameter to VReformat()")

   if \(scrollbar.thumb.type) then
      Vreformat_vscrollbar(scrollbar, length, width)
   else 
      Vreformat_hscrollbar(scrollbar, length, width)
end
