#
# $Id: tabset.icn,v 1.5 2004/11/05 19:24:55 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 holds the several {TabItems}, and represents a tabbed pane.
#
# A SELECTION_CHANGED_EVENT is fired whenever the tab is changed via
# user interaction.
#
class TabSet : Component(
   which_one,               #                 
   tab_h,                   #             
   lines,                   #             
   line_h,                  #              
   line_break               #
   )

   method get_y_reference()
      return self.y + self.tab_h
   end

   method get_h_reference()
      return self.h - self.tab_h
   end

   method display(buffer_flag)
      local last_on_a_line, hw

      #
      # Erase all and display outline of tabbed pane area.
      #
      EraseRectangle(self.cbwin, self.x, self.y, self.w, self.h)
      DrawRaisedRectangle(self.cbwin, self.x, self.y, self.w, self.h)

      #
      # Display all tabs.
      #
      every (!!self.line_break).display_tab()

      last_on_a_line := (!self.line_break)[-1] === self.which_one

      #
      # Display line under tabs.
      #
      hw := get_hilite_win(self.cbwin)

      DrawLine(hw, self.x, self.y + self.tab_h - 2, self.which_one.label_x, self.y + self.tab_h - 2)

      if /last_on_a_line then
         DrawLine(hw, self.which_one.label_x + self.which_one.label_w, self.y + self.tab_h - 2, self.x + self.w - 1, self.y + self.tab_h - 2)

      DrawLine(hw, self.x, self.y + self.tab_h - 1, self.which_one.label_x, self.y + self.tab_h - 1)

      if /last_on_a_line then
         DrawLine(hw, self.which_one.label_x + self.which_one.label_w, self.y + self.tab_h - 1, self.x + self.w - 2, self.y + self.tab_h - 1)

      #
      # Display contents of current tab into buffer
      #
      which_one.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

   #
   # Determine which tab if any mouse is over.
   #
   # @p
   method which_tab()
      l := (&y - self.y) / self.line_h + 1
      every c := !self.line_break[l] do {
         if c.is_unshaded() & (c.label_x <= &x < c.label_x + c.label_w) then
            return c
      }
   end

   #
   # Ensure which_one is at front of tab lines
   #
   # @p
   method adjust_lines()
      l := self.which_one.line_no
      self.line_break[l] :=: self.line_break[self.lines]
      every (!self.line_break[l]).line_no := l
      every (!self.line_break[self.lines]).line_no := self.lines
   end

   #
   # Set which tab is currently on display.
   # @param x  The {TabItem} to be displayed.
   #
   method set_which_one(x)
      if \ (\self.parent_dialog).is_open then {
         self.which_one := x
         self.adjust_lines()
         self.invalidate()
      } else
         self.which_one := x
      
      return x
   end

   #
   # Return the currently selected tab
   #
   method get_which_one()
      return self.which_one
   end

   method handle_event(e)
      if &meta & m := find_key(e) then {
         self.set_which_one(m)
         fire(SELECTION_CHANGED_EVENT, e)     
      } else if e === (&lpress | &rpress | &mpress) & (self.y <= &y < self.y + self.tab_h) then {
         self.set_which_one(which_tab())
         fire(SELECTION_CHANGED_EVENT, e)     
      }

      which_one.handle_event(e)
   end

   #
   # Find the TabItem with the given accelerator.  This isn't done using
   # the handle_accel method in the TabItem because only the current 
   # TabItem is treated as being unhidden.
   # @p
   method find_key(k)
      local m
      every m := !self.children do {
         if m.accel === k & not(m.is_shaded()) then
            return m
      }
   end

   #
   # Break the set of tabs up into lines, given the padding within each tab.
   # Returns a list each element of which is a list of those tabs on one line.
   #
   # @p
   method how_many_lines(pad)
      t := 0
      l := []
      cl := []
      every c := !self.children do {
         lw := TextWidth(self.cwin, c.label) + pad
         if (t > 0) & (t + lw > self.w) then {
            #
            # New line required.
            #
            t := 0
            put(l, cl)
            cl := []
         }
         t +:= lw
         put(cl, c)
      }
      #
      # Final line, if any
      #
      if t > 0 then
         put(l, cl)

      return l
   end

   method compute_absolutes()
      if *self.children = 0 then
         fatal("no TabItems in TabSet")

      every (!self.children).check_label()

      self.Component.compute_absolutes()

      #
      # Determine how many lines for minimum padding.
      #
      pad := 2 * DEFAULT_TEXT_X_SURROUND
      self.lines := *(line_break := how_many_lines(pad))

      self.line_h := WAttrib(self.cwin, "fheight") + 2 * DEFAULT_TEXT_Y_SURROUND
      self.tab_h :=  self.line_h * self.lines

      #
      # Expand padding whilst can do so and remain within the original
      # number of lines.  This should even out the tabs.
      #
      if 1 < self.lines < *self.children then {
         while *(l2 := how_many_lines(pad + X_PADDING_INC)) <= self.lines do {
            self.line_break := l2
            pad +:= X_PADDING_INC
         }
      }

      #
      # Finally, space out the tabs on each line to fill up each line.
      #
      n := 1
      every curr_line := !self.line_break do {
         #
         # Work out total already used.
         #
         t := 0
         every c := !curr_line do {
            c.label_w := TextWidth(self.cwin, c.label) + pad
            c.line_no := n
            t +:= c.label_w
         }

         #
         # Amount to add to each tab.
         #
         d := (self.w - t) / *curr_line

         #
         # Add the amount, compute new total
         #
         t := 0
         every c := !curr_line do {
            c.label_x := self.x + t
            c.label_w +:= d
            t +:= c.label_w
         }

         #
         # Add residual amount to rightmost tab.
         #
         curr_line[-1].label_w +:= self.w - t
         n +:= 1
      }

      /self.which_one := self.children[1]
      self.adjust_lines()
   end

   initially(a[])
      self.Component.initially()
      set_fields(a)
end