{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}

-- | HTk\'s /menus/.
-- A @Menu@ is a container for menu structures.
module HTk.Menuitems.Menu (

  Menu(..),
  HasMenu(..),

  createMenu,
  popup,

  post,
  unpost,

) where

import HTk.Kernel.Core
import HTk.Kernel.BaseClasses(Widget)
import HTk.Kernel.Configuration
import HTk.Kernel.Resources
import HTk.Kernel.Geometry
import Reactor.ReferenceVariables
import Events.Destructible
import Events.Synchronized
import Util.Computation
import HTk.Containers.Window


-- -----------------------------------------------------------------------
-- Menu
-- -----------------------------------------------------------------------

-- | The @Menu@ datatype.
data Menu = Menu GUIOBJECT (Ref Int)


-- -----------------------------------------------------------------------
-- class HasMenu
-- -----------------------------------------------------------------------

-- | Containers for menus (toplevel windows and menubuttons) instantiate the
-- @class HasMenu@.
class GUIObject w => HasMenu w where
  menu :: Menu -> Config w
  menu m w =
    do
      let (GUIOBJECT _ mostref) = toGUIObject m
      most <- getRef mostref
      cset w "menu" (show (objectname most))

-- | Windows are containers for menus.
instance Window w => HasMenu w


-- -----------------------------------------------------------------------
-- Menu Creation Command
-- -----------------------------------------------------------------------

createMenu :: GUIObject par => par
   -- ^ tearoff.  If True, means menu will be displayed in a
   -- separate top-level window.
   -> Bool
   -> [Config Menu]
   -> IO Menu
createMenu par to ol =
  do
    w <- createGUIObject (toGUIObject par) MENU menuMethods
    r <- newRef (if to  then 1 else 0)
    configure (Menu w r) (tearOff (if to then On else Off)  : ol)


-- -----------------------------------------------------------------------
-- Popup Menu
-- -----------------------------------------------------------------------

-- | Posts a menu (e.g. in respose of a keystroke or mousebutton press).
popup :: GUIObject i => Menu
   -- ^ The menu to post.
   -> Position
   -- ^ The position to pop-up.
   -> Maybe i
   -- ^ An optional entry to activate when the menu pops-up.
   -> IO ()
   -- ^ None.
popup m pos@(x,y) ent@Nothing =
  execMethod m (\nm -> tkPopup nm x y "")
popup m pos@(x,y) ent@(Just entry) =
  do
    name <- getObjectName (toGUIObject entry)
    case name of
      ObjectName s -> execMethod m (\nm -> tkPopup nm x y s)
      MenuItemName _ i -> execMethod m (\nm -> tkPopup nm x y (show i))
      _ -> done

tkPopup :: ObjectName -> Distance -> Distance -> String -> TclScript
tkPopup wn x y ent = ["tk_popup " ++ show wn ++ " " ++
        show x ++ " " ++ show y ++ " " ++ ent]
{-# INLINE tkPopup #-}


-- -----------------------------------------------------------------------
-- menu instances
-- -----------------------------------------------------------------------

-- | Internal.
instance Eq Menu where
  w1 == w2 = toGUIObject w1 == toGUIObject w2

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

-- | A menu can be destroyed.
instance Destroyable Menu where
  -- Destroys a menu.
  destroy = destroy . toGUIObject

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

-- | You can synchronize on a menu object.
instance Synchronized Menu where
  -- Synchronizes on a menu object.
  synchronize w = synchronize (toGUIObject w)

-- | A menu has a configureable border.
instance HasBorder Menu

-- | A menu has a normal foreground and background colour and an
-- active\/disabled foreground and background colour.
instance HasColour Menu where
  legalColourID w "background" = True
  legalColourID w "foreground" = True
  legalColourID w "activebackground" = True
  legalColourID w "activeforeground" = True
  legalColourID w _ = False

-- | You can specify the font of a menu.
instance HasFont Menu


-- -----------------------------------------------------------------------
-- config options
-- -----------------------------------------------------------------------

-- | A tear-off entry can be displayed with a menu.
tearOff :: Toggle
   -- ^ @On@ if you wish to display a tear-off
   -- entry, otherwise @Off@.
   -> Config Menu
   -- ^ The conerned menu.
tearOff tg mn = cset mn "tearoff" tg


-- -----------------------------------------------------------------------
-- Posting and Unposting Menues
-- -----------------------------------------------------------------------

-- | Displays a menu at the specified position.
post :: Menu
   -- ^ the menu to post.
   -> Position
   -- ^ the position to post the menu at.
   -> IO ()
   -- ^ None.
post mn pos@(x, y) = execMethod mn (\name -> tkPost name x y)

-- | Unmaps the menu.
unpost :: Menu
   -- ^ the menu to unmap.
   -> IO ()
   -- ^ None.
unpost mn = execMethod mn (\name -> tkUnPost name)


-- -----------------------------------------------------------------------
-- Menu methods
-- -----------------------------------------------------------------------

menuMethods = defMethods{ createCmd = tkCreateMenu,
                          packCmd = packCmd voidMethods }


-- -----------------------------------------------------------------------
-- Unparsing of Menu Commands
-- -----------------------------------------------------------------------

tkCreateMenu :: ObjectName -> ObjectKind -> ObjectName -> ObjectID ->
                [ConfigOption] -> TclScript
tkCreateMenu _ _ nm oid cnf =
  ["menu " ++ show nm ++ " " ++ showConfigs cnf]

tkPost :: ObjectName -> Distance -> Distance -> TclScript
tkPost name @ (ObjectName _) x y = [show name ++ " post " ++ show x ++ " " ++ show y]
tkPost name @ (MenuItemName mn i) _ _ = [show mn ++ " postcascade " ++ (show i)]
tkPost _ _ _ = []
{-# INLINE tkPost #-}

tkUnPost :: ObjectName -> TclScript
tkUnPost (MenuItemName _ _) = []
tkUnPost name = [show name ++ " unpost "]
{-# INLINE tkUnPost #-}