
#  $Id: _table.icn,v 1.1 2003-05-31 06:09:04 jeffery Exp $

##
#  This class displays a table, the columns of which are set up
#  using TableColumns.
#
class Table : Component(
   contents,
   max_width,
   head_x,
   head_y,
   head_h,
   columns,
   tx,
   ty,
   tw,
   th,
   hsb,
   vsb,
   last_refresh_x,
   last_refresh_y,
   line_height,
   checked,
   select_one,
   select_many,
   prev_down,
   which_down,
   is_held,
   going_up,
   highlight_lst,
   highlight_lst2, # temporary quick fix for 2 colors
   rchecked #swj
   )

   ##
   #  Set the height of the buttons at the top in pixels.  If not
   #  invoked, a sensible default will be used.
   #  @param x   The height
   #
   method set_button_group_height(x)
      return self.head_h := x
   end

   method get_contents()
      return self.contents
   end

   method set_checked(l)
      return self.checked := l
   end

   method get_checked()
      return self.checked
   end

   #
   # Present page size
   #
   method get_page_size()
      return (\self.vsb)$get_page_size() | *self.contents
   end

   ##
   # Move to the given position.  Either parameter may be omitted.
   # @param x   The row to move to
   # @param y   The left offset to move to
   #
   method goto_pos(x, y)
      (\self.vsb)$set_value(\x - 1)
      (\self.hsb)$set_value(\y)
      self$redisplay()
   end

   method compute_and_redisplay()
      if \ (\self.parent_Dialog).is_open then
         self$set_internal_fields()
      self$redisplay()
   end

   ##
   #  Set the contents of the table.  The parameter should be a
   #  two dimensional list.  Each element of the list should
   #  correspond to one row of the table.
   #  @param x   The contents
   #
   method set_contents(x)
      self.contents := x
      #
      # Expand/contract list if necessary
      #
      while *self.checked < *x do put(self.checked)
      while *self.checked > *x do pull(self.checked)

      compute_and_redisplay()
      return x
   end

   method clear_selections()
      self.checked := list(*contents)
      redisplay()
      return
   end

   ##
   #  Add the given TableColumn to the Table.
   #  @param c  The column to add.
   #
   method add(c)
      put(self.columns, c)
      c$set_parent_table(self)
   end

   method final_setup(x, y)
      self$Component.final_setup(x, y)
      every (!self.columns)$final_setup(x, self)
   end

   ##
   #  Configure the table so that one row of the table may be highlighted.
   #
   method set_select_one()
      self.select_one := 1
   end

   ##
   #  Configure the table so that several rows of the table may be highlighted.
   #
   method set_select_many()
      self.select_many := 1
   end

   method finally()
      self$Component.finally()
      every (!self.columns)$finally()
      (\self.vsb)$finally()
      (\self.hsb)$finally()
      self.vsb := self.hsb := &null
   end

   #
   # Return item currently under the clicked cursor
   #
   method get_which_down()
      return \self.which_down
   end

   #
   # Return row currently under the clicked cursor
   #
   method row_get_which_down()
      return self.contents[\self.which_down]
   end

   #
   # Return item currently under the clicked cursor
   #
   method get_prev_down()
      return \self.prev_down
   end

   #
   # Return row currently under the clicked cursor
   #
   method row_get_prev_down()
      return self.contents[\self.prev_down]
   end

   #
   # Return a list of items highlighted
   #
   ##
   #  Return a list of rows selected
   #  @return A list of rows currently selected
   #
   method get_selections()
      r := []
      every i := 1 to *self.checked do
         if \self.checked[i] then
            put(r, i)
      return r
   end

   method get_right_selections() #swj
      return self.rchecked
   end

   ##
   #  Set the current selections to the list l, which is a list of
   #  row numbers.
   #  @param l   The list of item numbers.
   #
   method set_selections(l)
      self.checked := list(*self.contents)
      every self.checked[!l] := 1
      redisplay()
   end

   method get_last_line()
      nlines := (\self.vsb)$get_page_size() | *self.contents
      return self$get_line() + nlines - 1
   end

   method handle_press(e)
      if \ (self.select_one | self.select_many) & (self.tx <= &x < self.tx + self.tw) & (self.ty  <= &y < self.ty + self.th) then {
         #
         # Compute line clicked
         #
         l := (&y - self.ty) / self.line_height + self$get_line()
         nlines := (\self.vsb)$get_page_size() | *self.contents

         if self.which_down := (self$get_last_line() >= l) then {
            if \self.select_many & (&shift | &control) then {
               #
               # Click with shift/ctrl - the state will end here.
               #
               if &control then
                  self.checked[self.which_down] := if /self.checked[self.which_down] then 1 else &null
               else {
                  #
                  # &shift
                  #
                  if \self.prev_down then {
                     if self.prev_down > self.which_down then
                        every self.checked[self.which_down to self.prev_down] := 1
                     else
                        every self.checked[self.prev_down to self.which_down] := 1
                  } else
                     self.checked[self.which_down] := 1
               }
               self.prev_down := self.which_down
               self.which_down := &null
               self$refresh(1)
               return _Event(e, self, 1)
            } else {
               #
               # Normal click down
               #
               self.is_held := 1
               self.checked := list(*self.contents)
               self$refresh(1)
               return _Event(e, self, 0)
            }
         }
      }
   end
   
   method handle_rpress(e)
      if \ (self.select_one | self.select_many) & (self.tx <= &x < self.tx + self.tw) & (self.ty  <= &y < self.ty + self.th) then {
         #
         # Compute line clicked
         #
         l := (&y - self.ty) / self.line_height + self$get_line()
         nlines := (\self.vsb)$get_page_size() | *self.contents

         if self.which_down := (self$get_last_line() >= l) then {
            if \self.select_many & (&shift | &control) then {
               #
               # Click with shift/ctrl - the state will end here.
               #
              # self.prev_down := self.which_down
              # self.which_down := &null
              # self$refresh(1)
               self.rchecked := 0
               return _Event(e, self, 0)
            } else {
               #
               # Normal click down
               #
               self.is_held := 1
               self.rchecked := 1
               self$refresh(1)
               return _Event(e, self, 0)
            }
         }
      }
   end

   

   method end_state()
      if \self.is_held then {
         self.is_held := &null
         stop_ticker()
         #
         # Clear flag, refresh, return event
         #
         self.checked := list(*self.contents)
         self.checked[self.which_down] := 1
         self.rchecked := self.which_down
         self.which_down := &null
         self.prev_down := self.which_down
         self$refresh(1)
      }
   end

   method handle_drag(e)
      local old_down, l

      if \self.is_held then {
         #
         # Mouse drag - save present marked line
         #
         old_down := self.which_down

         #
         # Mouse drag - save present marked line
         #
         old_down := self.which_down

         if &y < self.ty then {
            self.going_up := 1
            if /self.ticker_rate then
               set_ticker(self.parent_Dialog.repeat_rate)
         } else if &y > self.ty + self.th then {
            self.going_up := &null
            if /self.ticker_rate then
               set_ticker(self.parent_Dialog.repeat_rate)
         } else {
            l := (&y - self.ty) / self.line_height + self$get_line()
            l >:= self$get_last_line()
            self.which_down := l
            stop_ticker()
         }

         #
         # Refresh if line changed
         #
         if old_down ~=== self.which_down then
            self$refresh(1)
      }
   end

   method tick()
      if \going_up then {
         self.which_down := self$get_line() - 1
         self.which_down <:= 1
         (\self.vsb)$set_value(self.vsb$get_value() - 1)
      } else {
         self.which_down := self$get_last_line() + 1
         self.which_down >:= *self.contents
         (\self.vsb)$set_value(self.vsb$get_value() + 1)
      }
      self$refresh(1)
   end

   method handle_release(e)
      if \self.is_held then {
         #
         # Mouse released after being held down.  Clear flag
         #
         self.is_held := &null
         stop_ticker()
         #
         # Clear flag, refresh, return event
         #
         self.checked := list(*self.contents)
         self.checked[self.which_down] := 1
         self.prev_down := self.which_down
         self.rchecked := self.which_down
         self.which_down := &null
         self$refresh(1)
         return _Event(e, self, 1)
      }
   end

   method handle_key_page_up(e)
      if i := (\self.vsb)$get_value() then {
         self.vsb$set_value(i - self.vsb.page_size)
         self$refresh()
      }
   end

   method handle_key_page_down(e)
      if i := (\self.vsb)$get_value() then {
         self.vsb$set_value(i + self.vsb.page_size)
         self$refresh()
      }
   end

   method handle_key_up(e)
      if i := (\self.vsb)$get_value() then {
         self.vsb$set_value(i - 1)
         self$refresh()
      }
   end

   method handle_key_down(e)
      if i := (\self.vsb)$get_value() then {
         self.vsb$set_value(i + 1)
         self$refresh()
      }
   end

   method handle_key_left(e)
      if i := (\self.hsb)$get_value() then {
         self.hsb$set_value(i - self.hsb.increment_size)
         self$scroll_buttons()
         self$refresh()
      }
   end

   method handle_key_right(e)
      if i := (\self.hsb)$get_value() then {
         self.hsb$set_value(i + self.hsb.increment_size)
         self$scroll_buttons()
         self$refresh()
      }
   end

   method handle_key_home(e)
      if (\self.vsb)$set_value(0) then
         self$refresh()
   end

   method handle_key_end(e)
      if (\self.vsb)$set_value(*self.contents - 1) then
         self$refresh()
   end

   method handle_notify(e)
      if e$get_component() === self.vsb then
         self$refresh()
      else if e$get_component() === self.hsb then {
         self$scroll_buttons()
         self$refresh()
      }
   end

   method handle_event(e)
      local old_down, l

      if E := (!self.columns)$handle_event(e) & E$get_code() > 0 then
          return E
      else if E := (\self.vsb)$handle_event(e) then {
         #
         # Handled by VSB; amend line number and refresh contents
         #
         self$refresh()
      } else if E := (\self.hsb)$handle_event(e) then {
         #
         # Handled by HSB; amend left offset and refresh
         #
         self$scroll_buttons()
         self$refresh()
      } else return if integer(e) = (&lpress | &rpress | &mpress) then
         handle_press(e)
      else return if integer(e) = (&rpress) then # swj
         handle_rpress(e)
      else if integer(e) = (&ldrag | &rdrag | &mdrag) then
         handle_drag(e)
      else if integer(e) = (&lrelease | &rrelease | &mrelease) then
         handle_release(e)
      else if \self.has_focus then {
         case e of {
            Key_Home : handle_key_home(e)
            Key_End : handle_key_end(e)
            Key_PgUp : handle_key_page_up(e)
            Key_PgDn : handle_key_page_down(e)
            Key_Up : handle_key_up(e)
            Key_Down : handle_key_down(e)
            Key_Left : handle_key_left(e)
            Key_Right : handle_key_right(e)
         }
      }
   end

   method resize()
      if /self.contents then
         error("no contents specified")
      /self.head_h := WAttrib(self.cwin, "fheight") +  2 * DEFAULT_TEXT_Y_SURROUND
      self$Component.resize()
      self$set_internal_fields()
   end

   method scroll_buttons()
      i := self$get_left_pos()
      every b := !self.columns do {
         b$set_pos(i - self.x, self.head_y - self.y)
         b$resize()
         i +:= b.w
      }
   end

   #
   # Called on resize, buttons resized, or contents amended
   #
   method set_internal_fields()
      every (!self.columns)$check_width()

      self.line_height := WAttrib(self.cwin, "fheight")

      #
      # Top of button region
      #
      self.head_y := self.y + BORDER_WIDTH

      #
      # Top left of text region
      #
      self.tx := self.x + TX_PADDING
      self.ty := self.head_y + self.head_h + TY_PADDING

      #
      # Initialize left_pos field, clear optimizing flags used
      # in display.
      #
      self.last_refresh_x := self.last_refresh_y := &null

      #
      # Compute maximum width
      #
      self.max_width := 0
      every self.max_width +:= (!self.columns).column_width

      #
      # Compute max/min heights/widths for text; max if no scroll bar
      # needed; min otherwise.   Deduct an extra border from max_th to
      # be even within the bottom of the whole and the bottom of the header.
      #
      max_th := self.h - self.head_h - 2 * TY_PADDING - 2 * BORDER_WIDTH
      max_tw := self.w - 2 * TX_PADDING
      min_th := max_th - SB_SIZE
      min_tw := max_tw - SB_SIZE

      #
      # Set flags indicating whether scroll bars needed.  0 => definitely not
      # 1 => yes if opposite scroll bar needed; 2 => definitely yes.
      #
      if min_th / self.line_height >= *self.contents then
         need_vsb := 0
      else if max_th / self.line_height >= *self.contents then
         need_vsb := 1
      else
         need_vsb := 2

      if min_tw >= self.max_width then
         need_hsb := 0
      else if max_tw >= self.max_width then
         need_hsb := 1
      else
         need_hsb := 2

      #
      # Case analysis on flags to set up correct scroll bars, text width
      # and height fields.
      #
      if (need_vsb < 2) & (need_hsb < 2) then {
         #
         # No scroll bars.
         #
         self.th := max_th
         self.tw := max_tw
         (\self.vsb)$finally()
         (\self.hsb)$finally()
         self.vsb := self.hsb := &null
      } else if (need_hsb + need_vsb > 2) then {
         #
         # Two scroll bars.
         #
         #
         # Two scroll bars.
         #
         if /self.vsb := ScrollBar() then
            new_vsb := 1
         if /self.hsb := ScrollBar() then {
            self.hsb$set_is_horizontal()
            new_hsb := 1
         }

         self.th := min_th
         self.tw := min_tw

         self.vsb$set_pos(self.w - SB_SIZE - BORDER_WIDTH, BORDER_WIDTH)
         self.vsb$set_size(SB_SIZE, self.h - SB_SIZE - BORDER_WIDTH * 2)

         self.hsb$set_pos(BORDER_WIDTH, self.h - SB_SIZE - BORDER_WIDTH)
         self.hsb$set_size(self.w - SB_SIZE - BORDER_WIDTH * 2, SB_SIZE)
      } else if (need_hsb = 0) & (need_vsb = 2) then {
         #
         # One vertical scroll bar.
         #
         if /self.vsb := ScrollBar() then
            new_vsb := 1
         (\self.hsb)$finally()
         self.hsb := &null

         self.th := max_th
         self.tw := min_tw

         self.vsb$set_pos(self.w - SB_SIZE - BORDER_WIDTH, BORDER_WIDTH)
         self.vsb$set_size(SB_SIZE, self.h  - BORDER_WIDTH * 2)
      } else if (need_hsb = 2) & (need_vsb = 0) then {
         #
         # One horizontal scroll bar.
         #
         if /self.hsb := ScrollBar() then {
            self.hsb$set_is_horizontal()
            new_hsb := 1
         }
         (\self.vsb)$finally()
         self.vsb := &null

         self.th := min_th
         self.tw := max_tw

         self.hsb$set_pos(BORDER_WIDTH, self.h - SB_SIZE - BORDER_WIDTH)
         self.hsb$set_size(self.w  - BORDER_WIDTH * 2, SB_SIZE)
      }

      #
      # Initialize scroll bars.
      #
      if \self.vsb then {
         self.vsb$set_page_size(self.th / self.line_height)
         self.vsb$set_total_size(*self.contents)
         if \new_vsb then {
            self.vsb$set_increment_size(1)
            self.vsb$set_value(0)
            self.vsb$final_setup(self$get_parent_Dialog(), self)
            self.vsb$resize()
            self.vsb$firstly()
         } else
            self.vsb$resize()
      }

      if \self.hsb then {
         self.hsb$set_page_size(self.tw)
         self.hsb$set_total_size(self.max_width)
         if \new_hsb then {
            self.hsb$set_increment_size(TextWidth(self.cwin, "m"))
            self.hsb$set_value(0)
            self.hsb$final_setup(self$get_parent_Dialog(), self)
            self.hsb$resize()
            self.hsb$firstly()
         } else
            self.hsb$resize()
      }

      #
      # Compute button x, y positions
      #
      self$scroll_buttons()
   end

   method get_left_pos()
      return (self.tx - (\self.hsb)$get_value()) | self.tx
   end

   method get_line()
      return  ((\self.vsb)$get_value() + 1) | 1
   end

   method display(buffer_flag)
      EraseRectangle(self.cbwin, self.x, self.y, self.w, self.h)
      DrawSunkenRectangle(self.cbwin, self.x, self.y, self.w, self.h,-2)
      self$text_area_to_buffer()
      self$button_area_to_buffer()

      (\self.vsb)$display(1)
      (\self.hsb)$display(1)

      self$do_shading(self.cbwin)
      if /buffer_flag then
         CopyArea(self.cbwin, self.cwin, self.x, self.y, self.w, self.h, self.x, self.y)
   end

   #
   # Re-draw the text area.  Use double-buffering to avoid flicker.
   #
   method refresh(redraw)
      line := self$get_line()
      left_pos := self$get_left_pos()

      #
      # Do nothing unless have to
      #
      if /redraw & (\self.last_refresh_x = line) & (\self.last_refresh_y = left_pos) then
         return


      if \redraw | not(\self.last_refresh_y = left_pos) then {
         self$button_area_to_buffer()
         CopyArea(self.cbwin, self.cwin, self.tx, self.head_y, self.tw, self.head_h, self.tx, self.head_y)
      }

      #
      # Save present co-ordinates
      #
      self.last_refresh_x := line
      self.last_refresh_y := left_pos


      text_area_to_buffer()
      self$do_shading(self.cbwin)

      #
      # Copy buffer to window
      #
      CopyArea(self.cbwin, self.cwin, self.tx, self.ty, self.tw, self.th, self.tx, self.ty)
   end

   method button_area_to_buffer()
      EraseRectangle(self.cbwin, self.tx, self.head_y, self.tw, self.head_h)
      #
      # Draw buttons into buffer
      #
      every (!self.columns)$display(1)
   end


   method text_area_to_buffer()
      local rev,h_set,h_set2

      if *\highlight_lst > 0 then
         h_set := set(highlight_lst)
      else
         h_set := set() 

      if *\highlight_lst2 > 0 then
         h_set2 := set(highlight_lst2)
      else
         h_set2 := set()  

      line := self$get_line()
      left_pos := self$get_left_pos()

      EraseRectangle(self.cbwin, self.tx, self.ty, self.tw, self.th)

      #
      # Number of lines to draw
      #
      nlines := (\self.vsb)$get_page_size() | *self.contents

      rev := Clone(self.cbwin, "drawop=reverse")
      yp := self.ty + self.line_height / 2

      #
      # Write the lines
      #
      every l := self.contents[i := line to line + nlines - 1] do {
         if  member(h_set, i) then
            WAttrib(self.cbwin, "fg=red")
         else if member(h_set2, i) then
            WAttrib(self.cbwin, "fg=blue") # swj

         every j := 1 to *self.columns do {
            x1 := self.columns[j].x + DEFAULT_TEXT_X_SURROUND
            w1 := self.columns[j].w - 2 * DEFAULT_TEXT_X_SURROUND
            clip_x1 := x1
            clip_x1 <:= self.tx
            clip_x2 := x1 + w1
            clip_x2 >:= self.tx + self.tw

            Clip(self.cbwin, clip_x1, self.ty, clip_x2 - clip_x1, self.th)

           
            case self.columns[j].internal_alignment of {
               "r" : right_string(self.cbwin, x1 + w1, yp, l[j])
               "c" : center_string(self.cbwin, x1 + w1 / 2, yp, l[j])
               "l" : left_string(self.cbwin, x1, yp, l[j]) 
            }
            Clip(self.cbwin)
         }
         WAttrib(self.cbwin, "fg=black")

         if \self.checked[i] | i = \self.which_down then {
          if member(h_set,i) then
            WAttrib(rev, "fg=pale cyan") # swj
          else if member(h_set2,i) then
            WAttrib(rev, "fg=pale cyan") # swj
            FillRectangle(rev, self.tx, yp - self.line_height / 2, self.tw, self.line_height)
         }

         yp +:= self.line_height
      }

      Uncouple(rev)
   end

   initially(argv[])
      self$Component.initially()
      self.columns := []
      self$set_accepts_tab_focus()
      self.checked := []
      self.rchecked := 0
      highlight_lst := []
      highlight_lst2 := []
      if *argv > 0 then set_fields(argv)
end
