############################################################################
#
#	File:     vframe.icn
#
#	Subject:  Procedures for pane frame vidgets
#
#	Author:   Jon Lipp
#
#	Date:     May 2, 2001
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
#  Vidgets defined in this file:
#
# 	Vframe
#	Vroot_frame
#
#  Utility procedures in this file:
#
# 	Vmin_frame_width()
#	Vmin_frame_height()
#
############################################################################
#
#  Requires:  Version 9 graphics
#
############################################################################
#
#  Links:  vidgets
#
############################################################################

link vidgets

############################################################################
#	frame vidget -
#       Keeps track of panes.  Frames can contain
#       sub-frames in a hierarchy.  Frames know their own absolute
#       coordinates and the relative sizes and positions of their
#       children (panes and sub-frames).  They determine positioning
#       and size of each child, and route events.
############################################################################

record Vframe_rec(win, aw, ah, callback, id, lookup, draw, ax, ay,
   uid, P, F, V)

#
#  Creation procedure for a Vframe.
#	Specify its "own" utility procedures (V field).
#	Specify "special" procedures (format, in F field).
#	Get a unique id (uid).
#	check implicit insertion, insert if necessary.
#
procedure Vframe(params[])
   local self, procs, spec_procs, frame, x, y, ins

   procs := Vstd(event_Vframe, draw_Vframe, outline_Vidget,
      resize_Vframe, inrange_Vpane, init_Vframe,
      couplerset_Vpane, insert_Vframe, remove_Vframe,
      lookup_Vframe, set_abs_Vframe)
   spec_procs := Vstd_dialog( , , format_Vframe)

   if ins := Vinsert_check(params) then {
      frame := pop(params); x := pop(params); y:= pop(params)
      }
   self := Vframe_rec ! params[1:6|0]
   Vwin_check(self.win, "Vframe()")
   if (\self.aw, not numeric(self.aw) ) then
      _Vbomb("invalid aw parameter to Vframe()")
   if (\self.ah, not numeric(self.ah) ) then
      _Vbomb("invalid ah parameter to Vframe()")

   self.uid := Vget_uid()
   self.V := procs
   self.F := spec_procs
   self.P := Vstd_pos()
   self.V.init(self)
   if \ins then VInsert(frame, self, x, y)
   return self
end

#
#  Initialize procedure for Vframe.  Other frame types call this.
#
procedure init_Vframe(s)
   s.lookup := []
   s.draw := []
end

#
#  draw the contents of the frame.
#
procedure draw_Vframe(s, erased)
local p

#  PMIcon: fixed bug; drawig before resize.
   if /s.aw | /s.ah then _Vbomb("frame not resized yet")
   /erased & EraseArea(s.win, s.ax, s.ay, s.aw, s.ah)
   every p := !s.draw do p.V.draw(p, "erased")
   s.V.outline(s)
end

#
#  Set the absolute coordinates of everything on the draw list;
#  Don't do it for Vline, it is special.
#  It used to be that if the vidget is a Vpane,
#  a resize event was sent, so that it would notify its callback.
#  That "feature" has been commented out in the code below.
#
procedure resize_Vframe(s, x,y,wid,h)
   local w, slots
   static type

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

   resize_Vidget(s, x, y, wid, h)
   every w := !s.draw do {
      if (type(w) == "Vline_rec") then
         w.V.resize(s, w)
      else s.V.set_abs(s, w)
#     if type(w) == "Vpane_rec" then
#        w.V.event(w, -10)
      }
end
#
#  Determine the absolute coordinates of a vdiget based on its parent
#  frame's absolute coordinates, and the "virtual" coordinates passed
#  in upon creation.
#  Allows for the fact that a pane can have relative
#  position and size constraints intertwined with absolute.
#
procedure set_abs_Vframe(s, vid)
local ax,ay,aw,ah, a, b, w, h, vx, vy, vw, vh
   static type

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

   w := s.aw; h := s.ah
   vx := vid.P.x; vy := vid.P.y
   vw := vid.P.w; vh := vid.P.h

   ax := s.ax + ( (vx <= -1, w + vx - (\vid.aw | 0)) |
      (type(vx) == "real",
      (-1 <= vx < 0, w - vx*w) |
      (0 < vx <= 1, vx*w) ) | vx )
   ay := s.ay + ( (vy <= -1, h + vy - (\vid.ah | 0)) |
      (type(vy) == "real",
      (-1 <= vy < 0, h - vy*h) |
      (0 < vy <= 1, vy*h) ) | vy )

   aw := (\vw, (type(vw) == "real", 0 < vw <= 1, vw*w) |
      vw) | \vid.aw | w
   ah := (\vh, (type(vh) == "real", 0 < vh <= 1, vh*h) |
      vh) | \vid.ah | h
   aw := integer(aw)
   ah := integer(ah)

## don't let kid be bigger than the frame.
   if (a := aw + ax) > (b := s.aw + s.ax) then aw -:= (a-b)
   if (a := ah + ay) > (b := s.ah + s.ay) then ah -:= (a-b)
   vid.V.resize(vid, ax, ay, aw, ah)
