-- | HTk\'s /scrollbar/ widget.
--
-- A scroll bar is a widget which controls scrolling.
--
module HTk.Widgets.ScrollBar (

  HasScroller(..),
  ScrollBar,
  newScrollBar,

  ScrollUnit(..),

  Slider(..),
  HasSlider(..),

  ScrollBarElem(..),
  activateScrollBarElem,
  getActivatedElem,

  Fraction,
  fraction,
  identify,
  setView

) where

import HTk.Kernel.Core
import HTk.Kernel.BaseClasses(Widget)
import HTk.Kernel.Configuration
import HTk.Kernel.Geometry
import HTk.Kernel.Resources
import Events.Destructible
import HTk.Components.Slider
import Data.Char
import Util.Computation
import HTk.Kernel.Packer
import HTk.Kernel.Tooltip
import HTk.Kernel.GUIValue

-- -----------------------------------------------------------------------
-- fraction type
-- -----------------------------------------------------------------------

-- | Fractions are floating point values between 0 and 1 representing
-- relative positions within the scrolled range.
type Fraction = Double

data FractionPair = FractionPair Fraction Fraction

-- | Internal.
instance GUIValue FractionPair where
  cdefault = FractionPair 0.0 0.0

-- | Internal.
instance Show FractionPair where
  showsPrec d (FractionPair f1 f2) r = show f1 ++ " " ++ show f2 ++ r

-- | Internal.
instance Read FractionPair where
   -- Internal.
   readsPrec p b =
     case readsPrec p b of
       [(x,xs)] -> case readsPrec p xs of
                      [(y,ys)] -> [(FractionPair x y, ys)]
                      _        -> []
       _        -> []


-- -----------------------------------------------------------------------
-- classes
-- -----------------------------------------------------------------------

-- | Scrollable widgets instantiate @class HasScroller@.
class Widget w => HasScroller w where
  -- @True@ for widgets that are scrollable in the given
  -- orientation.
  isWfOrientation :: w -> Orientation -> Bool
  -- Associates a scrollbar with a scrollable widget.
  scrollbar       :: Orientation -> ScrollBar -> Config w
  -- Positions the scrolled widget so the give @Fraction@ is
  -- off-screen to the left.
  moveto          :: Orientation -> w -> Fraction -> IO ()
  -- Scrolls the associated widget by n pages or units (depending on the
  -- given @ScrollUnit@).
  scroll          :: Orientation -> w -> Int -> ScrollUnit -> IO ()
  -- Returns two fractions between 0 and 1 that describe the amount of
  -- the widget off-screen to the left and the amount of the widget visible.
  view            :: Orientation -> w -> IO (Fraction, Fraction)


  isWfOrientation _ _ = True

  scrollbar Horizontal sc w | isWfOrientation w Horizontal =
    do
      cset sc "command" (TkCommand (varname w ++ " xview"))
      execTclScript [tkDeclScrollVar w]
      cset w "xscrollcommand" (TkCommand (varname sc ++ " set"))
      execTclScript [tkDeclScrollVar sc]
      return w
  scrollbar Vertical sc w | isWfOrientation w Vertical =
    do
      cset sc "command" (TkCommand (varname w ++ " yview"))
      execTclScript [tkDeclScrollVar w]
      cset w "yscrollcommand" (TkCommand (varname sc ++ " set"))
      execTclScript [tkDeclScrollVar sc]
      return w
  scrollbar _ _ w = return w

  moveto ax w f | isWfOrientation w ax =
    execMethod w (\nm -> [tkMoveTo ax nm f])
  moveto _ _ _ = done

  scroll ax w num what | isWfOrientation w ax =
    execMethod w (\nm -> [tkScroll ax nm num what])
  scroll ax w num what = done

  view ax w =
    do
      FractionPair os vis <- (evalMethod w (tkView ax) :: IO FractionPair)
      return (os,vis)


-- -----------------------------------------------------------------------
-- datatype
-- -----------------------------------------------------------------------

-- | The @ScrollBar@ datatype.
newtype ScrollBar = ScrollBar GUIOBJECT deriving Eq


-- -----------------------------------------------------------------------
-- constructor
-- -----------------------------------------------------------------------

-- | Constructs a new scrollbar widget and returns a handler.
newScrollBar :: Container par => par
   -- ^ the parent widget, which has to be a container widget
   -- (an instance of @class Container@).
   -> [Config ScrollBar]
   -- ^ the list of configuration options for this scrollbar.
   -> IO ScrollBar
   -- ^ A scrollbar widget.
