#
# $Id: tree.icn,v 1.7 2004/11/13 20:49:23 rparlett Exp $
#
# This file is in the public domain.
#
# Author: Robert Parlett (parlett@dial.pipex.com)
#

package gui
link graphics

$include "guih.icn"

#
# This class represents a tree object.  A TREE_NODE_EXPANSION_EVENT occurs when
# a node is expanded/contracted.  Also, the events generated by the parent class
# SelectableScrollArea are inherited.
#
class Tree : SelectableScrollArea(
   root_node,
   default_bmps,
   col_w,
   bmp_w,
   bmp_h,
   plus,
   minus,
   little_h,
   little_w,
   last_expanded,
   show_root,
   show_root_handles
   )

   method get_line_height()
      res:= WAttrib(self.cwin, "fheight")
      #
      # Ensure the line height is slightly greater than the
      # bitmap height
      #
      res <:= \bmp_h + 4
      return res
   end

   method get_subject_width()
      mw := 0
      every s := !self.contents do
         mw <:= col_w * s.depth + col_w + TextWidth(self.cwin, s.label)
      return mw + 2 * HIGHLIGHT_TEXT_SPACING
   end

   #
   # Expand all the nodes in the {Tree}.
   #
   method expand()
      if \root_node then {
         root_node.expand()
         tree_structure_changed()
      }
   end

   #
   # Set the default bitmaps for each {Node} in the {Tree}.  The parameter should be
   # a list of three bitmaps;  see {Node} above for an explanation.
   # @param l  The list of bitmaps.
   #
   method set_default_bmps(l)
      return default_bmps := l
   end
   
   #
   # Set the root node of the {Tree}.
   #
   method set_root_node(r)
      root_node := r
      if /r then
         set_contents([])
      else {
         root_node.is_expanded_flag := 1
         compute_bmp_wh()
         set_contents(flatten())
         set_selections([])
      }
      return root_node
   end

   #
   # Get the root node of the tree
   #
   method get_root_node()
      return root_node
   end

   method tree_structure_changed()
      local sels
      if /root_node then
         return
      sels := object_get_selections()
      set_contents(flatten())
      object_set_selections(sels)
   end

   #
   # Set the component so that root handles are shown (the default).
   #
   method set_show_root_handles()
      self.show_root_handles := 1
      self.tree_structure_changed()
   end

   #
   # Set the component so that root handles are not shown.
   #
   method clear_show_root_handles()
      self.show_root_handles := &null
      self.tree_structure_changed()
   end

   #
   # Set the tree so that the root is shown (the default).
   #
   method set_show_root()
      self.show_root := 1
      self.tree_structure_changed()
   end

   #
   # Set the component such that the root node is not shown.
   #
   method clear_show_root()
      self.show_root := &null
      self.tree_structure_changed()
   end

   method compute_bmp_wh()
      bmp_w := bmp_h := 0

      every n := root_node.generate_all_preorder() do {
         bmps := \n.bmps | default_bmps
         bmp_w <:= img_width(bmps[1])
         bmp_w <:= img_width(bmps[2])
         bmp_w <:= img_width(bmps[3])
         bmp_h <:= img_height(bmps[1])
         bmp_h <:= img_height(bmps[2])
         bmp_h <:= img_height(bmps[3])
      }

      #
      # The column width is slightly wider than the bitmap width
      #
      col_w := bmp_w + 4
   end

   #
   # This method returns a flat list of all the {Nodes} in the tree that are
   # currently displayed.
   # @return  A list of nodes.
   #
   method flatten()
      l := []
      if \self.show_root then {
         if \self.show_root_handles then
            flatten2(l, root_node, "n")
         else
            flatten2(l, root_node, "")
      } else {
         if *root_node.subnodes = 1 then {
            flatten2(l, root_node.subnodes[1], "n")
         } else {
            flatten2(l, root_node.subnodes[1], "d")
            every sub := root_node.subnodes[2 to *root_node.subnodes - 1] do 
               flatten2(l, sub, "f")
            flatten2(l, root_node.subnodes[-1], "u")
         }
      }

      return l
   end

   method flatten2(l, n, dl)
      n.draw_line := dl
      n.depth := *dl
      put(l, n)
      if n.is_expanded() then {
         every sub := n.subnodes[1 to *n.subnodes - 1] do 
            flatten2(l, sub, dl || "f")
         flatten2(l, n.subnodes[-1], dl || "u")
      }
   end

   method handle_press(e)
      if (self.view.x <= &x < self.view.x + self.view.w) & (self.view.y  <= &y < self.view.y + self.view.h) then {
         lno := (&y - self.view.y) / self.line_height
         l := lno + self.get_first_line()
         if l <= self.get_last_line() then {
            N := self.contents[l]
            if (N.depth > 0) & (*N.subnodes > 0) | \N.always_expandable_flag then {
               #
               # Check for click on little +/- icon.
               #
               yp := self.view.y + self.line_height / 2 + self.line_height * lno - little_h / 2
               xp := self.get_left_pos() + (N.depth - 1) * col_w + little_w / 2
               if (xp <= &x < xp + little_w) & (yp <= &y < yp + little_h) then {
                  #
                  # Clicking on the little icon ends the sequence, and sets the selection
                  # to the given node.
                  #
                  start_handle(e)
                  N.toggle_expanded()
                  self.last_expanded := N
                  tree_structure_changed()
                  is_held := &null
                  selchange := 1
                  fire(TREE_NODE_EXPANSION_EVENT, e)
                  end_handle(e)
                  return
               }
            }
         }
      }
      self.SelectableScrollArea.handle_press(e)
   end

   method get_last_expanded()
      return self.last_expanded
   end

   method draw_line(xp, yp, i, selection_cw, cursor_cw, highlight_cw)
      local N, dashed, lp, j

      N := contents[i]
      dashed := Clone(self.cbwin, "pattern=gray", "fillstyle=textured")
      lp := xp
      every j := 1 to N.depth - 1 do {
         if N.draw_line[j] == ("f"|"d") then
            DrawLine(dashed, lp + col_w / 2, yp - self.line_height / 2, lp + col_w / 2, yp + self.line_height / 2)
         
         lp +:= col_w
      }
      if N.depth > 0 then {
         if N.draw_line[N.depth] == "d" then
            DrawLine(dashed, lp + col_w / 2, yp, lp + col_w / 2, yp + self.line_height / 2)
         else if N.draw_line[N.depth] == "f" then
            DrawLine(dashed, lp + col_w / 2, yp - self.line_height / 2, lp + col_w / 2, yp + self.line_height / 2)
         else if N.draw_line[N.depth] == "u" then
            DrawLine(dashed, lp + col_w / 2, yp - self.line_height / 2, lp + col_w / 2, yp)

         DrawLine(dashed, lp + col_w / 2, yp, lp + col_w + col_w / 2, yp)

         lp +:= col_w
      }

      bmps := \N.bmps | default_bmps
      if (*N.subnodes = 0) & /N.always_expandable_flag then
         img := bmps[3]
      else {
         if N.is_expanded() then {
            img := bmps[2]
            little := minus
            if *N.subnodes > 0 then
               DrawLine(dashed, lp + col_w / 2, yp, lp + col_w / 2, yp + self.line_height / 2)
         } else {
            img := bmps[1]
            little := plus
         }
         EraseArea(self.cbwin, lp - col_w / 2 - little_w / 2, yp - little_h / 2, little_w, little_h)
         DrawImageEx(self.cbwin, lp - col_w / 2 - little_w / 2, yp - little_h / 2, little)
      }
      DrawImageEx(self.cbwin, lp + col_w / 2 - bmp_w / 2, yp - bmp_h / 2, img)
      left_string(self.cbwin, lp + col_w + HIGHLIGHT_TEXT_SPACING, yp, N.label)

      if \selection_cw then
         FillRectangle(selection_cw, lp + col_w,
                       yp - self.line_height / 2, TextWidth(self.cbwin, N.label) + 2 * HIGHLIGHT_TEXT_SPACING, self.line_height)

      if \cursor_cw then {
         Rectangle(cursor_cw, lp + col_w,
                   yp - self.line_height / 2, TextWidth(self.cbwin, N.label) + 2 * HIGHLIGHT_TEXT_SPACING, self.line_height)
      }

      if \highlight_cw then {
         Rectangle(highlight_cw, lp + col_w,
                   yp - self.line_height / 2, TextWidth(self.cbwin, N.label) + 2 * HIGHLIGHT_TEXT_SPACING, self.line_height)
      }

      Uncouple(dashed)
   end

   method set_one(attr, val)
      case attr of {
         "show_root" :
            if test_flag(attr, val) then
               set_show_root()
            else
               clear_show_root()
         "show_root_handles" : 
            if test_flag(attr, val) then
               set_show_root_handles()
            else
               clear_show_root_handles()
         default: self.SelectableScrollArea.set_one(attr, val)
      }
   end

   initially(a[])
      self.SelectableScrollArea.initially()
      plus := img_style("plus")
      minus := img_style("minus")
      little_w := img_width(plus)
      little_h := img_height(plus)
      default_bmps := [img_style("closed_folder"), img_style("open_folder"), img_style("file")]
      show_root := show_root_handles := 1
      set_fields(a)
end