end

#
# Don't erase the vidget if erase is non-&null.
#
procedure remove_Vframe(s, pane, erase)
local new, k

   new := []
   every k := !s.lookup do if k ~=== pane then put(new,k)
   s.lookup := new
   new := []
   every k := !s.draw do if k ~=== pane then put(new,k)
   s.draw := new

   if /erase then VErase(pane)
end

#
#  Insert a vidget into a frame.
#
procedure insert_Vframe(s, pane, x, y, w, h)
local wc
static image

   initial image := proc("image", 0)	# protet attractive name

#defaults
   /x := 0
   /y := 0
   /w := \pane.aw
   /h := \pane.ah
   pane.P.x := x
   pane.P.y := y
   pane.P.w := w
   pane.P.h := h
   put(s.draw, pane)
   if not (image(pane.V.event) ? find("null_proc") ) then
      put(s.lookup, pane)
   if (\s.ax, \s.ay, \s.aw, s.ah) then { # is this frame sized yet
      if (type(pane) == "Vline_rec") then
         pane.V.resize(s, pane)
      else
         s.V.set_abs(s, pane)
   }
end

#
#  Get events, lookup vidget based on (x, y), call its event loop.
#
procedure event_Vframe(s, e, x, y)
local dest

   if dest := s.V.lookup(s, x, y) then {
      return dest.V.event(dest, e, x, y)
   }
end

#
#  For every vidget on lookup list, check if (x, y) lie within its
#  boundaries.  Doesn't address overlapping vidgets.
#
procedure lookup_Vframe(s, x, y)
local w

   every w := !s.lookup do
      if w.V.inrange(w, x, y) then
         return w
end

#
#  Determine and set the minimum bounding rectangle which encompasses
#  all vidgets within the frame.  Restriction is that all vidgies must have
#  been inserted with absolute coordinates and sizes.
#
procedure format_Vframe(self)
   resize_Vidget(self, , , Vmin_frame_width(self), Vmin_frame_height(self))
end


############################################################################
# Vroot_frame -
# Root of the X-Idol event window demultiplexing recordes.
# The root_frame record serves as the root for windows that are
# subdivided.
############################################################################

procedure Vroot_frame(params[])
   local self
   static procs, spec_procs

   initial {
      procs := Vstd(event_Vroot_frame, draw_Vframe, null_proc,
                   resize_Vroot_frame, inrange_Vpane, init_Vroot_frame,
                   couplerset_Vpane, insert_Vframe, remove_Vframe,
                   lookup_Vframe, set_abs_Vframe)
      spec_procs := Vstd_dialog( , , format_Vframe)

      VInit()
   }

   self := Vframe_rec ! params[1:2|0]
   Vwin_check(self.win, "Vroot_frame()")
   self.uid := Vget_uid()
   self.V := procs
   self.F := spec_procs
   self.P := Vstd_pos()
   self.V.init(self)
   return self
end

procedure init_Vroot_frame(s)
   s.ax := s.ay := 0
   init_Vframe(s)
end

#
#  Process events (same as for a frame).  Difference, is if we get a resize,
#  resize all vidgets within, and redraw screen (no lookup performed).
#
procedure event_Vroot_frame(s,e,x,y)
local dest

   if e === &resize then {
      s.V.resize(s)
      return &null
   }
   else {
      if dest:= s.V.lookup(s,x,y) then
         return dest.V.event(dest,e,x,y)
      else fail
   }
end

#
# The window was resized!  Well... reconfigure all the absolute
# position and sizes for all panes.  This benefits relative values
# the most.
#
procedure resize_Vroot_frame(s)

   s.aw := WAttrib(s.win, "width")
   s.ah := WAttrib(s.win, "height")
   resize_Vframe(s, s.ax, s.ay, s.aw, s.ah)
   s.V.draw(s)
end

############################################################################
# Utility procedures for frames.
############################################################################

#
# Min--- returns the minimum size of the frame that will encase all
# children.  NOTE - this can only be determined if all the children
# were inserted with absolute co-ords and sizes. I.e. positive and
# integral x, y, w, & h.
#
procedure Vmin_frame_width(s)
   local max, vid
   static type

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

   max := 2
   every vid := (!s.draw) do
      if (type(vid) ~== "Vline_rec") then {
         if type(vid.P.x) == "real" | type(vid.P.w) == "real" |
            vid.P.x < 0 | vid.P.w < 0 then
            _Vbomb("attempt to format a frame with non-absolute sized and positioned children")
         max <:= (vid.P.x + vid.P.w )
      }
   return max
end

procedure Vmin_frame_height(s)
   local max, vid
   static type

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

   max := 2
   every vid := (!s.draw) do
      if (type(vid) ~== "Vline_rec") then {
         if type(vid.P.y) == "real" | type(vid.P.h) == "real" |
            vid.P.y < 0 | vid.P.h < 0 then
            _Vbomb("attempt to format a frame with non-absolute sized and positioned children")
         max <:= (vid.P.y + vid.P.h )
      }
   return max
end