newScrollBar par cnf =
  do
    w <- createGUIObject (toGUIObject par) SCROLLBAR scrollbarMethods
    configure (ScrollBar w) cnf


-- -----------------------------------------------------------------------
-- ScrollBar configuration options
-- -----------------------------------------------------------------------

-- | Internal.
instance GUIObject ScrollBar where
  toGUIObject (ScrollBar w) = w
  cname _ = "ScrollBar"

-- | A scrollbar widget can be destroyed.
instance Destroyable ScrollBar where
  -- Destroys a scrollbar widget.
  destroy = destroy . toGUIObject

-- | A scrollbar widget has standard widget properties
-- (concerning focus, cursor).
instance Widget ScrollBar

-- | A scrollbar widget has a configureable border.
instance HasBorder ScrollBar

-- | A scrollbar widget has a background and activebackground
-- (regarding slider) colour.
instance HasColour ScrollBar where
  legalColourID w "bg" = True
  legalColourID w "activebackground" = True -- regards slider actually
  legalColourID w _ = False

-- | A scrollbar widget is a stateful widget, it can be enabled or
-- disabled.
instance HasEnable ScrollBar

-- | You can specify the width of a scrollbar.
instance HasSize ScrollBar where
  -- Dummy.
  height _ w = return w
  -- Dummy.
  getHeight w = return cdefault

-- | The scrollbar has a configureable slider component.
instance HasSlider ScrollBar

-- | The scrollbars orientation can be @Horizontal@ or
-- @Vertical@.
instance HasOrientation ScrollBar

-- | A scrollbar can have a tooltip.
instance HasTooltip ScrollBar


-- -----------------------------------------------------------------------
-- ScrollBar commands
-- -----------------------------------------------------------------------

-- | Sets the active element (which can be arrow1, arrow2 or slider).
activateScrollBarElem :: ScrollBar
   -- ^ the concerned scrollbar.
   -> ScrollBarElem
   -- ^ the element to activate.
   -> IO ()
   -- ^ None.
activateScrollBarElem sc elem =
  execMethod sc (\nm -> [tkActivate nm elem])

-- | Gets the active element (arrow1, arrow2 or slider).
getActivatedElem :: ScrollBar
   -- ^ the concerned scrollbar.
   -> IO (Maybe ScrollBarElem)
   -- ^ @Just [elem]@ if an element is active,
   -- otherwise @Nothing@.
getActivatedElem sc =
  do
    e <- evalMethod sc (\nm -> [tkGetActivate nm])
    case dropWhile isSpace e of
      "" -> return Nothing
      x -> return (Just (read x))

-- | Returns a fraction between 0 and 1 indicating the relative location
-- of the given position in the through.
fraction :: ScrollBar
   -- ^ the concerned scrollbar.
   -> Position
   -- ^ the conderned position.
   -> IO Fraction
   -- ^ The fraction indicating the relative location in the
   -- through.
fraction sc pos@(x, y) = evalMethod sc (\nm -> [tkFraction nm x y])

-- | Returns the @ScrollBarElem@ to indicate what is under
-- the given position.
identify :: ScrollBar
   -- ^ the concerned scrollbar.
   -> Position
   -- ^ the concerned position.
   -> IO (Maybe ScrollBarElem)
   -- ^ @Just [elem]@ if @[elem]@ is
   -- under the given position, otherwise
   -- @Nothing@.
identify sc pos@(x, y) =
  do
    e <- evalMethod sc (\nm -> [tkIdentify nm x y])
    case dropWhile (isSpace) e of
      "" -> return Nothing
      x -> return (Just (read x))

-- | Sets the scrollbar parameters.
setView :: ScrollBar
   -- ^ the concerned scrollbar.
   -> Fraction
   -- ^ fraction between 0 and 1 representing the relative
   -- position of the top left of the display.
   -> Fraction
   -- ^ fraction between 0 and 1 representing the relative
   -- position of the bottom right of the display.
   -> IO ()
   -- ^ None.
setView sc first last = execMethod sc (\nm -> [tkSet nm first last])


-- -----------------------------------------------------------------------
-- ScrollBar elem
-- -----------------------------------------------------------------------

-- | The @ScrollBarElem@ datatype - representing the elements
-- of the scrollbar.
data ScrollBarElem =
    Arrow1
  | Trough1
  | ScrollBarSlider
  | Trough2
  | Arrow2
  deriving (Eq,Ord,Enum)

-- | Internal.
instance GUIValue ScrollBarElem where
  cdefault = ScrollBarSlider

-- | Internal.
instance Read ScrollBarElem where
  readsPrec p b =
    case dropWhile (isSpace) b of
       'a':'r':'r':'o':'w':'1':xs -> [(Arrow1,xs)]
       't':'r':'o':'u':'g':'h':'1':xs -> [(Trough1,xs)]
       's':'l':'i':'d':'e':'r':xs -> [(ScrollBarSlider,xs)]
       't':'r':'o':'u':'g':'h':'2':xs -> [(Trough2,xs)]
       'a':'r':'r':'o':'w':'2':xs -> [(Arrow2,xs)]
       _ -> []

-- | Internal.
instance Show ScrollBarElem where
  showsPrec d p r =
     (case p of
         Arrow1 -> "arrow1"
         Trough1 -> "trough1"
         ScrollBarSlider -> "slider"
         Trough2 -> "trough2"
         Arrow2 -> "arrow2"
       ) ++ r


-- -----------------------------------------------------------------------
-- scroll unit
-- -----------------------------------------------------------------------

-- | The @ScrollUnit@ datatype - units for scrolling operations.
data ScrollUnit = Units | Pages

-- | Internal.
instance GUIValue ScrollUnit where
  cdefault = Units

-- | Internal.
instance Read ScrollUnit where
   -- Internal.
   readsPrec p b =
     case dropWhile (isSpace) b of
        'u':'n':'i':'t':'s':xs -> [(Units,xs)]
        'p':'a':'g':'e':'s':xs -> [(Pages,xs)]
        _ -> []

-- | Internal.
instance Show ScrollUnit where
   -- Internal.
   showsPrec d p r =
      (case p of
          Units -> "units"
          Pages -> "pages"
        ) ++ r


-- -----------------------------------------------------------------------
-- Scrollbar methods
-- -----------------------------------------------------------------------

scrollbarMethods = defMethods { cleanupCmd = tkCleanupScrollBar,
                                createCmd = tkCreateScrollBar }


-- -----------------------------------------------------------------------
-- Tk commands
-- -----------------------------------------------------------------------

tkCreateScrollBar :: ObjectName -> ObjectKind -> ObjectName -> ObjectID ->
                     [ConfigOption] -> TclScript
tkCreateScrollBar pnm kind name oid confs =
  tkDeclVar ("sv" ++ show oid) (show name) ++
  (createCmd defMethods) pnm kind name oid confs
{-# INLINE tkCreateScrollBar #-}

tkCleanupScrollBar :: ObjectID -> ObjectName -> TclScript
tkCleanupScrollBar oid _ = tkUndeclVar ("sv" ++ show oid)
{-# INLINE tkCleanupScrollBar #-}

varname :: GUIObject w => w -> String
varname w = (tkDeclScrollVar w) ++ "; $sv" ++ ((show .getObjectNo . toGUIObject) w)
{-# INLINE varname #-}

tkDeclScrollVar :: GUIObject w => w -> String
tkDeclScrollVar w = "global sv" ++ ((show .getObjectNo . toGUIObject) w)
{-# INLINE tkDeclScrollVar #-}

tkScroll :: Orientation -> ObjectName -> Int -> ScrollUnit -> TclCmd
tkScroll ax nm no what = show nm ++ " " ++ oshow ax ++ "view scroll " ++ show no ++ " " ++ show what
{-# INLINE tkScroll #-}

-- added Oct. '01, still experimental (ludi)
tkView :: Orientation -> ObjectName -> TclScript
tkView ax nm = [show nm ++ " " ++ oshow ax ++ "view"]
{-# INLINE tkView #-}

tkMoveTo :: Orientation -> ObjectName -> Fraction -> String
tkMoveTo ax nm f = show nm ++ " " ++ oshow ax ++ "view moveto " ++ show f
{-# INLINE tkMoveTo #-}

tkActivate :: ObjectName -> ScrollBarElem -> String
tkActivate nm e = show nm ++ " activate " ++ show e
{-# INLINE tkActivate #-}

tkGetActivate :: ObjectName -> String
tkGetActivate nm = show nm ++ " activate"
{-# INLINE tkGetActivate #-}

tkFraction :: ObjectName -> Distance -> Distance -> String
tkFraction nm x y = show nm ++ " fraction " ++ show x ++ " " ++ show y
{-# INLINE tkFraction #-}

tkIdentify :: ObjectName -> Distance -> Distance -> String
tkIdentify nm x y = show nm ++ " identify " ++ show x ++ " " ++ show y
{-# INLINE tkIdentify #-}

tkSet :: ObjectName -> Fraction -> Fraction -> String
tkSet nm x y = show nm ++ " set " ++ show x ++ " " ++ show y
{-# INLINE tkSet #-}

oshow Horizontal = "x"
oshow Vertical   = "y"